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