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