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