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