comparison lib/redmine/export/pdf.rb @ 511:107d36338b70 live

Merge from branch "cannam"
author Chris Cannam
date Thu, 14 Jul 2011 10:43:07 +0100
parents 0c939c159af4
children cbb26bc654de
comparison
equal deleted inserted replaced
451:a9f6345cb43d 511:107d36338b70
1 # encoding: utf-8 1 # encoding: utf-8
2 # 2 #
3 # Redmine - project management software 3 # Redmine - project management software
4 # Copyright (C) 2006-2009 Jean-Philippe Lang 4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 # 5 #
6 # This program is free software; you can redistribute it and/or 6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License 7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2 8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version. 9 # of the License, or (at your option) any later version.
10 # 10 #
11 # This program is distributed in the hope that it will be useful, 11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details. 14 # GNU General Public License for more details.
15 # 15 #
16 # You should have received a copy of the GNU General Public License 16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software 17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 require 'iconv' 20 require 'iconv'
21 require 'rfpdf/fpdf' 21 require 'rfpdf/fpdf'
22 require 'rfpdf/chinese' 22 require 'fpdf/chinese'
23 require 'fpdf/japanese'
24 require 'fpdf/korean'
23 25
24 module Redmine 26 module Redmine
25 module Export 27 module Export
26 module PDF 28 module PDF
27 include ActionView::Helpers::TextHelper 29 include ActionView::Helpers::TextHelper
28 include ActionView::Helpers::NumberHelper 30 include ActionView::Helpers::NumberHelper
29 31
30 class IFPDF < FPDF 32 class ITCPDF < TCPDF
31 include Redmine::I18n 33 include Redmine::I18n
32 attr_accessor :footer_date 34 attr_accessor :footer_date
33 35
34 def initialize(lang) 36 def initialize(lang)
35 super()
36 set_language_if_valid lang 37 set_language_if_valid lang
38 pdf_encoding = l(:general_pdf_encoding).upcase
39 if RUBY_VERSION < '1.9'
40 @ic = Iconv.new(pdf_encoding, 'UTF-8')
41 end
42 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
37 case current_language.to_s.downcase 43 case current_language.to_s.downcase
38 when 'ko' 44 when 'vi'
39 extend(PDF_Korean) 45 @font_for_content = 'DejaVuSans'
40 AddUHCFont() 46 @font_for_footer = 'DejaVuSans'
41 @font_for_content = 'UHC'
42 @font_for_footer = 'UHC'
43 when 'ja'
44 extend(PDF_Japanese)
45 AddSJISFont()
46 @font_for_content = 'SJIS'
47 @font_for_footer = 'SJIS'
48 when 'zh'
49 extend(PDF_Chinese)
50 AddGBFont()
51 @font_for_content = 'GB'
52 @font_for_footer = 'GB'
53 when 'zh-tw'
54 extend(PDF_Chinese)
55 AddBig5Font()
56 @font_for_content = 'Big5'
57 @font_for_footer = 'Big5'
58 else 47 else
59 @font_for_content = 'Arial' 48 case pdf_encoding
60 @font_for_footer = 'Helvetica' 49 when 'UTF-8'
50 @font_for_content = 'FreeSans'
51 @font_for_footer = 'FreeSans'
52 when 'CP949'
53 extend(PDF_Korean)
54 AddUHCFont()
55 @font_for_content = 'UHC'
56 @font_for_footer = 'UHC'
57 when 'CP932', 'SJIS', 'SHIFT_JIS'
58 extend(PDF_Japanese)
59 AddSJISFont()
60 @font_for_content = 'SJIS'
61 @font_for_footer = 'SJIS'
62 when 'GB18030'
63 extend(PDF_Chinese)
64 AddGBFont()
65 @font_for_content = 'GB'
66 @font_for_footer = 'GB'
67 when 'BIG5'
68 extend(PDF_Chinese)
69 AddBig5Font()
70 @font_for_content = 'Big5'
71 @font_for_footer = 'Big5'
72 else
73 @font_for_content = 'Arial'
74 @font_for_footer = 'Helvetica'
75 end
61 end 76 end
62 SetCreator(Redmine::Info.app_name) 77 SetCreator(Redmine::Info.app_name)
63 SetFont(@font_for_content) 78 SetFont(@font_for_content)
64 end 79 end
65 80
66 def SetFontStyle(style, size) 81 def SetFontStyle(style, size)
67 SetFont(@font_for_content, style, size) 82 SetFont(@font_for_content, style, size)
68 end 83 end
69 84
70 def SetTitle(txt) 85 def SetTitle(txt)
71 txt = begin 86 txt = begin
72 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) 87 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
73 hextxt = "<FEFF" # FEFF is BOM 88 hextxt = "<FEFF" # FEFF is BOM
74 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join 89 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
76 rescue 91 rescue
77 txt 92 txt
78 end || '' 93 end || ''
79 super(txt) 94 super(txt)
80 end 95 end
81 96
82 def textstring(s) 97 def textstring(s)
83 # Format a text string 98 # Format a text string
84 if s =~ /^</ # This means the string is hex-dumped. 99 if s =~ /^</ # This means the string is hex-dumped.
85 return s 100 return s
86 else 101 else
87 return '('+escape(s)+')' 102 return '('+escape(s)+')'
88 end 103 end
89 end 104 end
90 105
91 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='') 106 def fix_text_encoding(txt)
92 @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8') 107 RDMPdfEncoding::rdm_pdf_iconv(@ic, txt)
93 # these quotation marks are not correctly rendered in the pdf 108 end
94 txt = txt.gsub(/[“�]/, '"') if txt 109
95 txt = begin 110 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
96 # 0x5c char handling 111 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
97 txtar = txt.split('\\') 112 end
98 txtar << '' if txt[-1] == ?\\ 113
99 txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\") 114 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
100 rescue 115 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
101 txt 116 end
102 end || '' 117
103 super w,h,txt,border,ln,align,fill,link
104 end
105
106 def Footer 118 def Footer
107 SetFont(@font_for_footer, 'I', 8) 119 SetFont(@font_for_footer, 'I', 8)
108 SetY(-15) 120 SetY(-15)
109 SetX(15) 121 SetX(15)
110 Cell(0, 5, @footer_date, 0, 0, 'L') 122 RDMCell(0, 5, @footer_date, 0, 0, 'L')
111 SetY(-15) 123 SetY(-15)
112 SetX(-30) 124 SetX(-30)
113 Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') 125 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
114 end 126 end
115 end 127 end
116 128
117 # Returns a PDF string of a list of issues 129 # Returns a PDF string of a list of issues
118 def issues_to_pdf(issues, project, query) 130 def issues_to_pdf(issues, project, query)
119 pdf = IFPDF.new(current_language) 131 pdf = ITCPDF.new(current_language)
120 title = query.new_record? ? l(:label_issue_plural) : query.name 132 title = query.new_record? ? l(:label_issue_plural) : query.name
121 title = "#{project} - #{title}" if project 133 title = "#{project} - #{title}" if project
122 pdf.SetTitle(title) 134 pdf.SetTitle(title)
123 pdf.AliasNbPages 135 pdf.alias_nb_pages
124 pdf.footer_date = format_date(Date.today) 136 pdf.footer_date = format_date(Date.today)
137 pdf.SetAutoPageBreak(false)
125 pdf.AddPage("L") 138 pdf.AddPage("L")
126 139
127 row_height = 6 140 # Landscape A4 = 210 x 297 mm
141 page_height = 210
142 page_width = 297
143 right_margin = 10
144 bottom_margin = 20
145 col_id_width = 10
146 row_height = 5
147
148 # column widths
149 table_width = page_width - right_margin - 10 # fixed left margin
128 col_width = [] 150 col_width = []
129 unless query.columns.empty? 151 unless query.columns.empty?
130 col_width = query.columns.collect {|column| column.name == :subject ? 4.0 : 1.0 } 152 col_width = query.columns.collect do |c|
131 ratio = 262.0 / col_width.inject(0) {|s,w| s += w} 153 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) && ['string', 'text'].include?(c.custom_field.field_format)))? 4.0 : 1.0
154 end
155 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
132 col_width = col_width.collect {|w| w * ratio} 156 col_width = col_width.collect {|w| w * ratio}
133 end 157 end
134 158
135 # title 159 # title
136 pdf.SetFontStyle('B',11) 160 pdf.SetFontStyle('B',11)
137 pdf.Cell(190,10, title) 161 pdf.RDMCell(190,10, title)
138 pdf.Ln 162 pdf.Ln
139 163
140 # headers 164 # headers
141 pdf.SetFontStyle('B',8) 165 pdf.SetFontStyle('B',8)
142 pdf.SetFillColor(230, 230, 230) 166 pdf.SetFillColor(230, 230, 230)
143 pdf.Cell(15, row_height, "#", 1, 0, 'L', 1) 167
144 query.columns.each_with_index do |column, i| 168 # render it background to find the max height used
145 pdf.Cell(col_width[i], row_height, column.caption, 1, 0, 'L', 1) 169 base_x = pdf.GetX
146 end 170 base_y = pdf.GetY
147 pdf.Ln 171 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
148 172 pdf.Rect(base_x, base_y, table_width, max_height, 'FD');
173 pdf.SetXY(base_x, base_y);
174
175 # write the cells on page
176 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
177 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
178 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
179 pdf.SetY(base_y + max_height);
180
149 # rows 181 # rows
150 pdf.SetFontStyle('',8) 182 pdf.SetFontStyle('',8)
151 pdf.SetFillColor(255, 255, 255) 183 pdf.SetFillColor(255, 255, 255)
152 previous_group = false 184 previous_group = false
153 issues.each do |issue| 185 issues.each do |issue|
154 if query.grouped? && (group = query.group_by_column.value(issue)) != previous_group 186 if query.grouped? &&
187 (group = query.group_by_column.value(issue)) != previous_group
155 pdf.SetFontStyle('B',9) 188 pdf.SetFontStyle('B',9)
156 pdf.Cell(277, row_height, 189 pdf.RDMCell(277, row_height,
157 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})", 190 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
158 1, 1, 'L') 191 1, 1, 'L')
159 pdf.SetFontStyle('',8) 192 pdf.SetFontStyle('',8)
160 previous_group = group 193 previous_group = group
161 end 194 end
162 pdf.Cell(15, row_height, issue.id.to_s, 1, 0, 'L', 1) 195 # fetch all the row values
163 query.columns.each_with_index do |column, i| 196 col_values = query.columns.collect do |column|
164 s = if column.is_a?(QueryCustomFieldColumn) 197 s = if column.is_a?(QueryCustomFieldColumn)
165 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id} 198 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
166 show_value(cv) 199 show_value(cv)
167 else 200 else
168 value = issue.send(column.name) 201 value = issue.send(column.name)
172 format_time(value) 205 format_time(value)
173 else 206 else
174 value 207 value
175 end 208 end
176 end 209 end
177 pdf.Cell(col_width[i], row_height, s.to_s, 1, 0, 'L', 1) 210 s.to_s
178 end 211 end
179 pdf.Ln 212
180 end 213 # render it off-page to find the max height used
214 base_x = pdf.GetX
215 base_y = pdf.GetY
216 pdf.SetY(2 * page_height)
217 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
218 pdf.SetXY(base_x, base_y)
219
220 # make new page if it doesn't fit on the current one
221 space_left = page_height - base_y - bottom_margin
222 if max_height > space_left
223 pdf.AddPage("L")
224 base_x = pdf.GetX
225 base_y = pdf.GetY
226 end
227
228 # write the cells on page
229 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
230 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
231 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
232 pdf.SetY(base_y + max_height);
233 end
234
181 if issues.size == Setting.issues_export_limit.to_i 235 if issues.size == Setting.issues_export_limit.to_i
182 pdf.SetFontStyle('B',10) 236 pdf.SetFontStyle('B',10)
183 pdf.Cell(0, row_height, '...') 237 pdf.RDMCell(0, row_height, '...')
184 end 238 end
185 pdf.Output 239 pdf.Output
240 end
241
242 # Renders MultiCells and returns the maximum height used
243 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
244 row_height, head=false)
245 base_y = pdf.GetY
246 max_height = row_height
247 col_values.each_with_index do |column, i|
248 col_x = pdf.GetX
249 if head == true
250 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
251 else
252 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
253 end
254 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
255 pdf.SetXY(col_x + col_widths[i], base_y);
256 end
257 return max_height
258 end
259
260 # Draw lines to close the row (MultiCell border drawing in not uniform)
261 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
262 id_width, col_widths)
263 col_x = top_x + id_width
264 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
265 col_widths.each do |width|
266 col_x += width
267 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
268 end
269 pdf.Line(top_x, top_y, top_x, lower_y) # left border
270 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
186 end 271 end
187 272
188 # Returns a PDF string of a single issue 273 # Returns a PDF string of a single issue
189 def issue_to_pdf(issue) 274 def issue_to_pdf(issue)
190 pdf = IFPDF.new(current_language) 275 pdf = ITCPDF.new(current_language)
191 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}") 276 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
192 pdf.AliasNbPages 277 pdf.alias_nb_pages
193 pdf.footer_date = format_date(Date.today) 278 pdf.footer_date = format_date(Date.today)
194 pdf.AddPage 279 pdf.AddPage
195 280 pdf.SetFontStyle('B',11)
196 pdf.SetFontStyle('B',11) 281 pdf.RDMMultiCell(190,5,
197 pdf.Cell(190,10, "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}") 282 "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
198 pdf.Ln 283 pdf.Ln
199 284
200 y0 = pdf.GetY 285 pdf.SetFontStyle('B',9)
201 286 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
202 pdf.SetFontStyle('B',9) 287 pdf.SetFontStyle('',9)
203 pdf.Cell(35,5, l(:field_status) + ":","LT") 288 pdf.RDMCell(60,5, issue.status.to_s,"RT")
204 pdf.SetFontStyle('',9) 289 pdf.SetFontStyle('B',9)
205 pdf.Cell(60,5, issue.status.to_s,"RT") 290 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
206 pdf.SetFontStyle('B',9) 291 pdf.SetFontStyle('',9)
207 pdf.Cell(35,5, l(:field_priority) + ":","LT") 292 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
208 pdf.SetFontStyle('',9) 293 pdf.Ln
209 pdf.Cell(60,5, issue.priority.to_s,"RT") 294
210 pdf.Ln 295 pdf.SetFontStyle('B',9)
211 296 pdf.RDMCell(35,5, l(:field_author) + ":","L")
212 pdf.SetFontStyle('B',9) 297 pdf.SetFontStyle('',9)
213 pdf.Cell(35,5, l(:field_author) + ":","L") 298 pdf.RDMCell(60,5, issue.author.to_s,"R")
214 pdf.SetFontStyle('',9) 299 pdf.SetFontStyle('B',9)
215 pdf.Cell(60,5, issue.author.to_s,"R") 300 pdf.RDMCell(35,5, l(:field_category) + ":","L")
216 pdf.SetFontStyle('B',9) 301 pdf.SetFontStyle('',9)
217 pdf.Cell(35,5, l(:field_category) + ":","L") 302 pdf.RDMCell(60,5, issue.category.to_s,"R")
218 pdf.SetFontStyle('',9) 303 pdf.Ln
219 pdf.Cell(60,5, issue.category.to_s,"R") 304
220 pdf.Ln 305 pdf.SetFontStyle('B',9)
221 306 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
222 pdf.SetFontStyle('B',9) 307 pdf.SetFontStyle('',9)
223 pdf.Cell(35,5, l(:field_created_on) + ":","L") 308 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
224 pdf.SetFontStyle('',9) 309 pdf.SetFontStyle('B',9)
225 pdf.Cell(60,5, format_date(issue.created_on),"R") 310 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
226 pdf.SetFontStyle('B',9) 311 pdf.SetFontStyle('',9)
227 pdf.Cell(35,5, l(:field_assigned_to) + ":","L") 312 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
228 pdf.SetFontStyle('',9) 313 pdf.Ln
229 pdf.Cell(60,5, issue.assigned_to.to_s,"R") 314
230 pdf.Ln 315 pdf.SetFontStyle('B',9)
231 316 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
232 pdf.SetFontStyle('B',9) 317 pdf.SetFontStyle('',9)
233 pdf.Cell(35,5, l(:field_updated_on) + ":","LB") 318 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
234 pdf.SetFontStyle('',9) 319 pdf.SetFontStyle('B',9)
235 pdf.Cell(60,5, format_date(issue.updated_on),"RB") 320 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
236 pdf.SetFontStyle('B',9) 321 pdf.SetFontStyle('',9)
237 pdf.Cell(35,5, l(:field_due_date) + ":","LB") 322 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
238 pdf.SetFontStyle('',9) 323 pdf.Ln
239 pdf.Cell(60,5, format_date(issue.due_date),"RB") 324
240 pdf.Ln
241
242 for custom_value in issue.custom_field_values 325 for custom_value in issue.custom_field_values
243 pdf.SetFontStyle('B',9) 326 pdf.SetFontStyle('B',9)
244 pdf.Cell(35,5, custom_value.custom_field.name + ":","L") 327 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
245 pdf.SetFontStyle('',9) 328 pdf.SetFontStyle('',9)
246 pdf.MultiCell(155,5, (show_value custom_value),"R") 329 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
247 end 330 end
248 331
249 pdf.SetFontStyle('B',9) 332 y0 = pdf.GetY
250 pdf.Cell(35,5, l(:field_subject) + ":","LTB") 333
251 pdf.SetFontStyle('',9) 334 pdf.SetFontStyle('B',9)
252 pdf.Cell(155,5, issue.subject,"RTB") 335 pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
253 pdf.Ln 336 pdf.SetFontStyle('',9)
254 337 pdf.RDMMultiCell(155,5, issue.subject,"RT")
255 pdf.SetFontStyle('B',9)
256 pdf.Cell(35,5, l(:field_description) + ":")
257 pdf.SetFontStyle('',9)
258 pdf.MultiCell(155,5, issue.description,"BR")
259
260 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY) 338 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
261 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY) 339
262 pdf.Ln 340 pdf.SetFontStyle('B',9)
263 341 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
264 if issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project) 342 pdf.SetFontStyle('',9)
343 pdf.RDMMultiCell(35+155, 5, issue.description.to_s, "LRB")
344 pdf.Ln
345
346 if issue.changesets.any? &&
347 User.current.allowed_to?(:view_changesets, issue.project)
265 pdf.SetFontStyle('B',9) 348 pdf.SetFontStyle('B',9)
266 pdf.Cell(190,5, l(:label_associated_revisions), "B") 349 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
267 pdf.Ln 350 pdf.Ln
268 for changeset in issue.changesets 351 for changeset in issue.changesets
269 pdf.SetFontStyle('B',8) 352 pdf.SetFontStyle('B',8)
270 pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.author.to_s) 353 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
354 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
355 pdf.RDMCell(190, 5, csstr)
271 pdf.Ln 356 pdf.Ln
272 unless changeset.comments.blank? 357 unless changeset.comments.blank?
273 pdf.SetFontStyle('',8) 358 pdf.SetFontStyle('',8)
274 pdf.MultiCell(190,5, changeset.comments) 359 pdf.RDMMultiCell(190,5, changeset.comments.to_s)
275 end 360 end
276 pdf.Ln 361 pdf.Ln
277 end 362 end
278 end 363 end
279 364
280 pdf.SetFontStyle('B',9) 365 pdf.SetFontStyle('B',9)
281 pdf.Cell(190,5, l(:label_history), "B") 366 pdf.RDMCell(190,5, l(:label_history), "B")
282 pdf.Ln 367 pdf.Ln
283 for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") 368 for journal in issue.journals.find(
369 :all, :include => [:user, :details],
370 :order => "#{Journal.table_name}.created_on ASC")
284 pdf.SetFontStyle('B',8) 371 pdf.SetFontStyle('B',8)
285 pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name) 372 pdf.RDMCell(190,5,
373 format_time(journal.created_on) + " - " + journal.user.name)
286 pdf.Ln 374 pdf.Ln
287 pdf.SetFontStyle('I',8) 375 pdf.SetFontStyle('I',8)
288 for detail in journal.details 376 for detail in journal.details
289 pdf.Cell(190,5, "- " + show_detail(detail, true)) 377 pdf.RDMMultiCell(190,5, "- " + show_detail(detail, true))
290 pdf.Ln
291 end 378 end
292 if journal.notes? 379 if journal.notes?
380 pdf.Ln unless journal.details.empty?
293 pdf.SetFontStyle('',8) 381 pdf.SetFontStyle('',8)
294 pdf.MultiCell(190,5, journal.notes) 382 pdf.RDMMultiCell(190,5, journal.notes.to_s)
295 end 383 end
296 pdf.Ln 384 pdf.Ln
297 end 385 end
298 386
299 if issue.attachments.any? 387 if issue.attachments.any?
300 pdf.SetFontStyle('B',9) 388 pdf.SetFontStyle('B',9)
301 pdf.Cell(190,5, l(:label_attachment_plural), "B") 389 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
302 pdf.Ln 390 pdf.Ln
303 for attachment in issue.attachments 391 for attachment in issue.attachments
304 pdf.SetFontStyle('',8) 392 pdf.SetFontStyle('',8)
305 pdf.Cell(80,5, attachment.filename) 393 pdf.RDMCell(80,5, attachment.filename)
306 pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R") 394 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
307 pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R") 395 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
308 pdf.Cell(65,5, attachment.author.name,0,0,"R") 396 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
309 pdf.Ln 397 pdf.Ln
310 end 398 end
311 end 399 end
312 pdf.Output 400 pdf.Output
313 end 401 end
314 402
403 class RDMPdfEncoding
404 include Redmine::I18n
405 def self.rdm_pdf_iconv(ic, txt)
406 txt ||= ''
407 if txt.respond_to?(:force_encoding)
408 txt.force_encoding('UTF-8')
409 if l(:general_pdf_encoding).upcase != 'UTF-8'
410 txt = txt.encode(l(:general_pdf_encoding), :invalid => :replace,
411 :undef => :replace, :replace => '?')
412 else
413 txt = Redmine::CodesetUtil.replace_invalid_utf8(txt)
414 end
415 txt.force_encoding('ASCII-8BIT')
416 elsif RUBY_PLATFORM == 'java'
417 begin
418 ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
419 txt = ic.iconv(txt)
420 rescue
421 txt = txt.gsub(%r{[^\r\n\t\x20-\x7e]}, '?')
422 end
423 else
424 ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
425 txtar = ""
426 begin
427 txtar += ic.iconv(txt)
428 rescue Iconv::IllegalSequence
429 txtar += $!.success
430 txt = '?' + $!.failed[1,$!.failed.length]
431 retry
432 rescue
433 txtar += $!.success
434 end
435 txt = txtar
436 end
437 txt
438 end
439 end
315 end 440 end
316 end 441 end
317 end 442 end