comparison lib/redmine/export/pdf.rb @ 441:cbce1fd3b1b7 redmine-1.2

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