##// END OF EJS Templates
Merged r10956 from trunk (#12513)....
Jean-Philippe Lang -
r10775:d22f782d3f9b
parent child
Show More
@@ -1,792 +1,792
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, orientation='P')
37 def initialize(lang, orientation='P')
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(orientation, 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
42 super(orientation, '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.inline_columns.collect do |column|
239 query.inline_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.inline_columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding}
266 col_width_min = query.inline_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.inline_columns.map {|c|
269 word_width_max = query.inline_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.inline_columns, col_width, row_height, true)
373 max_height = issues_to_pdf_write_cells(pdf, query.inline_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.inline_columns, col_width, row_height, true)
379 issues_to_pdf_write_cells(pdf, query.inline_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, "L")
390 pdf = ITCPDF.new(current_language, "L")
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.inline_columns.empty?
410 unless query.inline_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 # use full width if the description is displayed
415 # use full width if the description is displayed
416 if table_width > 0 && query.has_column?(:description)
416 if table_width > 0 && query.has_column?(:description)
417 col_width = col_width.map {|w| w = w * (page_width - right_margin - 10 - col_id_width) / table_width}
417 col_width = col_width.map {|w| w = w * (page_width - right_margin - 10 - col_id_width) / table_width}
418 table_width = col_width.inject(0) {|s,v| s += v}
418 table_width = col_width.inject(0) {|s,v| s += v}
419 end
419 end
420
420
421 # title
421 # title
422 pdf.SetFontStyle('B',11)
422 pdf.SetFontStyle('B',11)
423 pdf.RDMCell(190,10, title)
423 pdf.RDMCell(190,10, title)
424 pdf.Ln
424 pdf.Ln
425 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
425 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
426 previous_group = false
426 previous_group = false
427 issue_list(issues) do |issue, level|
427 issue_list(issues) do |issue, level|
428 if query.grouped? &&
428 if query.grouped? &&
429 (group = query.group_by_column.value(issue)) != previous_group
429 (group = query.group_by_column.value(issue)) != previous_group
430 pdf.SetFontStyle('B',10)
430 pdf.SetFontStyle('B',10)
431 group_label = group.blank? ? 'None' : group.to_s
431 group_label = group.blank? ? 'None' : group.to_s.dup
432 group_label << " (#{query.issue_count_by_group[group]})"
432 group_label << " (#{query.issue_count_by_group[group]})"
433 pdf.Bookmark group_label, 0, -1
433 pdf.Bookmark group_label, 0, -1
434 pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L')
434 pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L')
435 pdf.SetFontStyle('',8)
435 pdf.SetFontStyle('',8)
436 previous_group = group
436 previous_group = group
437 end
437 end
438
438
439 # fetch row values
439 # fetch row values
440 col_values = fetch_row_values(issue, query, level)
440 col_values = fetch_row_values(issue, query, level)
441
441
442 # render it off-page to find the max height used
442 # render it off-page to find the max height used
443 base_x = pdf.GetX
443 base_x = pdf.GetX
444 base_y = pdf.GetY
444 base_y = pdf.GetY
445 pdf.SetY(2 * page_height)
445 pdf.SetY(2 * page_height)
446 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
446 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
447 pdf.SetXY(base_x, base_y)
447 pdf.SetXY(base_x, base_y)
448
448
449 # make new page if it doesn't fit on the current one
449 # make new page if it doesn't fit on the current one
450 space_left = page_height - base_y - bottom_margin
450 space_left = page_height - base_y - bottom_margin
451 if max_height > space_left
451 if max_height > space_left
452 pdf.AddPage("L")
452 pdf.AddPage("L")
453 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
453 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
454 base_x = pdf.GetX
454 base_x = pdf.GetX
455 base_y = pdf.GetY
455 base_y = pdf.GetY
456 end
456 end
457
457
458 # write the cells on page
458 # write the cells on page
459 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
459 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
460 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
460 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
461 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
461 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
462 pdf.SetY(base_y + max_height);
462 pdf.SetY(base_y + max_height);
463
463
464 if query.has_column?(:description) && issue.description?
464 if query.has_column?(:description) && issue.description?
465 pdf.SetX(10)
465 pdf.SetX(10)
466 pdf.SetAutoPageBreak(true, 20)
466 pdf.SetAutoPageBreak(true, 20)
467 pdf.RDMwriteHTMLCell(0, 5, 10, 0, issue.description.to_s, issue.attachments, "LRBT")
467 pdf.RDMwriteHTMLCell(0, 5, 10, 0, issue.description.to_s, issue.attachments, "LRBT")
468 pdf.SetAutoPageBreak(false)
468 pdf.SetAutoPageBreak(false)
469 end
469 end
470 end
470 end
471
471
472 if issues.size == Setting.issues_export_limit.to_i
472 if issues.size == Setting.issues_export_limit.to_i
473 pdf.SetFontStyle('B',10)
473 pdf.SetFontStyle('B',10)
474 pdf.RDMCell(0, row_height, '...')
474 pdf.RDMCell(0, row_height, '...')
475 end
475 end
476 pdf.Output
476 pdf.Output
477 end
477 end
478
478
479 # Renders MultiCells and returns the maximum height used
479 # Renders MultiCells and returns the maximum height used
480 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
480 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
481 row_height, head=false)
481 row_height, head=false)
482 base_y = pdf.GetY
482 base_y = pdf.GetY
483 max_height = row_height
483 max_height = row_height
484 col_values.each_with_index do |column, i|
484 col_values.each_with_index do |column, i|
485 col_x = pdf.GetX
485 col_x = pdf.GetX
486 if head == true
486 if head == true
487 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
487 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
488 else
488 else
489 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
489 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
490 end
490 end
491 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
491 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
492 pdf.SetXY(col_x + col_widths[i], base_y);
492 pdf.SetXY(col_x + col_widths[i], base_y);
493 end
493 end
494 return max_height
494 return max_height
495 end
495 end
496
496
497 # Draw lines to close the row (MultiCell border drawing in not uniform)
497 # Draw lines to close the row (MultiCell border drawing in not uniform)
498 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
498 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
499 id_width, col_widths)
499 id_width, col_widths)
500 col_x = top_x + id_width
500 col_x = top_x + id_width
501 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
501 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
502 col_widths.each do |width|
502 col_widths.each do |width|
503 col_x += width
503 col_x += width
504 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
504 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
505 end
505 end
506 pdf.Line(top_x, top_y, top_x, lower_y) # left border
506 pdf.Line(top_x, top_y, top_x, lower_y) # left border
507 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
507 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
508 end
508 end
509
509
510 # Returns a PDF string of a single issue
510 # Returns a PDF string of a single issue
511 def issue_to_pdf(issue, assoc={})
511 def issue_to_pdf(issue, assoc={})
512 pdf = ITCPDF.new(current_language)
512 pdf = ITCPDF.new(current_language)
513 pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}")
513 pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}")
514 pdf.alias_nb_pages
514 pdf.alias_nb_pages
515 pdf.footer_date = format_date(Date.today)
515 pdf.footer_date = format_date(Date.today)
516 pdf.AddPage
516 pdf.AddPage
517 pdf.SetFontStyle('B',11)
517 pdf.SetFontStyle('B',11)
518 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
518 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
519 pdf.RDMMultiCell(190, 5, buf)
519 pdf.RDMMultiCell(190, 5, buf)
520 pdf.SetFontStyle('',8)
520 pdf.SetFontStyle('',8)
521 base_x = pdf.GetX
521 base_x = pdf.GetX
522 i = 1
522 i = 1
523 issue.ancestors.visible.each do |ancestor|
523 issue.ancestors.visible.each do |ancestor|
524 pdf.SetX(base_x + i)
524 pdf.SetX(base_x + i)
525 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
525 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
526 pdf.RDMMultiCell(190 - i, 5, buf)
526 pdf.RDMMultiCell(190 - i, 5, buf)
527 i += 1 if i < 35
527 i += 1 if i < 35
528 end
528 end
529 pdf.SetFontStyle('B',11)
529 pdf.SetFontStyle('B',11)
530 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
530 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
531 pdf.SetFontStyle('',8)
531 pdf.SetFontStyle('',8)
532 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
532 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
533 pdf.Ln
533 pdf.Ln
534
534
535 left = []
535 left = []
536 left << [l(:field_status), issue.status]
536 left << [l(:field_status), issue.status]
537 left << [l(:field_priority), issue.priority]
537 left << [l(:field_priority), issue.priority]
538 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
538 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
539 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
539 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
540 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
540 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
541
541
542 right = []
542 right = []
543 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
543 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
544 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
544 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
545 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
545 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
546 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
546 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
547 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
547 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
548
548
549 rows = left.size > right.size ? left.size : right.size
549 rows = left.size > right.size ? left.size : right.size
550 while left.size < rows
550 while left.size < rows
551 left << nil
551 left << nil
552 end
552 end
553 while right.size < rows
553 while right.size < rows
554 right << nil
554 right << nil
555 end
555 end
556
556
557 half = (issue.custom_field_values.size / 2.0).ceil
557 half = (issue.custom_field_values.size / 2.0).ceil
558 issue.custom_field_values.each_with_index do |custom_value, i|
558 issue.custom_field_values.each_with_index do |custom_value, i|
559 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)]
559 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)]
560 end
560 end
561
561
562 rows = left.size > right.size ? left.size : right.size
562 rows = left.size > right.size ? left.size : right.size
563 rows.times do |i|
563 rows.times do |i|
564 item = left[i]
564 item = left[i]
565 pdf.SetFontStyle('B',9)
565 pdf.SetFontStyle('B',9)
566 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
566 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
567 pdf.SetFontStyle('',9)
567 pdf.SetFontStyle('',9)
568 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
568 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
569
569
570 item = right[i]
570 item = right[i]
571 pdf.SetFontStyle('B',9)
571 pdf.SetFontStyle('B',9)
572 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
572 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
573 pdf.SetFontStyle('',9)
573 pdf.SetFontStyle('',9)
574 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
574 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
575 pdf.Ln
575 pdf.Ln
576 end
576 end
577
577
578 pdf.SetFontStyle('B',9)
578 pdf.SetFontStyle('B',9)
579 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
579 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
580 pdf.SetFontStyle('',9)
580 pdf.SetFontStyle('',9)
581
581
582 # Set resize image scale
582 # Set resize image scale
583 pdf.SetImageScale(1.6)
583 pdf.SetImageScale(1.6)
584 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
584 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
585 issue.description.to_s, issue.attachments, "LRB")
585 issue.description.to_s, issue.attachments, "LRB")
586
586
587 unless issue.leaf?
587 unless issue.leaf?
588 # for CJK
588 # for CJK
589 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
589 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
590
590
591 pdf.SetFontStyle('B',9)
591 pdf.SetFontStyle('B',9)
592 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
592 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
593 pdf.Ln
593 pdf.Ln
594 issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
594 issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
595 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
595 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
596 :length => truncate_length)
596 :length => truncate_length)
597 level = 10 if level >= 10
597 level = 10 if level >= 10
598 pdf.SetFontStyle('',8)
598 pdf.SetFontStyle('',8)
599 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
599 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
600 pdf.SetFontStyle('B',8)
600 pdf.SetFontStyle('B',8)
601 pdf.RDMCell(20,5, child.status.to_s, "R")
601 pdf.RDMCell(20,5, child.status.to_s, "R")
602 pdf.Ln
602 pdf.Ln
603 end
603 end
604 end
604 end
605
605
606 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
606 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
607 unless relations.empty?
607 unless relations.empty?
608 # for CJK
608 # for CJK
609 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
609 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
610
610
611 pdf.SetFontStyle('B',9)
611 pdf.SetFontStyle('B',9)
612 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
612 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
613 pdf.Ln
613 pdf.Ln
614 relations.each do |relation|
614 relations.each do |relation|
615 buf = ""
615 buf = ""
616 buf += "#{l(relation.label_for(issue))} "
616 buf += "#{l(relation.label_for(issue))} "
617 if relation.delay && relation.delay != 0
617 if relation.delay && relation.delay != 0
618 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
618 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
619 end
619 end
620 if Setting.cross_project_issue_relations?
620 if Setting.cross_project_issue_relations?
621 buf += "#{relation.other_issue(issue).project} - "
621 buf += "#{relation.other_issue(issue).project} - "
622 end
622 end
623 buf += "#{relation.other_issue(issue).tracker}" +
623 buf += "#{relation.other_issue(issue).tracker}" +
624 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
624 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
625 buf = truncate(buf, :length => truncate_length)
625 buf = truncate(buf, :length => truncate_length)
626 pdf.SetFontStyle('', 8)
626 pdf.SetFontStyle('', 8)
627 pdf.RDMCell(35+155-60, 5, buf, "L")
627 pdf.RDMCell(35+155-60, 5, buf, "L")
628 pdf.SetFontStyle('B',8)
628 pdf.SetFontStyle('B',8)
629 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
629 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
630 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
630 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
631 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
631 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
632 pdf.Ln
632 pdf.Ln
633 end
633 end
634 end
634 end
635 pdf.RDMCell(190,5, "", "T")
635 pdf.RDMCell(190,5, "", "T")
636 pdf.Ln
636 pdf.Ln
637
637
638 if issue.changesets.any? &&
638 if issue.changesets.any? &&
639 User.current.allowed_to?(:view_changesets, issue.project)
639 User.current.allowed_to?(:view_changesets, issue.project)
640 pdf.SetFontStyle('B',9)
640 pdf.SetFontStyle('B',9)
641 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
641 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
642 pdf.Ln
642 pdf.Ln
643 for changeset in issue.changesets
643 for changeset in issue.changesets
644 pdf.SetFontStyle('B',8)
644 pdf.SetFontStyle('B',8)
645 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
645 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
646 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
646 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
647 pdf.RDMCell(190, 5, csstr)
647 pdf.RDMCell(190, 5, csstr)
648 pdf.Ln
648 pdf.Ln
649 unless changeset.comments.blank?
649 unless changeset.comments.blank?
650 pdf.SetFontStyle('',8)
650 pdf.SetFontStyle('',8)
651 pdf.RDMwriteHTMLCell(190,5,0,0,
651 pdf.RDMwriteHTMLCell(190,5,0,0,
652 changeset.comments.to_s, issue.attachments, "")
652 changeset.comments.to_s, issue.attachments, "")
653 end
653 end
654 pdf.Ln
654 pdf.Ln
655 end
655 end
656 end
656 end
657
657
658 if assoc[:journals].present?
658 if assoc[:journals].present?
659 pdf.SetFontStyle('B',9)
659 pdf.SetFontStyle('B',9)
660 pdf.RDMCell(190,5, l(:label_history), "B")
660 pdf.RDMCell(190,5, l(:label_history), "B")
661 pdf.Ln
661 pdf.Ln
662 assoc[:journals].each do |journal|
662 assoc[:journals].each do |journal|
663 pdf.SetFontStyle('B',8)
663 pdf.SetFontStyle('B',8)
664 title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
664 title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
665 title << " (#{l(:field_private_notes)})" if journal.private_notes?
665 title << " (#{l(:field_private_notes)})" if journal.private_notes?
666 pdf.RDMCell(190,5, title)
666 pdf.RDMCell(190,5, title)
667 pdf.Ln
667 pdf.Ln
668 pdf.SetFontStyle('I',8)
668 pdf.SetFontStyle('I',8)
669 details_to_strings(journal.details, true).each do |string|
669 details_to_strings(journal.details, true).each do |string|
670 pdf.RDMMultiCell(190,5, "- " + string)
670 pdf.RDMMultiCell(190,5, "- " + string)
671 end
671 end
672 if journal.notes?
672 if journal.notes?
673 pdf.Ln unless journal.details.empty?
673 pdf.Ln unless journal.details.empty?
674 pdf.SetFontStyle('',8)
674 pdf.SetFontStyle('',8)
675 pdf.RDMwriteHTMLCell(190,5,0,0,
675 pdf.RDMwriteHTMLCell(190,5,0,0,
676 journal.notes.to_s, issue.attachments, "")
676 journal.notes.to_s, issue.attachments, "")
677 end
677 end
678 pdf.Ln
678 pdf.Ln
679 end
679 end
680 end
680 end
681
681
682 if issue.attachments.any?
682 if issue.attachments.any?
683 pdf.SetFontStyle('B',9)
683 pdf.SetFontStyle('B',9)
684 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
684 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
685 pdf.Ln
685 pdf.Ln
686 for attachment in issue.attachments
686 for attachment in issue.attachments
687 pdf.SetFontStyle('',8)
687 pdf.SetFontStyle('',8)
688 pdf.RDMCell(80,5, attachment.filename)
688 pdf.RDMCell(80,5, attachment.filename)
689 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
689 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
690 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
690 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
691 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
691 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
692 pdf.Ln
692 pdf.Ln
693 end
693 end
694 end
694 end
695 pdf.Output
695 pdf.Output
696 end
696 end
697
697
698 # Returns a PDF string of a set of wiki pages
698 # Returns a PDF string of a set of wiki pages
699 def wiki_pages_to_pdf(pages, project)
699 def wiki_pages_to_pdf(pages, project)
700 pdf = ITCPDF.new(current_language)
700 pdf = ITCPDF.new(current_language)
701 pdf.SetTitle(project.name)
701 pdf.SetTitle(project.name)
702 pdf.alias_nb_pages
702 pdf.alias_nb_pages
703 pdf.footer_date = format_date(Date.today)
703 pdf.footer_date = format_date(Date.today)
704 pdf.AddPage
704 pdf.AddPage
705 pdf.SetFontStyle('B',11)
705 pdf.SetFontStyle('B',11)
706 pdf.RDMMultiCell(190,5, project.name)
706 pdf.RDMMultiCell(190,5, project.name)
707 pdf.Ln
707 pdf.Ln
708 # Set resize image scale
708 # Set resize image scale
709 pdf.SetImageScale(1.6)
709 pdf.SetImageScale(1.6)
710 pdf.SetFontStyle('',9)
710 pdf.SetFontStyle('',9)
711 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
711 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
712 pdf.Output
712 pdf.Output
713 end
713 end
714
714
715 # Returns a PDF string of a single wiki page
715 # Returns a PDF string of a single wiki page
716 def wiki_page_to_pdf(page, project)
716 def wiki_page_to_pdf(page, project)
717 pdf = ITCPDF.new(current_language)
717 pdf = ITCPDF.new(current_language)
718 pdf.SetTitle("#{project} - #{page.title}")
718 pdf.SetTitle("#{project} - #{page.title}")
719 pdf.alias_nb_pages
719 pdf.alias_nb_pages
720 pdf.footer_date = format_date(Date.today)
720 pdf.footer_date = format_date(Date.today)
721 pdf.AddPage
721 pdf.AddPage
722 pdf.SetFontStyle('B',11)
722 pdf.SetFontStyle('B',11)
723 pdf.RDMMultiCell(190,5,
723 pdf.RDMMultiCell(190,5,
724 "#{project} - #{page.title} - # #{page.content.version}")
724 "#{project} - #{page.title} - # #{page.content.version}")
725 pdf.Ln
725 pdf.Ln
726 # Set resize image scale
726 # Set resize image scale
727 pdf.SetImageScale(1.6)
727 pdf.SetImageScale(1.6)
728 pdf.SetFontStyle('',9)
728 pdf.SetFontStyle('',9)
729 write_wiki_page(pdf, page)
729 write_wiki_page(pdf, page)
730 pdf.Output
730 pdf.Output
731 end
731 end
732
732
733 def write_page_hierarchy(pdf, pages, node=nil, level=0)
733 def write_page_hierarchy(pdf, pages, node=nil, level=0)
734 if pages[node]
734 if pages[node]
735 pages[node].each do |page|
735 pages[node].each do |page|
736 if @new_page
736 if @new_page
737 pdf.AddPage
737 pdf.AddPage
738 else
738 else
739 @new_page = true
739 @new_page = true
740 end
740 end
741 pdf.Bookmark page.title, level
741 pdf.Bookmark page.title, level
742 write_wiki_page(pdf, page)
742 write_wiki_page(pdf, page)
743 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
743 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
744 end
744 end
745 end
745 end
746 end
746 end
747
747
748 def write_wiki_page(pdf, page)
748 def write_wiki_page(pdf, page)
749 pdf.RDMwriteHTMLCell(190,5,0,0,
749 pdf.RDMwriteHTMLCell(190,5,0,0,
750 page.content.text.to_s, page.attachments, 0)
750 page.content.text.to_s, page.attachments, 0)
751 if page.attachments.any?
751 if page.attachments.any?
752 pdf.Ln
752 pdf.Ln
753 pdf.SetFontStyle('B',9)
753 pdf.SetFontStyle('B',9)
754 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
754 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
755 pdf.Ln
755 pdf.Ln
756 for attachment in page.attachments
756 for attachment in page.attachments
757 pdf.SetFontStyle('',8)
757 pdf.SetFontStyle('',8)
758 pdf.RDMCell(80,5, attachment.filename)
758 pdf.RDMCell(80,5, attachment.filename)
759 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
759 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
760 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
760 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
761 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
761 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
762 pdf.Ln
762 pdf.Ln
763 end
763 end
764 end
764 end
765 end
765 end
766
766
767 class RDMPdfEncoding
767 class RDMPdfEncoding
768 def self.rdm_from_utf8(txt, encoding)
768 def self.rdm_from_utf8(txt, encoding)
769 txt ||= ''
769 txt ||= ''
770 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
770 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
771 if txt.respond_to?(:force_encoding)
771 if txt.respond_to?(:force_encoding)
772 txt.force_encoding('ASCII-8BIT')
772 txt.force_encoding('ASCII-8BIT')
773 end
773 end
774 txt
774 txt
775 end
775 end
776
776
777 def self.attach(attachments, filename, encoding)
777 def self.attach(attachments, filename, encoding)
778 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
778 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
779 atta = nil
779 atta = nil
780 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
780 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
781 atta = Attachment.latest_attach(attachments, filename_utf8)
781 atta = Attachment.latest_attach(attachments, filename_utf8)
782 end
782 end
783 if atta && atta.readable? && atta.visible?
783 if atta && atta.readable? && atta.visible?
784 return atta
784 return atta
785 else
785 else
786 return nil
786 return nil
787 end
787 end
788 end
788 end
789 end
789 end
790 end
790 end
791 end
791 end
792 end
792 end
General Comments 0
You need to be logged in to leave comments. Login now