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