##// END OF EJS Templates
Adds bookmarks to PDF when exporting a grouped issue list....
Jean-Philippe Lang -
r8612:a749e788abd9
parent child
Show More
@@ -1,553 +1,634
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 @outlines = []
80 @outlineRoot = nil
79 end
81 end
80
82
81 def SetFontStyle(style, size)
83 def SetFontStyle(style, size)
82 SetFont(@font_for_content, style, size)
84 SetFont(@font_for_content, style, size)
83 end
85 end
84
86
85 def SetTitle(txt)
87 def SetTitle(txt)
86 txt = begin
88 txt = begin
87 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
89 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
88 hextxt = "<FEFF" # FEFF is BOM
90 hextxt = "<FEFF" # FEFF is BOM
89 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
91 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
90 hextxt << ">"
92 hextxt << ">"
91 rescue
93 rescue
92 txt
94 txt
93 end || ''
95 end || ''
94 super(txt)
96 super(txt)
95 end
97 end
96
98
97 def textstring(s)
99 def textstring(s)
98 # Format a text string
100 # Format a text string
99 if s =~ /^</ # This means the string is hex-dumped.
101 if s =~ /^</ # This means the string is hex-dumped.
100 return s
102 return s
101 else
103 else
102 return '('+escape(s)+')'
104 return '('+escape(s)+')'
103 end
105 end
104 end
106 end
105
107
106 def fix_text_encoding(txt)
108 def fix_text_encoding(txt)
107 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
109 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
108 end
110 end
109
111
110 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
112 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)
113 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
112 end
114 end
113
115
114 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
116 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)
117 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
116 end
118 end
117
119
118 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
120 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
119 @attachments = attachments
121 @attachments = attachments
120 writeHTMLCell(w, h, x, y,
122 writeHTMLCell(w, h, x, y,
121 fix_text_encoding(
123 fix_text_encoding(
122 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
124 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
123 border, ln, fill)
125 border, ln, fill)
124 end
126 end
125
127
126 def getImageFilename(attrname)
128 def getImageFilename(attrname)
127 # attrname: general_pdf_encoding string file/uri name
129 # attrname: general_pdf_encoding string file/uri name
128 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
130 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
129 if atta
131 if atta
130 return atta.diskfile
132 return atta.diskfile
131 else
133 else
132 return nil
134 return nil
133 end
135 end
134 end
136 end
135
137
136 def Footer
138 def Footer
137 SetFont(@font_for_footer, 'I', 8)
139 SetFont(@font_for_footer, 'I', 8)
138 SetY(-15)
140 SetY(-15)
139 SetX(15)
141 SetX(15)
140 RDMCell(0, 5, @footer_date, 0, 0, 'L')
142 RDMCell(0, 5, @footer_date, 0, 0, 'L')
141 SetY(-15)
143 SetY(-15)
142 SetX(-30)
144 SetX(-30)
143 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
145 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
144 end
146 end
147
148 def Bookmark(txt, level=0, y=0)
149 utf16 = Iconv.conv('UTF-16', 'UTF-8', txt)
150 if (y == -1)
151 y = GetY()
152 end
153 @outlines << {:t => utf16, :l => level, :p => PageNo(), :y => (@h - y)*@k}
154 end
155
156 def putbookmarks
157 nb=@outlines.size
158 return if (nb==0)
159 lru=[]
160 level=0
161 @outlines.each_with_index do |o, i|
162 if(o[:l]>0)
163 parent=lru[o[:l]-1]
164 #Set parent and last pointers
165 @outlines[i][:parent]=parent
166 @outlines[parent][:last]=i
167 if (o[:l]>level)
168 #Level increasing: set first pointer
169 @outlines[parent][:first]=i
170 end
171 else
172 @outlines[i][:parent]=nb
173 end
174 if (o[:l]<=level && i>0)
175 #Set prev and next pointers
176 prev=lru[o[:l]]
177 @outlines[prev][:next]=i
178 @outlines[i][:prev]=prev
179 end
180 lru[o[:l]]=i
181 level=o[:l]
182 end
183 #Outline items
184 n=self.n+1
185 @outlines.each_with_index do |o, i|
186 newobj()
187 out('<</Title '+textstring(o[:t]))
188 out("/Parent #{n+o[:parent]} 0 R")
189 if (o[:prev])
190 out("/Prev #{n+o[:prev]} 0 R")
191 end
192 if (o[:next])
193 out("/Next #{n+o[:next]} 0 R")
194 end
195 if (o[:first])
196 out("/First #{n+o[:first]} 0 R")
197 end
198 if (o[:last])
199 out("/Last #{n+o[:last]} 0 R")
200 end
201 out("/Dest [%d 0 R /XYZ 0 %.2f null]" % [1+2*o[:p], o[:y]])
202 out('/Count 0>>')
203 out('endobj')
204 end
205 #Outline root
206 newobj()
207 @outlineRoot=self.n
208 out("<</Type /Outlines /First #{n} 0 R");
209 out("/Last #{n+lru[0]} 0 R>>");
210 out('endobj');
211 end
212
213 def putresources()
214 super
215 putbookmarks()
216 end
217
218 def putcatalog()
219 super
220 if(@outlines.size > 0)
221 out("/Outlines #{@outlineRoot} 0 R");
222 out('/PageMode /UseOutlines');
223 end
224 end
145 end
225 end
146
226
147 # Returns a PDF string of a list of issues
227 # Returns a PDF string of a list of issues
148 def issues_to_pdf(issues, project, query)
228 def issues_to_pdf(issues, project, query)
149 pdf = ITCPDF.new(current_language)
229 pdf = ITCPDF.new(current_language)
150 title = query.new_record? ? l(:label_issue_plural) : query.name
230 title = query.new_record? ? l(:label_issue_plural) : query.name
151 title = "#{project} - #{title}" if project
231 title = "#{project} - #{title}" if project
152 pdf.SetTitle(title)
232 pdf.SetTitle(title)
153 pdf.alias_nb_pages
233 pdf.alias_nb_pages
154 pdf.footer_date = format_date(Date.today)
234 pdf.footer_date = format_date(Date.today)
155 pdf.SetAutoPageBreak(false)
235 pdf.SetAutoPageBreak(false)
156 pdf.AddPage("L")
236 pdf.AddPage("L")
157
237
158 # Landscape A4 = 210 x 297 mm
238 # Landscape A4 = 210 x 297 mm
159 page_height = 210
239 page_height = 210
160 page_width = 297
240 page_width = 297
161 right_margin = 10
241 right_margin = 10
162 bottom_margin = 20
242 bottom_margin = 20
163 col_id_width = 10
243 col_id_width = 10
164 row_height = 5
244 row_height = 5
165
245
166 # column widths
246 # column widths
167 table_width = page_width - right_margin - 10 # fixed left margin
247 table_width = page_width - right_margin - 10 # fixed left margin
168 col_width = []
248 col_width = []
169 unless query.columns.empty?
249 unless query.columns.empty?
170 col_width = query.columns.collect do |c|
250 col_width = query.columns.collect do |c|
171 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) &&
251 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) &&
172 ['string', 'text'].include?(c.custom_field.field_format))) ? 4.0 : 1.0
252 ['string', 'text'].include?(c.custom_field.field_format))) ? 4.0 : 1.0
173 end
253 end
174 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
254 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
175 col_width = col_width.collect {|w| w * ratio}
255 col_width = col_width.collect {|w| w * ratio}
176 end
256 end
177
257
178 # title
258 # title
179 pdf.SetFontStyle('B',11)
259 pdf.SetFontStyle('B',11)
180 pdf.RDMCell(190,10, title)
260 pdf.RDMCell(190,10, title)
181 pdf.Ln
261 pdf.Ln
182
262
183 # headers
263 # headers
184 pdf.SetFontStyle('B',8)
264 pdf.SetFontStyle('B',8)
185 pdf.SetFillColor(230, 230, 230)
265 pdf.SetFillColor(230, 230, 230)
186
266
187 # render it background to find the max height used
267 # render it background to find the max height used
188 base_x = pdf.GetX
268 base_x = pdf.GetX
189 base_y = pdf.GetY
269 base_y = pdf.GetY
190 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
270 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
191 pdf.Rect(base_x, base_y, table_width, max_height, 'FD');
271 pdf.Rect(base_x, base_y, table_width, max_height, 'FD');
192 pdf.SetXY(base_x, base_y);
272 pdf.SetXY(base_x, base_y);
193
273
194 # write the cells on page
274 # write the cells on page
195 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
275 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
196 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
276 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
197 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
277 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
198 pdf.SetY(base_y + max_height);
278 pdf.SetY(base_y + max_height);
199
279
200 # rows
280 # rows
201 pdf.SetFontStyle('',8)
281 pdf.SetFontStyle('',8)
202 pdf.SetFillColor(255, 255, 255)
282 pdf.SetFillColor(255, 255, 255)
203 previous_group = false
283 previous_group = false
204 issue_list(issues) do |issue, level|
284 issue_list(issues) do |issue, level|
205 if query.grouped? &&
285 if query.grouped? &&
206 (group = query.group_by_column.value(issue)) != previous_group
286 (group = query.group_by_column.value(issue)) != previous_group
207 pdf.SetFontStyle('B',9)
287 pdf.SetFontStyle('B',9)
208 pdf.RDMCell(277, row_height,
288 group_label = group.blank? ? 'None' : group.to_s
209 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
289 group_label << " (#{query.issue_count_by_group[group]})"
210 1, 1, 'L')
290 pdf.Bookmark group_label, 0, -1
291 pdf.RDMCell(277, row_height, group_label, 1, 1, 'L')
211 pdf.SetFontStyle('',8)
292 pdf.SetFontStyle('',8)
212 previous_group = group
293 previous_group = group
213 end
294 end
214 # fetch all the row values
295 # fetch all the row values
215 col_values = query.columns.collect do |column|
296 col_values = query.columns.collect do |column|
216 s = if column.is_a?(QueryCustomFieldColumn)
297 s = if column.is_a?(QueryCustomFieldColumn)
217 cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
298 cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
218 show_value(cv)
299 show_value(cv)
219 else
300 else
220 value = issue.send(column.name)
301 value = issue.send(column.name)
221 if column.name == :subject
302 if column.name == :subject
222 value = " " * level + value
303 value = " " * level + value
223 end
304 end
224 if value.is_a?(Date)
305 if value.is_a?(Date)
225 format_date(value)
306 format_date(value)
226 elsif value.is_a?(Time)
307 elsif value.is_a?(Time)
227 format_time(value)
308 format_time(value)
228 else
309 else
229 value
310 value
230 end
311 end
231 end
312 end
232 s.to_s
313 s.to_s
233 end
314 end
234
315
235 # render it off-page to find the max height used
316 # render it off-page to find the max height used
236 base_x = pdf.GetX
317 base_x = pdf.GetX
237 base_y = pdf.GetY
318 base_y = pdf.GetY
238 pdf.SetY(2 * page_height)
319 pdf.SetY(2 * page_height)
239 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
320 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
240 pdf.SetXY(base_x, base_y)
321 pdf.SetXY(base_x, base_y)
241
322
242 # make new page if it doesn't fit on the current one
323 # make new page if it doesn't fit on the current one
243 space_left = page_height - base_y - bottom_margin
324 space_left = page_height - base_y - bottom_margin
244 if max_height > space_left
325 if max_height > space_left
245 pdf.AddPage("L")
326 pdf.AddPage("L")
246 base_x = pdf.GetX
327 base_x = pdf.GetX
247 base_y = pdf.GetY
328 base_y = pdf.GetY
248 end
329 end
249
330
250 # write the cells on page
331 # write the cells on page
251 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
332 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
252 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
333 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
253 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
334 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
254 pdf.SetY(base_y + max_height);
335 pdf.SetY(base_y + max_height);
255 end
336 end
256
337
257 if issues.size == Setting.issues_export_limit.to_i
338 if issues.size == Setting.issues_export_limit.to_i
258 pdf.SetFontStyle('B',10)
339 pdf.SetFontStyle('B',10)
259 pdf.RDMCell(0, row_height, '...')
340 pdf.RDMCell(0, row_height, '...')
260 end
341 end
261 pdf.Output
342 pdf.Output
262 end
343 end
263
344
264 # Renders MultiCells and returns the maximum height used
345 # Renders MultiCells and returns the maximum height used
265 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
346 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
266 row_height, head=false)
347 row_height, head=false)
267 base_y = pdf.GetY
348 base_y = pdf.GetY
268 max_height = row_height
349 max_height = row_height
269 col_values.each_with_index do |column, i|
350 col_values.each_with_index do |column, i|
270 col_x = pdf.GetX
351 col_x = pdf.GetX
271 if head == true
352 if head == true
272 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
353 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
273 else
354 else
274 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
355 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
275 end
356 end
276 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
357 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
277 pdf.SetXY(col_x + col_widths[i], base_y);
358 pdf.SetXY(col_x + col_widths[i], base_y);
278 end
359 end
279 return max_height
360 return max_height
280 end
361 end
281
362
282 # Draw lines to close the row (MultiCell border drawing in not uniform)
363 # Draw lines to close the row (MultiCell border drawing in not uniform)
283 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
364 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
284 id_width, col_widths)
365 id_width, col_widths)
285 col_x = top_x + id_width
366 col_x = top_x + id_width
286 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
367 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
287 col_widths.each do |width|
368 col_widths.each do |width|
288 col_x += width
369 col_x += width
289 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
370 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
290 end
371 end
291 pdf.Line(top_x, top_y, top_x, lower_y) # left border
372 pdf.Line(top_x, top_y, top_x, lower_y) # left border
292 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
373 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
293 end
374 end
294
375
295 # Returns a PDF string of a single issue
376 # Returns a PDF string of a single issue
296 def issue_to_pdf(issue)
377 def issue_to_pdf(issue)
297 pdf = ITCPDF.new(current_language)
378 pdf = ITCPDF.new(current_language)
298 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
379 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
299 pdf.alias_nb_pages
380 pdf.alias_nb_pages
300 pdf.footer_date = format_date(Date.today)
381 pdf.footer_date = format_date(Date.today)
301 pdf.AddPage
382 pdf.AddPage
302 pdf.SetFontStyle('B',11)
383 pdf.SetFontStyle('B',11)
303 buf = "#{issue.project} - #{issue.tracker} # #{issue.id}"
384 buf = "#{issue.project} - #{issue.tracker} # #{issue.id}"
304 pdf.RDMMultiCell(190, 5, buf)
385 pdf.RDMMultiCell(190, 5, buf)
305 pdf.Ln
386 pdf.Ln
306 pdf.SetFontStyle('',8)
387 pdf.SetFontStyle('',8)
307 base_x = pdf.GetX
388 base_x = pdf.GetX
308 i = 1
389 i = 1
309 issue.ancestors.each do |ancestor|
390 issue.ancestors.each do |ancestor|
310 pdf.SetX(base_x + i)
391 pdf.SetX(base_x + i)
311 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
392 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
312 pdf.RDMMultiCell(190 - i, 5, buf)
393 pdf.RDMMultiCell(190 - i, 5, buf)
313 i += 1 if i < 35
394 i += 1 if i < 35
314 end
395 end
315 pdf.Ln
396 pdf.Ln
316
397
317 pdf.SetFontStyle('B',9)
398 pdf.SetFontStyle('B',9)
318 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
399 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
319 pdf.SetFontStyle('',9)
400 pdf.SetFontStyle('',9)
320 pdf.RDMCell(60,5, issue.status.to_s,"RT")
401 pdf.RDMCell(60,5, issue.status.to_s,"RT")
321 pdf.SetFontStyle('B',9)
402 pdf.SetFontStyle('B',9)
322 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
403 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
323 pdf.SetFontStyle('',9)
404 pdf.SetFontStyle('',9)
324 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
405 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
325 pdf.Ln
406 pdf.Ln
326
407
327 pdf.SetFontStyle('B',9)
408 pdf.SetFontStyle('B',9)
328 pdf.RDMCell(35,5, l(:field_author) + ":","L")
409 pdf.RDMCell(35,5, l(:field_author) + ":","L")
329 pdf.SetFontStyle('',9)
410 pdf.SetFontStyle('',9)
330 pdf.RDMCell(60,5, issue.author.to_s,"R")
411 pdf.RDMCell(60,5, issue.author.to_s,"R")
331 pdf.SetFontStyle('B',9)
412 pdf.SetFontStyle('B',9)
332 pdf.RDMCell(35,5, l(:field_category) + ":","L")
413 pdf.RDMCell(35,5, l(:field_category) + ":","L")
333 pdf.SetFontStyle('',9)
414 pdf.SetFontStyle('',9)
334 pdf.RDMCell(60,5, issue.category.to_s,"R")
415 pdf.RDMCell(60,5, issue.category.to_s,"R")
335 pdf.Ln
416 pdf.Ln
336
417
337 pdf.SetFontStyle('B',9)
418 pdf.SetFontStyle('B',9)
338 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
419 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
339 pdf.SetFontStyle('',9)
420 pdf.SetFontStyle('',9)
340 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
421 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
341 pdf.SetFontStyle('B',9)
422 pdf.SetFontStyle('B',9)
342 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
423 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
343 pdf.SetFontStyle('',9)
424 pdf.SetFontStyle('',9)
344 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
425 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
345 pdf.Ln
426 pdf.Ln
346
427
347 pdf.SetFontStyle('B',9)
428 pdf.SetFontStyle('B',9)
348 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
429 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
349 pdf.SetFontStyle('',9)
430 pdf.SetFontStyle('',9)
350 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
431 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
351 pdf.SetFontStyle('B',9)
432 pdf.SetFontStyle('B',9)
352 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
433 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
353 pdf.SetFontStyle('',9)
434 pdf.SetFontStyle('',9)
354 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
435 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
355 pdf.Ln
436 pdf.Ln
356
437
357 for custom_value in issue.custom_field_values
438 for custom_value in issue.custom_field_values
358 pdf.SetFontStyle('B',9)
439 pdf.SetFontStyle('B',9)
359 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
440 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
360 pdf.SetFontStyle('',9)
441 pdf.SetFontStyle('',9)
361 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
442 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
362 end
443 end
363
444
364 y0 = pdf.GetY
445 y0 = pdf.GetY
365
446
366 pdf.SetFontStyle('B',9)
447 pdf.SetFontStyle('B',9)
367 pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
448 pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
368 pdf.SetFontStyle('',9)
449 pdf.SetFontStyle('',9)
369 pdf.RDMMultiCell(155,5, issue.subject,"RT")
450 pdf.RDMMultiCell(155,5, issue.subject,"RT")
370 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
451 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
371
452
372 pdf.SetFontStyle('B',9)
453 pdf.SetFontStyle('B',9)
373 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
454 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
374 pdf.SetFontStyle('',9)
455 pdf.SetFontStyle('',9)
375
456
376 # Set resize image scale
457 # Set resize image scale
377 pdf.SetImageScale(1.6)
458 pdf.SetImageScale(1.6)
378 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
459 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
379 issue.description.to_s, issue.attachments, "LRB")
460 issue.description.to_s, issue.attachments, "LRB")
380
461
381 unless issue.leaf?
462 unless issue.leaf?
382 # for CJK
463 # for CJK
383 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
464 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
384
465
385 pdf.SetFontStyle('B',9)
466 pdf.SetFontStyle('B',9)
386 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
467 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
387 pdf.Ln
468 pdf.Ln
388 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
469 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
389 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
470 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
390 :length => truncate_length)
471 :length => truncate_length)
391 level = 10 if level >= 10
472 level = 10 if level >= 10
392 pdf.SetFontStyle('',8)
473 pdf.SetFontStyle('',8)
393 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
474 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
394 pdf.SetFontStyle('B',8)
475 pdf.SetFontStyle('B',8)
395 pdf.RDMCell(20,5, child.status.to_s, "R")
476 pdf.RDMCell(20,5, child.status.to_s, "R")
396 pdf.Ln
477 pdf.Ln
397 end
478 end
398 end
479 end
399
480
400 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
481 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
401 unless relations.empty?
482 unless relations.empty?
402 # for CJK
483 # for CJK
403 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
484 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
404
485
405 pdf.SetFontStyle('B',9)
486 pdf.SetFontStyle('B',9)
406 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
487 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
407 pdf.Ln
488 pdf.Ln
408 relations.each do |relation|
489 relations.each do |relation|
409 buf = ""
490 buf = ""
410 buf += "#{l(relation.label_for(issue))} "
491 buf += "#{l(relation.label_for(issue))} "
411 if relation.delay && relation.delay != 0
492 if relation.delay && relation.delay != 0
412 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
493 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
413 end
494 end
414 if Setting.cross_project_issue_relations?
495 if Setting.cross_project_issue_relations?
415 buf += "#{relation.other_issue(issue).project} - "
496 buf += "#{relation.other_issue(issue).project} - "
416 end
497 end
417 buf += "#{relation.other_issue(issue).tracker}" +
498 buf += "#{relation.other_issue(issue).tracker}" +
418 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
499 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
419 buf = truncate(buf, :length => truncate_length)
500 buf = truncate(buf, :length => truncate_length)
420 pdf.SetFontStyle('', 8)
501 pdf.SetFontStyle('', 8)
421 pdf.RDMCell(35+155-60, 5, buf, "L")
502 pdf.RDMCell(35+155-60, 5, buf, "L")
422 pdf.SetFontStyle('B',8)
503 pdf.SetFontStyle('B',8)
423 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
504 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
424 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
505 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
425 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
506 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
426 pdf.Ln
507 pdf.Ln
427 end
508 end
428 end
509 end
429 pdf.RDMCell(190,5, "", "T")
510 pdf.RDMCell(190,5, "", "T")
430 pdf.Ln
511 pdf.Ln
431
512
432 if issue.changesets.any? &&
513 if issue.changesets.any? &&
433 User.current.allowed_to?(:view_changesets, issue.project)
514 User.current.allowed_to?(:view_changesets, issue.project)
434 pdf.SetFontStyle('B',9)
515 pdf.SetFontStyle('B',9)
435 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
516 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
436 pdf.Ln
517 pdf.Ln
437 for changeset in issue.changesets
518 for changeset in issue.changesets
438 pdf.SetFontStyle('B',8)
519 pdf.SetFontStyle('B',8)
439 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
520 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
440 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
521 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
441 pdf.RDMCell(190, 5, csstr)
522 pdf.RDMCell(190, 5, csstr)
442 pdf.Ln
523 pdf.Ln
443 unless changeset.comments.blank?
524 unless changeset.comments.blank?
444 pdf.SetFontStyle('',8)
525 pdf.SetFontStyle('',8)
445 pdf.RDMwriteHTMLCell(190,5,0,0,
526 pdf.RDMwriteHTMLCell(190,5,0,0,
446 changeset.comments.to_s, issue.attachments, "")
527 changeset.comments.to_s, issue.attachments, "")
447 end
528 end
448 pdf.Ln
529 pdf.Ln
449 end
530 end
450 end
531 end
451
532
452 pdf.SetFontStyle('B',9)
533 pdf.SetFontStyle('B',9)
453 pdf.RDMCell(190,5, l(:label_history), "B")
534 pdf.RDMCell(190,5, l(:label_history), "B")
454 pdf.Ln
535 pdf.Ln
455 indice = 0
536 indice = 0
456 for journal in issue.journals.find(
537 for journal in issue.journals.find(
457 :all, :include => [:user, :details],
538 :all, :include => [:user, :details],
458 :order => "#{Journal.table_name}.created_on ASC")
539 :order => "#{Journal.table_name}.created_on ASC")
459 indice = indice + 1
540 indice = indice + 1
460 pdf.SetFontStyle('B',8)
541 pdf.SetFontStyle('B',8)
461 pdf.RDMCell(190,5,
542 pdf.RDMCell(190,5,
462 "#" + indice.to_s +
543 "#" + indice.to_s +
463 " - " + format_time(journal.created_on) +
544 " - " + format_time(journal.created_on) +
464 " - " + journal.user.name)
545 " - " + journal.user.name)
465 pdf.Ln
546 pdf.Ln
466 pdf.SetFontStyle('I',8)
547 pdf.SetFontStyle('I',8)
467 details_to_strings(journal.details, true).each do |string|
548 details_to_strings(journal.details, true).each do |string|
468 pdf.RDMMultiCell(190,5, "- " + string)
549 pdf.RDMMultiCell(190,5, "- " + string)
469 end
550 end
470 if journal.notes?
551 if journal.notes?
471 pdf.Ln unless journal.details.empty?
552 pdf.Ln unless journal.details.empty?
472 pdf.SetFontStyle('',8)
553 pdf.SetFontStyle('',8)
473 pdf.RDMwriteHTMLCell(190,5,0,0,
554 pdf.RDMwriteHTMLCell(190,5,0,0,
474 journal.notes.to_s, issue.attachments, "")
555 journal.notes.to_s, issue.attachments, "")
475 end
556 end
476 pdf.Ln
557 pdf.Ln
477 end
558 end
478
559
479 if issue.attachments.any?
560 if issue.attachments.any?
480 pdf.SetFontStyle('B',9)
561 pdf.SetFontStyle('B',9)
481 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
562 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
482 pdf.Ln
563 pdf.Ln
483 for attachment in issue.attachments
564 for attachment in issue.attachments
484 pdf.SetFontStyle('',8)
565 pdf.SetFontStyle('',8)
485 pdf.RDMCell(80,5, attachment.filename)
566 pdf.RDMCell(80,5, attachment.filename)
486 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
567 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
487 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
568 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
488 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
569 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
489 pdf.Ln
570 pdf.Ln
490 end
571 end
491 end
572 end
492 pdf.Output
573 pdf.Output
493 end
574 end
494
575
495 # Returns a PDF string of a single wiki page
576 # Returns a PDF string of a single wiki page
496 def wiki_to_pdf(page, project)
577 def wiki_to_pdf(page, project)
497 pdf = ITCPDF.new(current_language)
578 pdf = ITCPDF.new(current_language)
498 pdf.SetTitle("#{project} - #{page.title}")
579 pdf.SetTitle("#{project} - #{page.title}")
499 pdf.alias_nb_pages
580 pdf.alias_nb_pages
500 pdf.footer_date = format_date(Date.today)
581 pdf.footer_date = format_date(Date.today)
501 pdf.AddPage
582 pdf.AddPage
502 pdf.SetFontStyle('B',11)
583 pdf.SetFontStyle('B',11)
503 pdf.RDMMultiCell(190,5,
584 pdf.RDMMultiCell(190,5,
504 "#{project} - #{page.title} - # #{page.content.version}")
585 "#{project} - #{page.title} - # #{page.content.version}")
505 pdf.Ln
586 pdf.Ln
506 # Set resize image scale
587 # Set resize image scale
507 pdf.SetImageScale(1.6)
588 pdf.SetImageScale(1.6)
508 pdf.SetFontStyle('',9)
589 pdf.SetFontStyle('',9)
509 pdf.RDMwriteHTMLCell(190,5,0,0,
590 pdf.RDMwriteHTMLCell(190,5,0,0,
510 page.content.text.to_s, page.attachments, "TLRB")
591 page.content.text.to_s, page.attachments, "TLRB")
511 if page.attachments.any?
592 if page.attachments.any?
512 pdf.Ln
593 pdf.Ln
513 pdf.SetFontStyle('B',9)
594 pdf.SetFontStyle('B',9)
514 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
595 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
515 pdf.Ln
596 pdf.Ln
516 for attachment in page.attachments
597 for attachment in page.attachments
517 pdf.SetFontStyle('',8)
598 pdf.SetFontStyle('',8)
518 pdf.RDMCell(80,5, attachment.filename)
599 pdf.RDMCell(80,5, attachment.filename)
519 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
600 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
520 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
601 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
521 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
602 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
522 pdf.Ln
603 pdf.Ln
523 end
604 end
524 end
605 end
525 pdf.Output
606 pdf.Output
526 end
607 end
527
608
528 class RDMPdfEncoding
609 class RDMPdfEncoding
529 def self.rdm_from_utf8(txt, encoding)
610 def self.rdm_from_utf8(txt, encoding)
530 txt ||= ''
611 txt ||= ''
531 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
612 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
532 if txt.respond_to?(:force_encoding)
613 if txt.respond_to?(:force_encoding)
533 txt.force_encoding('ASCII-8BIT')
614 txt.force_encoding('ASCII-8BIT')
534 end
615 end
535 txt
616 txt
536 end
617 end
537
618
538 def self.attach(attachments, filename, encoding)
619 def self.attach(attachments, filename, encoding)
539 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
620 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
540 atta = nil
621 atta = nil
541 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
622 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
542 atta = Attachment.latest_attach(attachments, filename_utf8)
623 atta = Attachment.latest_attach(attachments, filename_utf8)
543 end
624 end
544 if atta && atta.readable? && atta.visible?
625 if atta && atta.readable? && atta.visible?
545 return atta
626 return atta
546 else
627 else
547 return nil
628 return nil
548 end
629 end
549 end
630 end
550 end
631 end
551 end
632 end
552 end
633 end
553 end
634 end
General Comments 0
You need to be logged in to leave comments. Login now