##// END OF EJS Templates
Merged r10956 from trunk (#12513)....
Jean-Philippe Lang -
r10774:14879e24c4ae
parent child
Show More
@@ -1,782 +1,782
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 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 'tcpdf'
21 require 'tcpdf'
22 require 'fpdf/chinese'
22 require 'fpdf/chinese'
23 require 'fpdf/japanese'
23 require 'fpdf/japanese'
24 require 'fpdf/korean'
24 require 'fpdf/korean'
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 = []
79 @outlines = []
80 @outlineRoot = nil
80 @outlineRoot = nil
81 end
81 end
82
82
83 def SetFontStyle(style, size)
83 def SetFontStyle(style, size)
84 SetFont(@font_for_content, style, size)
84 SetFont(@font_for_content, style, size)
85 end
85 end
86
86
87 def SetTitle(txt)
87 def SetTitle(txt)
88 txt = begin
88 txt = begin
89 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
89 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
90 hextxt = "<FEFF" # FEFF is BOM
90 hextxt = "<FEFF" # FEFF is BOM
91 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
91 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
92 hextxt << ">"
92 hextxt << ">"
93 rescue
93 rescue
94 txt
94 txt
95 end || ''
95 end || ''
96 super(txt)
96 super(txt)
97 end
97 end
98
98
99 def textstring(s)
99 def textstring(s)
100 # Format a text string
100 # Format a text string
101 if s =~ /^</ # This means the string is hex-dumped.
101 if s =~ /^</ # This means the string is hex-dumped.
102 return s
102 return s
103 else
103 else
104 return '('+escape(s)+')'
104 return '('+escape(s)+')'
105 end
105 end
106 end
106 end
107
107
108 def fix_text_encoding(txt)
108 def fix_text_encoding(txt)
109 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
109 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
110 end
110 end
111
111
112 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='')
113 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)
114 end
114 end
115
115
116 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)
117 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
117 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
118 end
118 end
119
119
120 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)
121 @attachments = attachments
121 @attachments = attachments
122 writeHTMLCell(w, h, x, y,
122 writeHTMLCell(w, h, x, y,
123 fix_text_encoding(
123 fix_text_encoding(
124 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
124 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
125 border, ln, fill)
125 border, ln, fill)
126 end
126 end
127
127
128 def getImageFilename(attrname)
128 def getImageFilename(attrname)
129 # attrname: general_pdf_encoding string file/uri name
129 # attrname: general_pdf_encoding string file/uri name
130 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
130 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
131 if atta
131 if atta
132 return atta.diskfile
132 return atta.diskfile
133 else
133 else
134 return nil
134 return nil
135 end
135 end
136 end
136 end
137
137
138 def Footer
138 def Footer
139 SetFont(@font_for_footer, 'I', 8)
139 SetFont(@font_for_footer, 'I', 8)
140 SetY(-15)
140 SetY(-15)
141 SetX(15)
141 SetX(15)
142 RDMCell(0, 5, @footer_date, 0, 0, 'L')
142 RDMCell(0, 5, @footer_date, 0, 0, 'L')
143 SetY(-15)
143 SetY(-15)
144 SetX(-30)
144 SetX(-30)
145 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
145 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
146 end
146 end
147
147
148 def Bookmark(txt, level=0, y=0)
148 def Bookmark(txt, level=0, y=0)
149 if (y == -1)
149 if (y == -1)
150 y = GetY()
150 y = GetY()
151 end
151 end
152 @outlines << {:t => txt, :l => level, :p => PageNo(), :y => (@h - y)*@k}
152 @outlines << {:t => txt, :l => level, :p => PageNo(), :y => (@h - y)*@k}
153 end
153 end
154
154
155 def bookmark_title(txt)
155 def bookmark_title(txt)
156 txt = begin
156 txt = begin
157 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
157 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
158 hextxt = "<FEFF" # FEFF is BOM
158 hextxt = "<FEFF" # FEFF is BOM
159 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
159 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
160 hextxt << ">"
160 hextxt << ">"
161 rescue
161 rescue
162 txt
162 txt
163 end || ''
163 end || ''
164 end
164 end
165
165
166 def putbookmarks
166 def putbookmarks
167 nb=@outlines.size
167 nb=@outlines.size
168 return if (nb==0)
168 return if (nb==0)
169 lru=[]
169 lru=[]
170 level=0
170 level=0
171 @outlines.each_with_index do |o, i|
171 @outlines.each_with_index do |o, i|
172 if(o[:l]>0)
172 if(o[:l]>0)
173 parent=lru[o[:l]-1]
173 parent=lru[o[:l]-1]
174 #Set parent and last pointers
174 #Set parent and last pointers
175 @outlines[i][:parent]=parent
175 @outlines[i][:parent]=parent
176 @outlines[parent][:last]=i
176 @outlines[parent][:last]=i
177 if (o[:l]>level)
177 if (o[:l]>level)
178 #Level increasing: set first pointer
178 #Level increasing: set first pointer
179 @outlines[parent][:first]=i
179 @outlines[parent][:first]=i
180 end
180 end
181 else
181 else
182 @outlines[i][:parent]=nb
182 @outlines[i][:parent]=nb
183 end
183 end
184 if (o[:l]<=level && i>0)
184 if (o[:l]<=level && i>0)
185 #Set prev and next pointers
185 #Set prev and next pointers
186 prev=lru[o[:l]]
186 prev=lru[o[:l]]
187 @outlines[prev][:next]=i
187 @outlines[prev][:next]=i
188 @outlines[i][:prev]=prev
188 @outlines[i][:prev]=prev
189 end
189 end
190 lru[o[:l]]=i
190 lru[o[:l]]=i
191 level=o[:l]
191 level=o[:l]
192 end
192 end
193 #Outline items
193 #Outline items
194 n=self.n+1
194 n=self.n+1
195 @outlines.each_with_index do |o, i|
195 @outlines.each_with_index do |o, i|
196 newobj()
196 newobj()
197 out('<</Title '+bookmark_title(o[:t]))
197 out('<</Title '+bookmark_title(o[:t]))
198 out("/Parent #{n+o[:parent]} 0 R")
198 out("/Parent #{n+o[:parent]} 0 R")
199 if (o[:prev])
199 if (o[:prev])
200 out("/Prev #{n+o[:prev]} 0 R")
200 out("/Prev #{n+o[:prev]} 0 R")
201 end
201 end
202 if (o[:next])
202 if (o[:next])
203 out("/Next #{n+o[:next]} 0 R")
203 out("/Next #{n+o[:next]} 0 R")
204 end
204 end
205 if (o[:first])
205 if (o[:first])
206 out("/First #{n+o[:first]} 0 R")
206 out("/First #{n+o[:first]} 0 R")
207 end
207 end
208 if (o[:last])
208 if (o[:last])
209 out("/Last #{n+o[:last]} 0 R")
209 out("/Last #{n+o[:last]} 0 R")
210 end
210 end
211 out("/Dest [%d 0 R /XYZ 0 %.2f null]" % [1+2*o[:p], o[:y]])
211 out("/Dest [%d 0 R /XYZ 0 %.2f null]" % [1+2*o[:p], o[:y]])
212 out('/Count 0>>')
212 out('/Count 0>>')
213 out('endobj')
213 out('endobj')
214 end
214 end
215 #Outline root
215 #Outline root
216 newobj()
216 newobj()
217 @outlineRoot=self.n
217 @outlineRoot=self.n
218 out("<</Type /Outlines /First #{n} 0 R");
218 out("<</Type /Outlines /First #{n} 0 R");
219 out("/Last #{n+lru[0]} 0 R>>");
219 out("/Last #{n+lru[0]} 0 R>>");
220 out('endobj');
220 out('endobj');
221 end
221 end
222
222
223 def putresources()
223 def putresources()
224 super
224 super
225 putbookmarks()
225 putbookmarks()
226 end
226 end
227
227
228 def putcatalog()
228 def putcatalog()
229 super
229 super
230 if(@outlines.size > 0)
230 if(@outlines.size > 0)
231 out("/Outlines #{@outlineRoot} 0 R");
231 out("/Outlines #{@outlineRoot} 0 R");
232 out('/PageMode /UseOutlines');
232 out('/PageMode /UseOutlines');
233 end
233 end
234 end
234 end
235 end
235 end
236
236
237 # fetch row values
237 # fetch row values
238 def fetch_row_values(issue, query, level)
238 def fetch_row_values(issue, query, level)
239 query.columns.collect do |column|
239 query.columns.collect do |column|
240 s = if column.is_a?(QueryCustomFieldColumn)
240 s = if column.is_a?(QueryCustomFieldColumn)
241 cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
241 cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
242 show_value(cv)
242 show_value(cv)
243 else
243 else
244 value = issue.send(column.name)
244 value = issue.send(column.name)
245 if column.name == :subject
245 if column.name == :subject
246 value = " " * level + value
246 value = " " * level + value
247 end
247 end
248 if value.is_a?(Date)
248 if value.is_a?(Date)
249 format_date(value)
249 format_date(value)
250 elsif value.is_a?(Time)
250 elsif value.is_a?(Time)
251 format_time(value)
251 format_time(value)
252 else
252 else
253 value
253 value
254 end
254 end
255 end
255 end
256 s.to_s
256 s.to_s
257 end
257 end
258 end
258 end
259
259
260 # calculate columns width
260 # calculate columns width
261 def calc_col_width(issues, query, table_width, pdf)
261 def calc_col_width(issues, query, table_width, pdf)
262 # calculate statistics
262 # calculate statistics
263 # by captions
263 # by captions
264 pdf.SetFontStyle('B',8)
264 pdf.SetFontStyle('B',8)
265 col_padding = pdf.GetStringWidth('OO')
265 col_padding = pdf.GetStringWidth('OO')
266 col_width_min = query.columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding}
266 col_width_min = query.columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding}
267 col_width_max = Array.new(col_width_min)
267 col_width_max = Array.new(col_width_min)
268 col_width_avg = Array.new(col_width_min)
268 col_width_avg = Array.new(col_width_min)
269 word_width_max = query.columns.map {|c|
269 word_width_max = query.columns.map {|c|
270 n = 10
270 n = 10
271 c.caption.split.each {|w|
271 c.caption.split.each {|w|
272 x = pdf.GetStringWidth(w) + col_padding
272 x = pdf.GetStringWidth(w) + col_padding
273 n = x if n < x
273 n = x if n < x
274 }
274 }
275 n
275 n
276 }
276 }
277
277
278 # by properties of issues
278 # by properties of issues
279 pdf.SetFontStyle('',8)
279 pdf.SetFontStyle('',8)
280 col_padding = pdf.GetStringWidth('OO')
280 col_padding = pdf.GetStringWidth('OO')
281 k = 1
281 k = 1
282 issue_list(issues) {|issue, level|
282 issue_list(issues) {|issue, level|
283 k += 1
283 k += 1
284 values = fetch_row_values(issue, query, level)
284 values = fetch_row_values(issue, query, level)
285 values.each_with_index {|v,i|
285 values.each_with_index {|v,i|
286 n = pdf.GetStringWidth(v) + col_padding
286 n = pdf.GetStringWidth(v) + col_padding
287 col_width_max[i] = n if col_width_max[i] < n
287 col_width_max[i] = n if col_width_max[i] < n
288 col_width_min[i] = n if col_width_min[i] > n
288 col_width_min[i] = n if col_width_min[i] > n
289 col_width_avg[i] += n
289 col_width_avg[i] += n
290 v.split.each {|w|
290 v.split.each {|w|
291 x = pdf.GetStringWidth(w) + col_padding
291 x = pdf.GetStringWidth(w) + col_padding
292 word_width_max[i] = x if word_width_max[i] < x
292 word_width_max[i] = x if word_width_max[i] < x
293 }
293 }
294 }
294 }
295 }
295 }
296 col_width_avg.map! {|x| x / k}
296 col_width_avg.map! {|x| x / k}
297
297
298 # calculate columns width
298 # calculate columns width
299 ratio = table_width / col_width_avg.inject(0) {|s,w| s += w}
299 ratio = table_width / col_width_avg.inject(0) {|s,w| s += w}
300 col_width = col_width_avg.map {|w| w * ratio}
300 col_width = col_width_avg.map {|w| w * ratio}
301
301
302 # correct max word width if too many columns
302 # correct max word width if too many columns
303 ratio = table_width / word_width_max.inject(0) {|s,w| s += w}
303 ratio = table_width / word_width_max.inject(0) {|s,w| s += w}
304 word_width_max.map! {|v| v * ratio} if ratio < 1
304 word_width_max.map! {|v| v * ratio} if ratio < 1
305
305
306 # correct and lock width of some columns
306 # correct and lock width of some columns
307 done = 1
307 done = 1
308 col_fix = []
308 col_fix = []
309 col_width.each_with_index do |w,i|
309 col_width.each_with_index do |w,i|
310 if w > col_width_max[i]
310 if w > col_width_max[i]
311 col_width[i] = col_width_max[i]
311 col_width[i] = col_width_max[i]
312 col_fix[i] = 1
312 col_fix[i] = 1
313 done = 0
313 done = 0
314 elsif w < word_width_max[i]
314 elsif w < word_width_max[i]
315 col_width[i] = word_width_max[i]
315 col_width[i] = word_width_max[i]
316 col_fix[i] = 1
316 col_fix[i] = 1
317 done = 0
317 done = 0
318 else
318 else
319 col_fix[i] = 0
319 col_fix[i] = 0
320 end
320 end
321 end
321 end
322
322
323 # iterate while need to correct and lock coluns width
323 # iterate while need to correct and lock coluns width
324 while done == 0
324 while done == 0
325 # calculate free & locked columns width
325 # calculate free & locked columns width
326 done = 1
326 done = 1
327 fix_col_width = 0
327 fix_col_width = 0
328 free_col_width = 0
328 free_col_width = 0
329 col_width.each_with_index do |w,i|
329 col_width.each_with_index do |w,i|
330 if col_fix[i] == 1
330 if col_fix[i] == 1
331 fix_col_width += w
331 fix_col_width += w
332 else
332 else
333 free_col_width += w
333 free_col_width += w
334 end
334 end
335 end
335 end
336
336
337 # calculate column normalizing ratio
337 # calculate column normalizing ratio
338 if free_col_width == 0
338 if free_col_width == 0
339 ratio = table_width / col_width.inject(0) {|s,w| s += w}
339 ratio = table_width / col_width.inject(0) {|s,w| s += w}
340 else
340 else
341 ratio = (table_width - fix_col_width) / free_col_width
341 ratio = (table_width - fix_col_width) / free_col_width
342 end
342 end
343
343
344 # correct columns width
344 # correct columns width
345 col_width.each_with_index do |w,i|
345 col_width.each_with_index do |w,i|
346 if col_fix[i] == 0
346 if col_fix[i] == 0
347 col_width[i] = w * ratio
347 col_width[i] = w * ratio
348
348
349 # check if column width less then max word width
349 # check if column width less then max word width
350 if col_width[i] < word_width_max[i]
350 if col_width[i] < word_width_max[i]
351 col_width[i] = word_width_max[i]
351 col_width[i] = word_width_max[i]
352 col_fix[i] = 1
352 col_fix[i] = 1
353 done = 0
353 done = 0
354 elsif col_width[i] > col_width_max[i]
354 elsif col_width[i] > col_width_max[i]
355 col_width[i] = col_width_max[i]
355 col_width[i] = col_width_max[i]
356 col_fix[i] = 1
356 col_fix[i] = 1
357 done = 0
357 done = 0
358 end
358 end
359 end
359 end
360 end
360 end
361 end
361 end
362 col_width
362 col_width
363 end
363 end
364
364
365 def render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
365 def render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
366 # headers
366 # headers
367 pdf.SetFontStyle('B',8)
367 pdf.SetFontStyle('B',8)
368 pdf.SetFillColor(230, 230, 230)
368 pdf.SetFillColor(230, 230, 230)
369
369
370 # render it background to find the max height used
370 # render it background to find the max height used
371 base_x = pdf.GetX
371 base_x = pdf.GetX
372 base_y = pdf.GetY
372 base_y = pdf.GetY
373 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
373 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
374 pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD');
374 pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD');
375 pdf.SetXY(base_x, base_y);
375 pdf.SetXY(base_x, base_y);
376
376
377 # write the cells on page
377 # write the cells on page
378 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
378 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
379 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
379 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
380 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
380 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
381 pdf.SetY(base_y + max_height);
381 pdf.SetY(base_y + max_height);
382
382
383 # rows
383 # rows
384 pdf.SetFontStyle('',8)
384 pdf.SetFontStyle('',8)
385 pdf.SetFillColor(255, 255, 255)
385 pdf.SetFillColor(255, 255, 255)
386 end
386 end
387
387
388 # Returns a PDF string of a list of issues
388 # Returns a PDF string of a list of issues
389 def issues_to_pdf(issues, project, query)
389 def issues_to_pdf(issues, project, query)
390 pdf = ITCPDF.new(current_language)
390 pdf = ITCPDF.new(current_language)
391 title = query.new_record? ? l(:label_issue_plural) : query.name
391 title = query.new_record? ? l(:label_issue_plural) : query.name
392 title = "#{project} - #{title}" if project
392 title = "#{project} - #{title}" if project
393 pdf.SetTitle(title)
393 pdf.SetTitle(title)
394 pdf.alias_nb_pages
394 pdf.alias_nb_pages
395 pdf.footer_date = format_date(Date.today)
395 pdf.footer_date = format_date(Date.today)
396 pdf.SetAutoPageBreak(false)
396 pdf.SetAutoPageBreak(false)
397 pdf.AddPage("L")
397 pdf.AddPage("L")
398
398
399 # Landscape A4 = 210 x 297 mm
399 # Landscape A4 = 210 x 297 mm
400 page_height = 210
400 page_height = 210
401 page_width = 297
401 page_width = 297
402 right_margin = 10
402 right_margin = 10
403 bottom_margin = 20
403 bottom_margin = 20
404 col_id_width = 10
404 col_id_width = 10
405 row_height = 4
405 row_height = 4
406
406
407 # column widths
407 # column widths
408 table_width = page_width - right_margin - 10 # fixed left margin
408 table_width = page_width - right_margin - 10 # fixed left margin
409 col_width = []
409 col_width = []
410 unless query.columns.empty?
410 unless query.columns.empty?
411 col_width = calc_col_width(issues, query, table_width - col_id_width, pdf)
411 col_width = calc_col_width(issues, query, table_width - col_id_width, pdf)
412 table_width = col_width.inject(0) {|s,v| s += v}
412 table_width = col_width.inject(0) {|s,v| s += v}
413 end
413 end
414
414
415 # title
415 # title
416 pdf.SetFontStyle('B',11)
416 pdf.SetFontStyle('B',11)
417 pdf.RDMCell(190,10, title)
417 pdf.RDMCell(190,10, title)
418 pdf.Ln
418 pdf.Ln
419 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
419 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
420 previous_group = false
420 previous_group = false
421 issue_list(issues) do |issue, level|
421 issue_list(issues) do |issue, level|
422 if query.grouped? &&
422 if query.grouped? &&
423 (group = query.group_by_column.value(issue)) != previous_group
423 (group = query.group_by_column.value(issue)) != previous_group
424 pdf.SetFontStyle('B',10)
424 pdf.SetFontStyle('B',10)
425 group_label = group.blank? ? 'None' : group.to_s
425 group_label = group.blank? ? 'None' : group.to_s.dup
426 group_label << " (#{query.issue_count_by_group[group]})"
426 group_label << " (#{query.issue_count_by_group[group]})"
427 pdf.Bookmark group_label, 0, -1
427 pdf.Bookmark group_label, 0, -1
428 pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L')
428 pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L')
429 pdf.SetFontStyle('',8)
429 pdf.SetFontStyle('',8)
430 previous_group = group
430 previous_group = group
431 end
431 end
432
432
433 # fetch row values
433 # fetch row values
434 col_values = fetch_row_values(issue, query, level)
434 col_values = fetch_row_values(issue, query, level)
435
435
436 # render it off-page to find the max height used
436 # render it off-page to find the max height used
437 base_x = pdf.GetX
437 base_x = pdf.GetX
438 base_y = pdf.GetY
438 base_y = pdf.GetY
439 pdf.SetY(2 * page_height)
439 pdf.SetY(2 * page_height)
440 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
440 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
441 pdf.SetXY(base_x, base_y)
441 pdf.SetXY(base_x, base_y)
442
442
443 # make new page if it doesn't fit on the current one
443 # make new page if it doesn't fit on the current one
444 space_left = page_height - base_y - bottom_margin
444 space_left = page_height - base_y - bottom_margin
445 if max_height > space_left
445 if max_height > space_left
446 pdf.AddPage("L")
446 pdf.AddPage("L")
447 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
447 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
448 base_x = pdf.GetX
448 base_x = pdf.GetX
449 base_y = pdf.GetY
449 base_y = pdf.GetY
450 end
450 end
451
451
452 # write the cells on page
452 # write the cells on page
453 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
453 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
454 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
454 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
455 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
455 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
456 pdf.SetY(base_y + max_height);
456 pdf.SetY(base_y + max_height);
457 end
457 end
458
458
459 if issues.size == Setting.issues_export_limit.to_i
459 if issues.size == Setting.issues_export_limit.to_i
460 pdf.SetFontStyle('B',10)
460 pdf.SetFontStyle('B',10)
461 pdf.RDMCell(0, row_height, '...')
461 pdf.RDMCell(0, row_height, '...')
462 end
462 end
463 pdf.Output
463 pdf.Output
464 end
464 end
465
465
466 # Renders MultiCells and returns the maximum height used
466 # Renders MultiCells and returns the maximum height used
467 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
467 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
468 row_height, head=false)
468 row_height, head=false)
469 base_y = pdf.GetY
469 base_y = pdf.GetY
470 max_height = row_height
470 max_height = row_height
471 col_values.each_with_index do |column, i|
471 col_values.each_with_index do |column, i|
472 col_x = pdf.GetX
472 col_x = pdf.GetX
473 if head == true
473 if head == true
474 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
474 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
475 else
475 else
476 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
476 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
477 end
477 end
478 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
478 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
479 pdf.SetXY(col_x + col_widths[i], base_y);
479 pdf.SetXY(col_x + col_widths[i], base_y);
480 end
480 end
481 return max_height
481 return max_height
482 end
482 end
483
483
484 # Draw lines to close the row (MultiCell border drawing in not uniform)
484 # Draw lines to close the row (MultiCell border drawing in not uniform)
485 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
485 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
486 id_width, col_widths)
486 id_width, col_widths)
487 col_x = top_x + id_width
487 col_x = top_x + id_width
488 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
488 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
489 col_widths.each do |width|
489 col_widths.each do |width|
490 col_x += width
490 col_x += width
491 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
491 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
492 end
492 end
493 pdf.Line(top_x, top_y, top_x, lower_y) # left border
493 pdf.Line(top_x, top_y, top_x, lower_y) # left border
494 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
494 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
495 end
495 end
496
496
497 # Returns a PDF string of a single issue
497 # Returns a PDF string of a single issue
498 def issue_to_pdf(issue)
498 def issue_to_pdf(issue)
499 pdf = ITCPDF.new(current_language)
499 pdf = ITCPDF.new(current_language)
500 pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}")
500 pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}")
501 pdf.alias_nb_pages
501 pdf.alias_nb_pages
502 pdf.footer_date = format_date(Date.today)
502 pdf.footer_date = format_date(Date.today)
503 pdf.AddPage
503 pdf.AddPage
504 pdf.SetFontStyle('B',11)
504 pdf.SetFontStyle('B',11)
505 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
505 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
506 pdf.RDMMultiCell(190, 5, buf)
506 pdf.RDMMultiCell(190, 5, buf)
507 pdf.SetFontStyle('',8)
507 pdf.SetFontStyle('',8)
508 base_x = pdf.GetX
508 base_x = pdf.GetX
509 i = 1
509 i = 1
510 issue.ancestors.each do |ancestor|
510 issue.ancestors.each do |ancestor|
511 pdf.SetX(base_x + i)
511 pdf.SetX(base_x + i)
512 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
512 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
513 pdf.RDMMultiCell(190 - i, 5, buf)
513 pdf.RDMMultiCell(190 - i, 5, buf)
514 i += 1 if i < 35
514 i += 1 if i < 35
515 end
515 end
516 pdf.SetFontStyle('B',11)
516 pdf.SetFontStyle('B',11)
517 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
517 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
518 pdf.SetFontStyle('',8)
518 pdf.SetFontStyle('',8)
519 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
519 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
520 pdf.Ln
520 pdf.Ln
521
521
522 left = []
522 left = []
523 left << [l(:field_status), issue.status]
523 left << [l(:field_status), issue.status]
524 left << [l(:field_priority), issue.priority]
524 left << [l(:field_priority), issue.priority]
525 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
525 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
526 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
526 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
527 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
527 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
528
528
529 right = []
529 right = []
530 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
530 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
531 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
531 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
532 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
532 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
533 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
533 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
534 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
534 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
535
535
536 rows = left.size > right.size ? left.size : right.size
536 rows = left.size > right.size ? left.size : right.size
537 while left.size < rows
537 while left.size < rows
538 left << nil
538 left << nil
539 end
539 end
540 while right.size < rows
540 while right.size < rows
541 right << nil
541 right << nil
542 end
542 end
543
543
544 half = (issue.custom_field_values.size / 2.0).ceil
544 half = (issue.custom_field_values.size / 2.0).ceil
545 issue.custom_field_values.each_with_index do |custom_value, i|
545 issue.custom_field_values.each_with_index do |custom_value, i|
546 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)]
546 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)]
547 end
547 end
548
548
549 rows = left.size > right.size ? left.size : right.size
549 rows = left.size > right.size ? left.size : right.size
550 rows.times do |i|
550 rows.times do |i|
551 item = left[i]
551 item = left[i]
552 pdf.SetFontStyle('B',9)
552 pdf.SetFontStyle('B',9)
553 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
553 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
554 pdf.SetFontStyle('',9)
554 pdf.SetFontStyle('',9)
555 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
555 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
556
556
557 item = right[i]
557 item = right[i]
558 pdf.SetFontStyle('B',9)
558 pdf.SetFontStyle('B',9)
559 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
559 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
560 pdf.SetFontStyle('',9)
560 pdf.SetFontStyle('',9)
561 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
561 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
562 pdf.Ln
562 pdf.Ln
563 end
563 end
564
564
565 pdf.SetFontStyle('B',9)
565 pdf.SetFontStyle('B',9)
566 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
566 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
567 pdf.SetFontStyle('',9)
567 pdf.SetFontStyle('',9)
568
568
569 # Set resize image scale
569 # Set resize image scale
570 pdf.SetImageScale(1.6)
570 pdf.SetImageScale(1.6)
571 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
571 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
572 issue.description.to_s, issue.attachments, "LRB")
572 issue.description.to_s, issue.attachments, "LRB")
573
573
574 unless issue.leaf?
574 unless issue.leaf?
575 # for CJK
575 # for CJK
576 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
576 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
577
577
578 pdf.SetFontStyle('B',9)
578 pdf.SetFontStyle('B',9)
579 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
579 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
580 pdf.Ln
580 pdf.Ln
581 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
581 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
582 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
582 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
583 :length => truncate_length)
583 :length => truncate_length)
584 level = 10 if level >= 10
584 level = 10 if level >= 10
585 pdf.SetFontStyle('',8)
585 pdf.SetFontStyle('',8)
586 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
586 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
587 pdf.SetFontStyle('B',8)
587 pdf.SetFontStyle('B',8)
588 pdf.RDMCell(20,5, child.status.to_s, "R")
588 pdf.RDMCell(20,5, child.status.to_s, "R")
589 pdf.Ln
589 pdf.Ln
590 end
590 end
591 end
591 end
592
592
593 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
593 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
594 unless relations.empty?
594 unless relations.empty?
595 # for CJK
595 # for CJK
596 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
596 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
597
597
598 pdf.SetFontStyle('B',9)
598 pdf.SetFontStyle('B',9)
599 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
599 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
600 pdf.Ln
600 pdf.Ln
601 relations.each do |relation|
601 relations.each do |relation|
602 buf = ""
602 buf = ""
603 buf += "#{l(relation.label_for(issue))} "
603 buf += "#{l(relation.label_for(issue))} "
604 if relation.delay && relation.delay != 0
604 if relation.delay && relation.delay != 0
605 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
605 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
606 end
606 end
607 if Setting.cross_project_issue_relations?
607 if Setting.cross_project_issue_relations?
608 buf += "#{relation.other_issue(issue).project} - "
608 buf += "#{relation.other_issue(issue).project} - "
609 end
609 end
610 buf += "#{relation.other_issue(issue).tracker}" +
610 buf += "#{relation.other_issue(issue).tracker}" +
611 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
611 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
612 buf = truncate(buf, :length => truncate_length)
612 buf = truncate(buf, :length => truncate_length)
613 pdf.SetFontStyle('', 8)
613 pdf.SetFontStyle('', 8)
614 pdf.RDMCell(35+155-60, 5, buf, "L")
614 pdf.RDMCell(35+155-60, 5, buf, "L")
615 pdf.SetFontStyle('B',8)
615 pdf.SetFontStyle('B',8)
616 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
616 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
617 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
617 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
618 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
618 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
619 pdf.Ln
619 pdf.Ln
620 end
620 end
621 end
621 end
622 pdf.RDMCell(190,5, "", "T")
622 pdf.RDMCell(190,5, "", "T")
623 pdf.Ln
623 pdf.Ln
624
624
625 if issue.changesets.any? &&
625 if issue.changesets.any? &&
626 User.current.allowed_to?(:view_changesets, issue.project)
626 User.current.allowed_to?(:view_changesets, issue.project)
627 pdf.SetFontStyle('B',9)
627 pdf.SetFontStyle('B',9)
628 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
628 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
629 pdf.Ln
629 pdf.Ln
630 for changeset in issue.changesets
630 for changeset in issue.changesets
631 pdf.SetFontStyle('B',8)
631 pdf.SetFontStyle('B',8)
632 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
632 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
633 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
633 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
634 pdf.RDMCell(190, 5, csstr)
634 pdf.RDMCell(190, 5, csstr)
635 pdf.Ln
635 pdf.Ln
636 unless changeset.comments.blank?
636 unless changeset.comments.blank?
637 pdf.SetFontStyle('',8)
637 pdf.SetFontStyle('',8)
638 pdf.RDMwriteHTMLCell(190,5,0,0,
638 pdf.RDMwriteHTMLCell(190,5,0,0,
639 changeset.comments.to_s, issue.attachments, "")
639 changeset.comments.to_s, issue.attachments, "")
640 end
640 end
641 pdf.Ln
641 pdf.Ln
642 end
642 end
643 end
643 end
644
644
645 pdf.SetFontStyle('B',9)
645 pdf.SetFontStyle('B',9)
646 pdf.RDMCell(190,5, l(:label_history), "B")
646 pdf.RDMCell(190,5, l(:label_history), "B")
647 pdf.Ln
647 pdf.Ln
648 indice = 0
648 indice = 0
649 for journal in issue.journals.find(
649 for journal in issue.journals.find(
650 :all, :include => [:user, :details],
650 :all, :include => [:user, :details],
651 :order => "#{Journal.table_name}.created_on ASC")
651 :order => "#{Journal.table_name}.created_on ASC")
652 indice = indice + 1
652 indice = indice + 1
653 pdf.SetFontStyle('B',8)
653 pdf.SetFontStyle('B',8)
654 pdf.RDMCell(190,5,
654 pdf.RDMCell(190,5,
655 "#" + indice.to_s +
655 "#" + indice.to_s +
656 " - " + format_time(journal.created_on) +
656 " - " + format_time(journal.created_on) +
657 " - " + journal.user.name)
657 " - " + journal.user.name)
658 pdf.Ln
658 pdf.Ln
659 pdf.SetFontStyle('I',8)
659 pdf.SetFontStyle('I',8)
660 details_to_strings(journal.details, true).each do |string|
660 details_to_strings(journal.details, true).each do |string|
661 pdf.RDMMultiCell(190,5, "- " + string)
661 pdf.RDMMultiCell(190,5, "- " + string)
662 end
662 end
663 if journal.notes?
663 if journal.notes?
664 pdf.Ln unless journal.details.empty?
664 pdf.Ln unless journal.details.empty?
665 pdf.SetFontStyle('',8)
665 pdf.SetFontStyle('',8)
666 pdf.RDMwriteHTMLCell(190,5,0,0,
666 pdf.RDMwriteHTMLCell(190,5,0,0,
667 journal.notes.to_s, issue.attachments, "")
667 journal.notes.to_s, issue.attachments, "")
668 end
668 end
669 pdf.Ln
669 pdf.Ln
670 end
670 end
671
671
672 if issue.attachments.any?
672 if issue.attachments.any?
673 pdf.SetFontStyle('B',9)
673 pdf.SetFontStyle('B',9)
674 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
674 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
675 pdf.Ln
675 pdf.Ln
676 for attachment in issue.attachments
676 for attachment in issue.attachments
677 pdf.SetFontStyle('',8)
677 pdf.SetFontStyle('',8)
678 pdf.RDMCell(80,5, attachment.filename)
678 pdf.RDMCell(80,5, attachment.filename)
679 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
679 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
680 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
680 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
681 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
681 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
682 pdf.Ln
682 pdf.Ln
683 end
683 end
684 end
684 end
685 pdf.Output
685 pdf.Output
686 end
686 end
687
687
688 # Returns a PDF string of a set of wiki pages
688 # Returns a PDF string of a set of wiki pages
689 def wiki_pages_to_pdf(pages, project)
689 def wiki_pages_to_pdf(pages, project)
690 pdf = ITCPDF.new(current_language)
690 pdf = ITCPDF.new(current_language)
691 pdf.SetTitle(project.name)
691 pdf.SetTitle(project.name)
692 pdf.alias_nb_pages
692 pdf.alias_nb_pages
693 pdf.footer_date = format_date(Date.today)
693 pdf.footer_date = format_date(Date.today)
694 pdf.AddPage
694 pdf.AddPage
695 pdf.SetFontStyle('B',11)
695 pdf.SetFontStyle('B',11)
696 pdf.RDMMultiCell(190,5, project.name)
696 pdf.RDMMultiCell(190,5, project.name)
697 pdf.Ln
697 pdf.Ln
698 # Set resize image scale
698 # Set resize image scale
699 pdf.SetImageScale(1.6)
699 pdf.SetImageScale(1.6)
700 pdf.SetFontStyle('',9)
700 pdf.SetFontStyle('',9)
701 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
701 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
702 pdf.Output
702 pdf.Output
703 end
703 end
704
704
705 # Returns a PDF string of a single wiki page
705 # Returns a PDF string of a single wiki page
706 def wiki_page_to_pdf(page, project)
706 def wiki_page_to_pdf(page, project)
707 pdf = ITCPDF.new(current_language)
707 pdf = ITCPDF.new(current_language)
708 pdf.SetTitle("#{project} - #{page.title}")
708 pdf.SetTitle("#{project} - #{page.title}")
709 pdf.alias_nb_pages
709 pdf.alias_nb_pages
710 pdf.footer_date = format_date(Date.today)
710 pdf.footer_date = format_date(Date.today)
711 pdf.AddPage
711 pdf.AddPage
712 pdf.SetFontStyle('B',11)
712 pdf.SetFontStyle('B',11)
713 pdf.RDMMultiCell(190,5,
713 pdf.RDMMultiCell(190,5,
714 "#{project} - #{page.title} - # #{page.content.version}")
714 "#{project} - #{page.title} - # #{page.content.version}")
715 pdf.Ln
715 pdf.Ln
716 # Set resize image scale
716 # Set resize image scale
717 pdf.SetImageScale(1.6)
717 pdf.SetImageScale(1.6)
718 pdf.SetFontStyle('',9)
718 pdf.SetFontStyle('',9)
719 write_wiki_page(pdf, page)
719 write_wiki_page(pdf, page)
720 pdf.Output
720 pdf.Output
721 end
721 end
722
722
723 def write_page_hierarchy(pdf, pages, node=nil, level=0)
723 def write_page_hierarchy(pdf, pages, node=nil, level=0)
724 if pages[node]
724 if pages[node]
725 pages[node].each do |page|
725 pages[node].each do |page|
726 if @new_page
726 if @new_page
727 pdf.AddPage
727 pdf.AddPage
728 else
728 else
729 @new_page = true
729 @new_page = true
730 end
730 end
731 pdf.Bookmark page.title, level
731 pdf.Bookmark page.title, level
732 write_wiki_page(pdf, page)
732 write_wiki_page(pdf, page)
733 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
733 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
734 end
734 end
735 end
735 end
736 end
736 end
737
737
738 def write_wiki_page(pdf, page)
738 def write_wiki_page(pdf, page)
739 pdf.RDMwriteHTMLCell(190,5,0,0,
739 pdf.RDMwriteHTMLCell(190,5,0,0,
740 page.content.text.to_s, page.attachments, 0)
740 page.content.text.to_s, page.attachments, 0)
741 if page.attachments.any?
741 if page.attachments.any?
742 pdf.Ln
742 pdf.Ln
743 pdf.SetFontStyle('B',9)
743 pdf.SetFontStyle('B',9)
744 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
744 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
745 pdf.Ln
745 pdf.Ln
746 for attachment in page.attachments
746 for attachment in page.attachments
747 pdf.SetFontStyle('',8)
747 pdf.SetFontStyle('',8)
748 pdf.RDMCell(80,5, attachment.filename)
748 pdf.RDMCell(80,5, attachment.filename)
749 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
749 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
750 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
750 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
751 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
751 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
752 pdf.Ln
752 pdf.Ln
753 end
753 end
754 end
754 end
755 end
755 end
756
756
757 class RDMPdfEncoding
757 class RDMPdfEncoding
758 def self.rdm_from_utf8(txt, encoding)
758 def self.rdm_from_utf8(txt, encoding)
759 txt ||= ''
759 txt ||= ''
760 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
760 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
761 if txt.respond_to?(:force_encoding)
761 if txt.respond_to?(:force_encoding)
762 txt.force_encoding('ASCII-8BIT')
762 txt.force_encoding('ASCII-8BIT')
763 end
763 end
764 txt
764 txt
765 end
765 end
766
766
767 def self.attach(attachments, filename, encoding)
767 def self.attach(attachments, filename, encoding)
768 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
768 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
769 atta = nil
769 atta = nil
770 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
770 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
771 atta = Attachment.latest_attach(attachments, filename_utf8)
771 atta = Attachment.latest_attach(attachments, filename_utf8)
772 end
772 end
773 if atta && atta.readable? && atta.visible?
773 if atta && atta.readable? && atta.visible?
774 return atta
774 return atta
775 else
775 else
776 return nil
776 return nil
777 end
777 end
778 end
778 end
779 end
779 end
780 end
780 end
781 end
781 end
782 end
782 end
General Comments 0
You need to be logged in to leave comments. Login now