##// END OF EJS Templates
pdf: add the new feature to include attachment images (#3261)...
Toshi MARUYAMA -
r7795:b200fcfdae3c
parent child
Show More
@@ -1,538 +1,549
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'iconv'
20 require 'iconv'
21 require 'fpdf/chinese'
21 require 'fpdf/chinese'
22 require 'fpdf/japanese'
22 require 'fpdf/japanese'
23 require 'fpdf/korean'
23 require 'fpdf/korean'
24 require 'core/rmagick'
24 require 'core/rmagick'
25
25
26 module Redmine
26 module Redmine
27 module Export
27 module Export
28 module PDF
28 module PDF
29 include ActionView::Helpers::TextHelper
29 include ActionView::Helpers::TextHelper
30 include ActionView::Helpers::NumberHelper
30 include ActionView::Helpers::NumberHelper
31 include IssuesHelper
31 include IssuesHelper
32
32
33 class ITCPDF < TCPDF
33 class ITCPDF < TCPDF
34 include Redmine::I18n
34 include Redmine::I18n
35 attr_accessor :footer_date
35 attr_accessor :footer_date
36
36
37 def initialize(lang)
37 def initialize(lang)
38 @@k_path_cache = Rails.root.join('tmp', 'pdf')
38 @@k_path_cache = Rails.root.join('tmp', 'pdf')
39 FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache)
39 FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache)
40 set_language_if_valid lang
40 set_language_if_valid lang
41 pdf_encoding = l(:general_pdf_encoding).upcase
41 pdf_encoding = l(:general_pdf_encoding).upcase
42 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
42 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
43 case current_language.to_s.downcase
43 case current_language.to_s.downcase
44 when 'vi'
44 when 'vi'
45 @font_for_content = 'DejaVuSans'
45 @font_for_content = 'DejaVuSans'
46 @font_for_footer = 'DejaVuSans'
46 @font_for_footer = 'DejaVuSans'
47 else
47 else
48 case pdf_encoding
48 case pdf_encoding
49 when 'UTF-8'
49 when 'UTF-8'
50 @font_for_content = 'FreeSans'
50 @font_for_content = 'FreeSans'
51 @font_for_footer = 'FreeSans'
51 @font_for_footer = 'FreeSans'
52 when 'CP949'
52 when 'CP949'
53 extend(PDF_Korean)
53 extend(PDF_Korean)
54 AddUHCFont()
54 AddUHCFont()
55 @font_for_content = 'UHC'
55 @font_for_content = 'UHC'
56 @font_for_footer = 'UHC'
56 @font_for_footer = 'UHC'
57 when 'CP932', 'SJIS', 'SHIFT_JIS'
57 when 'CP932', 'SJIS', 'SHIFT_JIS'
58 extend(PDF_Japanese)
58 extend(PDF_Japanese)
59 AddSJISFont()
59 AddSJISFont()
60 @font_for_content = 'SJIS'
60 @font_for_content = 'SJIS'
61 @font_for_footer = 'SJIS'
61 @font_for_footer = 'SJIS'
62 when 'GB18030'
62 when 'GB18030'
63 extend(PDF_Chinese)
63 extend(PDF_Chinese)
64 AddGBFont()
64 AddGBFont()
65 @font_for_content = 'GB'
65 @font_for_content = 'GB'
66 @font_for_footer = 'GB'
66 @font_for_footer = 'GB'
67 when 'BIG5'
67 when 'BIG5'
68 extend(PDF_Chinese)
68 extend(PDF_Chinese)
69 AddBig5Font()
69 AddBig5Font()
70 @font_for_content = 'Big5'
70 @font_for_content = 'Big5'
71 @font_for_footer = 'Big5'
71 @font_for_footer = 'Big5'
72 else
72 else
73 @font_for_content = 'Arial'
73 @font_for_content = 'Arial'
74 @font_for_footer = 'Helvetica'
74 @font_for_footer = 'Helvetica'
75 end
75 end
76 end
76 end
77 SetCreator(Redmine::Info.app_name)
77 SetCreator(Redmine::Info.app_name)
78 SetFont(@font_for_content)
78 SetFont(@font_for_content)
79 end
79 end
80
80
81 def SetFontStyle(style, size)
81 def SetFontStyle(style, size)
82 SetFont(@font_for_content, style, size)
82 SetFont(@font_for_content, style, size)
83 end
83 end
84
84
85 def SetTitle(txt)
85 def SetTitle(txt)
86 txt = begin
86 txt = begin
87 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
87 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
88 hextxt = "<FEFF" # FEFF is BOM
88 hextxt = "<FEFF" # FEFF is BOM
89 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
89 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
90 hextxt << ">"
90 hextxt << ">"
91 rescue
91 rescue
92 txt
92 txt
93 end || ''
93 end || ''
94 super(txt)
94 super(txt)
95 end
95 end
96
96
97 def textstring(s)
97 def textstring(s)
98 # Format a text string
98 # Format a text string
99 if s =~ /^</ # This means the string is hex-dumped.
99 if s =~ /^</ # This means the string is hex-dumped.
100 return s
100 return s
101 else
101 else
102 return '('+escape(s)+')'
102 return '('+escape(s)+')'
103 end
103 end
104 end
104 end
105
105
106 def fix_text_encoding(txt)
106 def fix_text_encoding(txt)
107 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
107 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
108 end
108 end
109
109
110 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
110 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
111 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
111 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
112 end
112 end
113
113
114 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
114 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
115 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
115 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
116 end
116 end
117
117
118 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
118 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
119 @attachments = attachments
119 writeHTMLCell(w, h, x, y,
120 writeHTMLCell(w, h, x, y,
120 fix_text_encoding(
121 fix_text_encoding(
121 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
122 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
122 border, ln, fill)
123 border, ln, fill)
123 end
124 end
124
125
126 def getImageFilename(attrname)
127 # attrname: general_pdf_encoding string file/uri name
128 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
129 if atta
130 return atta.diskfile
131 else
132 return nil
133 end
134 end
135
125 def Footer
136 def Footer
126 SetFont(@font_for_footer, 'I', 8)
137 SetFont(@font_for_footer, 'I', 8)
127 SetY(-15)
138 SetY(-15)
128 SetX(15)
139 SetX(15)
129 RDMCell(0, 5, @footer_date, 0, 0, 'L')
140 RDMCell(0, 5, @footer_date, 0, 0, 'L')
130 SetY(-15)
141 SetY(-15)
131 SetX(-30)
142 SetX(-30)
132 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
143 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
133 end
144 end
134 end
145 end
135
146
136 # Returns a PDF string of a list of issues
147 # Returns a PDF string of a list of issues
137 def issues_to_pdf(issues, project, query)
148 def issues_to_pdf(issues, project, query)
138 pdf = ITCPDF.new(current_language)
149 pdf = ITCPDF.new(current_language)
139 title = query.new_record? ? l(:label_issue_plural) : query.name
150 title = query.new_record? ? l(:label_issue_plural) : query.name
140 title = "#{project} - #{title}" if project
151 title = "#{project} - #{title}" if project
141 pdf.SetTitle(title)
152 pdf.SetTitle(title)
142 pdf.alias_nb_pages
153 pdf.alias_nb_pages
143 pdf.footer_date = format_date(Date.today)
154 pdf.footer_date = format_date(Date.today)
144 pdf.SetAutoPageBreak(false)
155 pdf.SetAutoPageBreak(false)
145 pdf.AddPage("L")
156 pdf.AddPage("L")
146
157
147 # Landscape A4 = 210 x 297 mm
158 # Landscape A4 = 210 x 297 mm
148 page_height = 210
159 page_height = 210
149 page_width = 297
160 page_width = 297
150 right_margin = 10
161 right_margin = 10
151 bottom_margin = 20
162 bottom_margin = 20
152 col_id_width = 10
163 col_id_width = 10
153 row_height = 5
164 row_height = 5
154
165
155 # column widths
166 # column widths
156 table_width = page_width - right_margin - 10 # fixed left margin
167 table_width = page_width - right_margin - 10 # fixed left margin
157 col_width = []
168 col_width = []
158 unless query.columns.empty?
169 unless query.columns.empty?
159 col_width = query.columns.collect do |c|
170 col_width = query.columns.collect do |c|
160 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) &&
171 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) &&
161 ['string', 'text'].include?(c.custom_field.field_format))) ? 4.0 : 1.0
172 ['string', 'text'].include?(c.custom_field.field_format))) ? 4.0 : 1.0
162 end
173 end
163 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
174 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
164 col_width = col_width.collect {|w| w * ratio}
175 col_width = col_width.collect {|w| w * ratio}
165 end
176 end
166
177
167 # title
178 # title
168 pdf.SetFontStyle('B',11)
179 pdf.SetFontStyle('B',11)
169 pdf.RDMCell(190,10, title)
180 pdf.RDMCell(190,10, title)
170 pdf.Ln
181 pdf.Ln
171
182
172 # headers
183 # headers
173 pdf.SetFontStyle('B',8)
184 pdf.SetFontStyle('B',8)
174 pdf.SetFillColor(230, 230, 230)
185 pdf.SetFillColor(230, 230, 230)
175
186
176 # render it background to find the max height used
187 # render it background to find the max height used
177 base_x = pdf.GetX
188 base_x = pdf.GetX
178 base_y = pdf.GetY
189 base_y = pdf.GetY
179 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
190 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
180 pdf.Rect(base_x, base_y, table_width, max_height, 'FD');
191 pdf.Rect(base_x, base_y, table_width, max_height, 'FD');
181 pdf.SetXY(base_x, base_y);
192 pdf.SetXY(base_x, base_y);
182
193
183 # write the cells on page
194 # write the cells on page
184 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
195 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
185 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
196 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
186 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
197 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
187 pdf.SetY(base_y + max_height);
198 pdf.SetY(base_y + max_height);
188
199
189 # rows
200 # rows
190 pdf.SetFontStyle('',8)
201 pdf.SetFontStyle('',8)
191 pdf.SetFillColor(255, 255, 255)
202 pdf.SetFillColor(255, 255, 255)
192 previous_group = false
203 previous_group = false
193 issue_list(issues) do |issue, level|
204 issue_list(issues) do |issue, level|
194 if query.grouped? &&
205 if query.grouped? &&
195 (group = query.group_by_column.value(issue)) != previous_group
206 (group = query.group_by_column.value(issue)) != previous_group
196 pdf.SetFontStyle('B',9)
207 pdf.SetFontStyle('B',9)
197 pdf.RDMCell(277, row_height,
208 pdf.RDMCell(277, row_height,
198 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
209 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
199 1, 1, 'L')
210 1, 1, 'L')
200 pdf.SetFontStyle('',8)
211 pdf.SetFontStyle('',8)
201 previous_group = group
212 previous_group = group
202 end
213 end
203 # fetch all the row values
214 # fetch all the row values
204 col_values = query.columns.collect do |column|
215 col_values = query.columns.collect do |column|
205 s = if column.is_a?(QueryCustomFieldColumn)
216 s = if column.is_a?(QueryCustomFieldColumn)
206 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
217 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
207 show_value(cv)
218 show_value(cv)
208 else
219 else
209 value = issue.send(column.name)
220 value = issue.send(column.name)
210 if column.name == :subject
221 if column.name == :subject
211 value = " " * level + value
222 value = " " * level + value
212 end
223 end
213 if value.is_a?(Date)
224 if value.is_a?(Date)
214 format_date(value)
225 format_date(value)
215 elsif value.is_a?(Time)
226 elsif value.is_a?(Time)
216 format_time(value)
227 format_time(value)
217 else
228 else
218 value
229 value
219 end
230 end
220 end
231 end
221 s.to_s
232 s.to_s
222 end
233 end
223
234
224 # render it off-page to find the max height used
235 # render it off-page to find the max height used
225 base_x = pdf.GetX
236 base_x = pdf.GetX
226 base_y = pdf.GetY
237 base_y = pdf.GetY
227 pdf.SetY(2 * page_height)
238 pdf.SetY(2 * page_height)
228 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
239 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
229 pdf.SetXY(base_x, base_y)
240 pdf.SetXY(base_x, base_y)
230
241
231 # make new page if it doesn't fit on the current one
242 # make new page if it doesn't fit on the current one
232 space_left = page_height - base_y - bottom_margin
243 space_left = page_height - base_y - bottom_margin
233 if max_height > space_left
244 if max_height > space_left
234 pdf.AddPage("L")
245 pdf.AddPage("L")
235 base_x = pdf.GetX
246 base_x = pdf.GetX
236 base_y = pdf.GetY
247 base_y = pdf.GetY
237 end
248 end
238
249
239 # write the cells on page
250 # write the cells on page
240 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
251 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
241 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
252 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
242 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
253 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
243 pdf.SetY(base_y + max_height);
254 pdf.SetY(base_y + max_height);
244 end
255 end
245
256
246 if issues.size == Setting.issues_export_limit.to_i
257 if issues.size == Setting.issues_export_limit.to_i
247 pdf.SetFontStyle('B',10)
258 pdf.SetFontStyle('B',10)
248 pdf.RDMCell(0, row_height, '...')
259 pdf.RDMCell(0, row_height, '...')
249 end
260 end
250 pdf.Output
261 pdf.Output
251 end
262 end
252
263
253 # Renders MultiCells and returns the maximum height used
264 # Renders MultiCells and returns the maximum height used
254 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
265 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
255 row_height, head=false)
266 row_height, head=false)
256 base_y = pdf.GetY
267 base_y = pdf.GetY
257 max_height = row_height
268 max_height = row_height
258 col_values.each_with_index do |column, i|
269 col_values.each_with_index do |column, i|
259 col_x = pdf.GetX
270 col_x = pdf.GetX
260 if head == true
271 if head == true
261 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
272 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
262 else
273 else
263 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
274 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
264 end
275 end
265 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
276 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
266 pdf.SetXY(col_x + col_widths[i], base_y);
277 pdf.SetXY(col_x + col_widths[i], base_y);
267 end
278 end
268 return max_height
279 return max_height
269 end
280 end
270
281
271 # Draw lines to close the row (MultiCell border drawing in not uniform)
282 # Draw lines to close the row (MultiCell border drawing in not uniform)
272 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
283 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
273 id_width, col_widths)
284 id_width, col_widths)
274 col_x = top_x + id_width
285 col_x = top_x + id_width
275 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
286 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
276 col_widths.each do |width|
287 col_widths.each do |width|
277 col_x += width
288 col_x += width
278 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
289 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
279 end
290 end
280 pdf.Line(top_x, top_y, top_x, lower_y) # left border
291 pdf.Line(top_x, top_y, top_x, lower_y) # left border
281 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
292 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
282 end
293 end
283
294
284 # Returns a PDF string of a single issue
295 # Returns a PDF string of a single issue
285 def issue_to_pdf(issue)
296 def issue_to_pdf(issue)
286 pdf = ITCPDF.new(current_language)
297 pdf = ITCPDF.new(current_language)
287 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
298 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
288 pdf.alias_nb_pages
299 pdf.alias_nb_pages
289 pdf.footer_date = format_date(Date.today)
300 pdf.footer_date = format_date(Date.today)
290 pdf.AddPage
301 pdf.AddPage
291 pdf.SetFontStyle('B',11)
302 pdf.SetFontStyle('B',11)
292 buf = "#{issue.project} - #{issue.tracker} # #{issue.id}"
303 buf = "#{issue.project} - #{issue.tracker} # #{issue.id}"
293 pdf.RDMMultiCell(190, 5, buf)
304 pdf.RDMMultiCell(190, 5, buf)
294 pdf.Ln
305 pdf.Ln
295 pdf.SetFontStyle('',8)
306 pdf.SetFontStyle('',8)
296 base_x = pdf.GetX
307 base_x = pdf.GetX
297 i = 1
308 i = 1
298 issue.ancestors.each do |ancestor|
309 issue.ancestors.each do |ancestor|
299 pdf.SetX(base_x + i)
310 pdf.SetX(base_x + i)
300 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
311 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
301 pdf.RDMMultiCell(190 - i, 5, buf)
312 pdf.RDMMultiCell(190 - i, 5, buf)
302 i += 1 if i < 35
313 i += 1 if i < 35
303 end
314 end
304 pdf.Ln
315 pdf.Ln
305
316
306 pdf.SetFontStyle('B',9)
317 pdf.SetFontStyle('B',9)
307 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
318 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
308 pdf.SetFontStyle('',9)
319 pdf.SetFontStyle('',9)
309 pdf.RDMCell(60,5, issue.status.to_s,"RT")
320 pdf.RDMCell(60,5, issue.status.to_s,"RT")
310 pdf.SetFontStyle('B',9)
321 pdf.SetFontStyle('B',9)
311 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
322 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
312 pdf.SetFontStyle('',9)
323 pdf.SetFontStyle('',9)
313 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
324 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
314 pdf.Ln
325 pdf.Ln
315
326
316 pdf.SetFontStyle('B',9)
327 pdf.SetFontStyle('B',9)
317 pdf.RDMCell(35,5, l(:field_author) + ":","L")
328 pdf.RDMCell(35,5, l(:field_author) + ":","L")
318 pdf.SetFontStyle('',9)
329 pdf.SetFontStyle('',9)
319 pdf.RDMCell(60,5, issue.author.to_s,"R")
330 pdf.RDMCell(60,5, issue.author.to_s,"R")
320 pdf.SetFontStyle('B',9)
331 pdf.SetFontStyle('B',9)
321 pdf.RDMCell(35,5, l(:field_category) + ":","L")
332 pdf.RDMCell(35,5, l(:field_category) + ":","L")
322 pdf.SetFontStyle('',9)
333 pdf.SetFontStyle('',9)
323 pdf.RDMCell(60,5, issue.category.to_s,"R")
334 pdf.RDMCell(60,5, issue.category.to_s,"R")
324 pdf.Ln
335 pdf.Ln
325
336
326 pdf.SetFontStyle('B',9)
337 pdf.SetFontStyle('B',9)
327 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
338 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
328 pdf.SetFontStyle('',9)
339 pdf.SetFontStyle('',9)
329 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
340 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
330 pdf.SetFontStyle('B',9)
341 pdf.SetFontStyle('B',9)
331 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
342 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
332 pdf.SetFontStyle('',9)
343 pdf.SetFontStyle('',9)
333 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
344 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
334 pdf.Ln
345 pdf.Ln
335
346
336 pdf.SetFontStyle('B',9)
347 pdf.SetFontStyle('B',9)
337 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
348 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
338 pdf.SetFontStyle('',9)
349 pdf.SetFontStyle('',9)
339 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
350 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
340 pdf.SetFontStyle('B',9)
351 pdf.SetFontStyle('B',9)
341 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
352 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
342 pdf.SetFontStyle('',9)
353 pdf.SetFontStyle('',9)
343 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
354 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
344 pdf.Ln
355 pdf.Ln
345
356
346 for custom_value in issue.custom_field_values
357 for custom_value in issue.custom_field_values
347 pdf.SetFontStyle('B',9)
358 pdf.SetFontStyle('B',9)
348 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
359 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
349 pdf.SetFontStyle('',9)
360 pdf.SetFontStyle('',9)
350 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
361 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
351 end
362 end
352
363
353 y0 = pdf.GetY
364 y0 = pdf.GetY
354
365
355 pdf.SetFontStyle('B',9)
366 pdf.SetFontStyle('B',9)
356 pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
367 pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
357 pdf.SetFontStyle('',9)
368 pdf.SetFontStyle('',9)
358 pdf.RDMMultiCell(155,5, issue.subject,"RT")
369 pdf.RDMMultiCell(155,5, issue.subject,"RT")
359 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
370 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
360
371
361 pdf.SetFontStyle('B',9)
372 pdf.SetFontStyle('B',9)
362 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
373 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
363 pdf.SetFontStyle('',9)
374 pdf.SetFontStyle('',9)
364
375
365 # Set resize image scale
376 # Set resize image scale
366 pdf.SetImageScale(1.6)
377 pdf.SetImageScale(1.6)
367 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
378 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
368 issue.description.to_s, issue.attachments, "LRB")
379 issue.description.to_s, issue.attachments, "LRB")
369
380
370 unless issue.leaf?
381 unless issue.leaf?
371 # for CJK
382 # for CJK
372 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
383 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
373
384
374 pdf.SetFontStyle('B',9)
385 pdf.SetFontStyle('B',9)
375 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
386 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
376 pdf.Ln
387 pdf.Ln
377 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
388 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
378 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
389 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
379 :length => truncate_length)
390 :length => truncate_length)
380 level = 10 if level >= 10
391 level = 10 if level >= 10
381 pdf.SetFontStyle('',8)
392 pdf.SetFontStyle('',8)
382 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
393 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
383 pdf.SetFontStyle('B',8)
394 pdf.SetFontStyle('B',8)
384 pdf.RDMCell(20,5, child.status.to_s, "R")
395 pdf.RDMCell(20,5, child.status.to_s, "R")
385 pdf.Ln
396 pdf.Ln
386 end
397 end
387 end
398 end
388
399
389 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
400 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
390 unless relations.empty?
401 unless relations.empty?
391 # for CJK
402 # for CJK
392 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
403 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
393
404
394 pdf.SetFontStyle('B',9)
405 pdf.SetFontStyle('B',9)
395 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
406 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
396 pdf.Ln
407 pdf.Ln
397 relations.each do |relation|
408 relations.each do |relation|
398 buf = ""
409 buf = ""
399 buf += "#{l(relation.label_for(issue))} "
410 buf += "#{l(relation.label_for(issue))} "
400 if relation.delay && relation.delay != 0
411 if relation.delay && relation.delay != 0
401 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
412 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
402 end
413 end
403 if Setting.cross_project_issue_relations?
414 if Setting.cross_project_issue_relations?
404 buf += "#{relation.other_issue(issue).project} - "
415 buf += "#{relation.other_issue(issue).project} - "
405 end
416 end
406 buf += "#{relation.other_issue(issue).tracker}" +
417 buf += "#{relation.other_issue(issue).tracker}" +
407 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
418 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
408 buf = truncate(buf, :length => truncate_length)
419 buf = truncate(buf, :length => truncate_length)
409 pdf.SetFontStyle('', 8)
420 pdf.SetFontStyle('', 8)
410 pdf.RDMCell(35+155-50,5, buf, "L")
421 pdf.RDMCell(35+155-50,5, buf, "L")
411 pdf.SetFontStyle('B',8)
422 pdf.SetFontStyle('B',8)
412 pdf.RDMCell(10,5, relation.other_issue(issue).status.to_s, "")
423 pdf.RDMCell(10,5, relation.other_issue(issue).status.to_s, "")
413 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
424 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
414 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
425 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
415 pdf.Ln
426 pdf.Ln
416 end
427 end
417 end
428 end
418 pdf.RDMCell(190,5, "", "T")
429 pdf.RDMCell(190,5, "", "T")
419 pdf.Ln
430 pdf.Ln
420
431
421 if issue.changesets.any? &&
432 if issue.changesets.any? &&
422 User.current.allowed_to?(:view_changesets, issue.project)
433 User.current.allowed_to?(:view_changesets, issue.project)
423 pdf.SetFontStyle('B',9)
434 pdf.SetFontStyle('B',9)
424 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
435 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
425 pdf.Ln
436 pdf.Ln
426 for changeset in issue.changesets
437 for changeset in issue.changesets
427 pdf.SetFontStyle('B',8)
438 pdf.SetFontStyle('B',8)
428 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
439 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
429 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
440 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
430 pdf.RDMCell(190, 5, csstr)
441 pdf.RDMCell(190, 5, csstr)
431 pdf.Ln
442 pdf.Ln
432 unless changeset.comments.blank?
443 unless changeset.comments.blank?
433 pdf.SetFontStyle('',8)
444 pdf.SetFontStyle('',8)
434 pdf.RDMwriteHTMLCell(190,5,0,0,
445 pdf.RDMwriteHTMLCell(190,5,0,0,
435 changeset.comments.to_s, issue.attachments, "")
446 changeset.comments.to_s, issue.attachments, "")
436 end
447 end
437 pdf.Ln
448 pdf.Ln
438 end
449 end
439 end
450 end
440
451
441 pdf.SetFontStyle('B',9)
452 pdf.SetFontStyle('B',9)
442 pdf.RDMCell(190,5, l(:label_history), "B")
453 pdf.RDMCell(190,5, l(:label_history), "B")
443 pdf.Ln
454 pdf.Ln
444 for journal in issue.journals.find(
455 for journal in issue.journals.find(
445 :all, :include => [:user, :details],
456 :all, :include => [:user, :details],
446 :order => "#{Journal.table_name}.created_on ASC")
457 :order => "#{Journal.table_name}.created_on ASC")
447 pdf.SetFontStyle('B',8)
458 pdf.SetFontStyle('B',8)
448 pdf.RDMCell(190,5,
459 pdf.RDMCell(190,5,
449 format_time(journal.created_on) + " - " + journal.user.name)
460 format_time(journal.created_on) + " - " + journal.user.name)
450 pdf.Ln
461 pdf.Ln
451 pdf.SetFontStyle('I',8)
462 pdf.SetFontStyle('I',8)
452 for detail in journal.details
463 for detail in journal.details
453 pdf.RDMMultiCell(190,5, "- " + show_detail(detail, true))
464 pdf.RDMMultiCell(190,5, "- " + show_detail(detail, true))
454 end
465 end
455 if journal.notes?
466 if journal.notes?
456 pdf.Ln unless journal.details.empty?
467 pdf.Ln unless journal.details.empty?
457 pdf.SetFontStyle('',8)
468 pdf.SetFontStyle('',8)
458 pdf.RDMwriteHTMLCell(190,5,0,0,
469 pdf.RDMwriteHTMLCell(190,5,0,0,
459 journal.notes.to_s, issue.attachments, "")
470 journal.notes.to_s, issue.attachments, "")
460 end
471 end
461 pdf.Ln
472 pdf.Ln
462 end
473 end
463
474
464 if issue.attachments.any?
475 if issue.attachments.any?
465 pdf.SetFontStyle('B',9)
476 pdf.SetFontStyle('B',9)
466 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
477 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
467 pdf.Ln
478 pdf.Ln
468 for attachment in issue.attachments
479 for attachment in issue.attachments
469 pdf.SetFontStyle('',8)
480 pdf.SetFontStyle('',8)
470 pdf.RDMCell(80,5, attachment.filename)
481 pdf.RDMCell(80,5, attachment.filename)
471 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
482 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
472 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
483 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
473 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
484 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
474 pdf.Ln
485 pdf.Ln
475 end
486 end
476 end
487 end
477 pdf.Output
488 pdf.Output
478 end
489 end
479
490
480 # Returns a PDF string of a single wiki page
491 # Returns a PDF string of a single wiki page
481 def wiki_to_pdf(page, project)
492 def wiki_to_pdf(page, project)
482 pdf = ITCPDF.new(current_language)
493 pdf = ITCPDF.new(current_language)
483 pdf.SetTitle("#{project} - #{page.title}")
494 pdf.SetTitle("#{project} - #{page.title}")
484 pdf.alias_nb_pages
495 pdf.alias_nb_pages
485 pdf.footer_date = format_date(Date.today)
496 pdf.footer_date = format_date(Date.today)
486 pdf.AddPage
497 pdf.AddPage
487 pdf.SetFontStyle('B',11)
498 pdf.SetFontStyle('B',11)
488 pdf.RDMMultiCell(190,5,
499 pdf.RDMMultiCell(190,5,
489 "#{project} - #{page.title} - # #{page.content.version}")
500 "#{project} - #{page.title} - # #{page.content.version}")
490 pdf.Ln
501 pdf.Ln
491 # Set resize image scale
502 # Set resize image scale
492 pdf.SetImageScale(1.6)
503 pdf.SetImageScale(1.6)
493 pdf.SetFontStyle('',9)
504 pdf.SetFontStyle('',9)
494 pdf.RDMwriteHTMLCell(190,5,0,0,
505 pdf.RDMwriteHTMLCell(190,5,0,0,
495 page.content.text.to_s, page.attachments, "TLRB")
506 page.content.text.to_s, page.attachments, "TLRB")
496 if page.attachments.any?
507 if page.attachments.any?
497 pdf.Ln
508 pdf.Ln
498 pdf.SetFontStyle('B',9)
509 pdf.SetFontStyle('B',9)
499 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
510 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
500 pdf.Ln
511 pdf.Ln
501 for attachment in page.attachments
512 for attachment in page.attachments
502 pdf.SetFontStyle('',8)
513 pdf.SetFontStyle('',8)
503 pdf.RDMCell(80,5, attachment.filename)
514 pdf.RDMCell(80,5, attachment.filename)
504 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
515 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
505 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
516 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
506 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
517 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
507 pdf.Ln
518 pdf.Ln
508 end
519 end
509 end
520 end
510 pdf.Output
521 pdf.Output
511 end
522 end
512
523
513 class RDMPdfEncoding
524 class RDMPdfEncoding
514 def self.rdm_from_utf8(txt, encoding)
525 def self.rdm_from_utf8(txt, encoding)
515 txt ||= ''
526 txt ||= ''
516 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
527 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
517 if txt.respond_to?(:force_encoding)
528 if txt.respond_to?(:force_encoding)
518 txt.force_encoding('ASCII-8BIT')
529 txt.force_encoding('ASCII-8BIT')
519 end
530 end
520 txt
531 txt
521 end
532 end
522
533
523 def self.attach(attachments, filename, encoding)
534 def self.attach(attachments, filename, encoding)
524 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
535 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
525 atta = nil
536 atta = nil
526 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
537 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
527 atta = Attachment.latest_attach(attachments, filename_utf8)
538 atta = Attachment.latest_attach(attachments, filename_utf8)
528 end
539 end
529 if atta && atta.readable? && atta.visible?
540 if atta && atta.readable? && atta.visible?
530 return atta
541 return atta
531 else
542 else
532 return nil
543 return nil
533 end
544 end
534 end
545 end
535 end
546 end
536 end
547 end
537 end
548 end
538 end
549 end
General Comments 0
You need to be logged in to leave comments. Login now