##// END OF EJS Templates
pdf: lib: prepare to use rfpdf plug-in rmagick feature (#3261)...
Toshi MARUYAMA -
r7794:81bbb8f9b855
parent child
Show More
@@ -1,535 +1,538
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require 'iconv'
21 21 require 'fpdf/chinese'
22 22 require 'fpdf/japanese'
23 23 require 'fpdf/korean'
24 require 'core/rmagick'
24 25
25 26 module Redmine
26 27 module Export
27 28 module PDF
28 29 include ActionView::Helpers::TextHelper
29 30 include ActionView::Helpers::NumberHelper
30 31 include IssuesHelper
31 32
32 33 class ITCPDF < TCPDF
33 34 include Redmine::I18n
34 35 attr_accessor :footer_date
35 36
36 37 def initialize(lang)
38 @@k_path_cache = Rails.root.join('tmp', 'pdf')
39 FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache)
37 40 set_language_if_valid lang
38 41 pdf_encoding = l(:general_pdf_encoding).upcase
39 42 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
40 43 case current_language.to_s.downcase
41 44 when 'vi'
42 45 @font_for_content = 'DejaVuSans'
43 46 @font_for_footer = 'DejaVuSans'
44 47 else
45 48 case pdf_encoding
46 49 when 'UTF-8'
47 50 @font_for_content = 'FreeSans'
48 51 @font_for_footer = 'FreeSans'
49 52 when 'CP949'
50 53 extend(PDF_Korean)
51 54 AddUHCFont()
52 55 @font_for_content = 'UHC'
53 56 @font_for_footer = 'UHC'
54 57 when 'CP932', 'SJIS', 'SHIFT_JIS'
55 58 extend(PDF_Japanese)
56 59 AddSJISFont()
57 60 @font_for_content = 'SJIS'
58 61 @font_for_footer = 'SJIS'
59 62 when 'GB18030'
60 63 extend(PDF_Chinese)
61 64 AddGBFont()
62 65 @font_for_content = 'GB'
63 66 @font_for_footer = 'GB'
64 67 when 'BIG5'
65 68 extend(PDF_Chinese)
66 69 AddBig5Font()
67 70 @font_for_content = 'Big5'
68 71 @font_for_footer = 'Big5'
69 72 else
70 73 @font_for_content = 'Arial'
71 74 @font_for_footer = 'Helvetica'
72 75 end
73 76 end
74 77 SetCreator(Redmine::Info.app_name)
75 78 SetFont(@font_for_content)
76 79 end
77 80
78 81 def SetFontStyle(style, size)
79 82 SetFont(@font_for_content, style, size)
80 83 end
81 84
82 85 def SetTitle(txt)
83 86 txt = begin
84 87 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
85 88 hextxt = "<FEFF" # FEFF is BOM
86 89 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
87 90 hextxt << ">"
88 91 rescue
89 92 txt
90 93 end || ''
91 94 super(txt)
92 95 end
93 96
94 97 def textstring(s)
95 98 # Format a text string
96 99 if s =~ /^</ # This means the string is hex-dumped.
97 100 return s
98 101 else
99 102 return '('+escape(s)+')'
100 103 end
101 104 end
102 105
103 106 def fix_text_encoding(txt)
104 107 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
105 108 end
106 109
107 110 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
108 111 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
109 112 end
110 113
111 114 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
112 115 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
113 116 end
114 117
115 118 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
116 119 writeHTMLCell(w, h, x, y,
117 120 fix_text_encoding(
118 121 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
119 122 border, ln, fill)
120 123 end
121 124
122 125 def Footer
123 126 SetFont(@font_for_footer, 'I', 8)
124 127 SetY(-15)
125 128 SetX(15)
126 129 RDMCell(0, 5, @footer_date, 0, 0, 'L')
127 130 SetY(-15)
128 131 SetX(-30)
129 132 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
130 133 end
131 134 end
132 135
133 136 # Returns a PDF string of a list of issues
134 137 def issues_to_pdf(issues, project, query)
135 138 pdf = ITCPDF.new(current_language)
136 139 title = query.new_record? ? l(:label_issue_plural) : query.name
137 140 title = "#{project} - #{title}" if project
138 141 pdf.SetTitle(title)
139 142 pdf.alias_nb_pages
140 143 pdf.footer_date = format_date(Date.today)
141 144 pdf.SetAutoPageBreak(false)
142 145 pdf.AddPage("L")
143 146
144 147 # Landscape A4 = 210 x 297 mm
145 148 page_height = 210
146 149 page_width = 297
147 150 right_margin = 10
148 151 bottom_margin = 20
149 152 col_id_width = 10
150 153 row_height = 5
151 154
152 155 # column widths
153 156 table_width = page_width - right_margin - 10 # fixed left margin
154 157 col_width = []
155 158 unless query.columns.empty?
156 159 col_width = query.columns.collect do |c|
157 160 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) &&
158 161 ['string', 'text'].include?(c.custom_field.field_format))) ? 4.0 : 1.0
159 162 end
160 163 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
161 164 col_width = col_width.collect {|w| w * ratio}
162 165 end
163 166
164 167 # title
165 168 pdf.SetFontStyle('B',11)
166 169 pdf.RDMCell(190,10, title)
167 170 pdf.Ln
168 171
169 172 # headers
170 173 pdf.SetFontStyle('B',8)
171 174 pdf.SetFillColor(230, 230, 230)
172 175
173 176 # render it background to find the max height used
174 177 base_x = pdf.GetX
175 178 base_y = pdf.GetY
176 179 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
177 180 pdf.Rect(base_x, base_y, table_width, max_height, 'FD');
178 181 pdf.SetXY(base_x, base_y);
179 182
180 183 # write the cells on page
181 184 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
182 185 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
183 186 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
184 187 pdf.SetY(base_y + max_height);
185 188
186 189 # rows
187 190 pdf.SetFontStyle('',8)
188 191 pdf.SetFillColor(255, 255, 255)
189 192 previous_group = false
190 193 issue_list(issues) do |issue, level|
191 194 if query.grouped? &&
192 195 (group = query.group_by_column.value(issue)) != previous_group
193 196 pdf.SetFontStyle('B',9)
194 197 pdf.RDMCell(277, row_height,
195 198 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
196 199 1, 1, 'L')
197 200 pdf.SetFontStyle('',8)
198 201 previous_group = group
199 202 end
200 203 # fetch all the row values
201 204 col_values = query.columns.collect do |column|
202 205 s = if column.is_a?(QueryCustomFieldColumn)
203 206 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
204 207 show_value(cv)
205 208 else
206 209 value = issue.send(column.name)
207 210 if column.name == :subject
208 211 value = " " * level + value
209 212 end
210 213 if value.is_a?(Date)
211 214 format_date(value)
212 215 elsif value.is_a?(Time)
213 216 format_time(value)
214 217 else
215 218 value
216 219 end
217 220 end
218 221 s.to_s
219 222 end
220 223
221 224 # render it off-page to find the max height used
222 225 base_x = pdf.GetX
223 226 base_y = pdf.GetY
224 227 pdf.SetY(2 * page_height)
225 228 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
226 229 pdf.SetXY(base_x, base_y)
227 230
228 231 # make new page if it doesn't fit on the current one
229 232 space_left = page_height - base_y - bottom_margin
230 233 if max_height > space_left
231 234 pdf.AddPage("L")
232 235 base_x = pdf.GetX
233 236 base_y = pdf.GetY
234 237 end
235 238
236 239 # write the cells on page
237 240 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
238 241 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
239 242 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
240 243 pdf.SetY(base_y + max_height);
241 244 end
242 245
243 246 if issues.size == Setting.issues_export_limit.to_i
244 247 pdf.SetFontStyle('B',10)
245 248 pdf.RDMCell(0, row_height, '...')
246 249 end
247 250 pdf.Output
248 251 end
249 252
250 253 # Renders MultiCells and returns the maximum height used
251 254 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
252 255 row_height, head=false)
253 256 base_y = pdf.GetY
254 257 max_height = row_height
255 258 col_values.each_with_index do |column, i|
256 259 col_x = pdf.GetX
257 260 if head == true
258 261 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
259 262 else
260 263 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
261 264 end
262 265 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
263 266 pdf.SetXY(col_x + col_widths[i], base_y);
264 267 end
265 268 return max_height
266 269 end
267 270
268 271 # Draw lines to close the row (MultiCell border drawing in not uniform)
269 272 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
270 273 id_width, col_widths)
271 274 col_x = top_x + id_width
272 275 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
273 276 col_widths.each do |width|
274 277 col_x += width
275 278 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
276 279 end
277 280 pdf.Line(top_x, top_y, top_x, lower_y) # left border
278 281 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
279 282 end
280 283
281 284 # Returns a PDF string of a single issue
282 285 def issue_to_pdf(issue)
283 286 pdf = ITCPDF.new(current_language)
284 287 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
285 288 pdf.alias_nb_pages
286 289 pdf.footer_date = format_date(Date.today)
287 290 pdf.AddPage
288 291 pdf.SetFontStyle('B',11)
289 292 buf = "#{issue.project} - #{issue.tracker} # #{issue.id}"
290 293 pdf.RDMMultiCell(190, 5, buf)
291 294 pdf.Ln
292 295 pdf.SetFontStyle('',8)
293 296 base_x = pdf.GetX
294 297 i = 1
295 298 issue.ancestors.each do |ancestor|
296 299 pdf.SetX(base_x + i)
297 300 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
298 301 pdf.RDMMultiCell(190 - i, 5, buf)
299 302 i += 1 if i < 35
300 303 end
301 304 pdf.Ln
302 305
303 306 pdf.SetFontStyle('B',9)
304 307 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
305 308 pdf.SetFontStyle('',9)
306 309 pdf.RDMCell(60,5, issue.status.to_s,"RT")
307 310 pdf.SetFontStyle('B',9)
308 311 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
309 312 pdf.SetFontStyle('',9)
310 313 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
311 314 pdf.Ln
312 315
313 316 pdf.SetFontStyle('B',9)
314 317 pdf.RDMCell(35,5, l(:field_author) + ":","L")
315 318 pdf.SetFontStyle('',9)
316 319 pdf.RDMCell(60,5, issue.author.to_s,"R")
317 320 pdf.SetFontStyle('B',9)
318 321 pdf.RDMCell(35,5, l(:field_category) + ":","L")
319 322 pdf.SetFontStyle('',9)
320 323 pdf.RDMCell(60,5, issue.category.to_s,"R")
321 324 pdf.Ln
322 325
323 326 pdf.SetFontStyle('B',9)
324 327 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
325 328 pdf.SetFontStyle('',9)
326 329 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
327 330 pdf.SetFontStyle('B',9)
328 331 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
329 332 pdf.SetFontStyle('',9)
330 333 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
331 334 pdf.Ln
332 335
333 336 pdf.SetFontStyle('B',9)
334 337 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
335 338 pdf.SetFontStyle('',9)
336 339 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
337 340 pdf.SetFontStyle('B',9)
338 341 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
339 342 pdf.SetFontStyle('',9)
340 343 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
341 344 pdf.Ln
342 345
343 346 for custom_value in issue.custom_field_values
344 347 pdf.SetFontStyle('B',9)
345 348 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
346 349 pdf.SetFontStyle('',9)
347 350 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
348 351 end
349 352
350 353 y0 = pdf.GetY
351 354
352 355 pdf.SetFontStyle('B',9)
353 356 pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
354 357 pdf.SetFontStyle('',9)
355 358 pdf.RDMMultiCell(155,5, issue.subject,"RT")
356 359 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
357 360
358 361 pdf.SetFontStyle('B',9)
359 362 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
360 363 pdf.SetFontStyle('',9)
361 364
362 365 # Set resize image scale
363 366 pdf.SetImageScale(1.6)
364 367 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
365 368 issue.description.to_s, issue.attachments, "LRB")
366 369
367 370 unless issue.leaf?
368 371 # for CJK
369 372 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
370 373
371 374 pdf.SetFontStyle('B',9)
372 375 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
373 376 pdf.Ln
374 377 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
375 378 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
376 379 :length => truncate_length)
377 380 level = 10 if level >= 10
378 381 pdf.SetFontStyle('',8)
379 382 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
380 383 pdf.SetFontStyle('B',8)
381 384 pdf.RDMCell(20,5, child.status.to_s, "R")
382 385 pdf.Ln
383 386 end
384 387 end
385 388
386 389 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
387 390 unless relations.empty?
388 391 # for CJK
389 392 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
390 393
391 394 pdf.SetFontStyle('B',9)
392 395 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
393 396 pdf.Ln
394 397 relations.each do |relation|
395 398 buf = ""
396 399 buf += "#{l(relation.label_for(issue))} "
397 400 if relation.delay && relation.delay != 0
398 401 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
399 402 end
400 403 if Setting.cross_project_issue_relations?
401 404 buf += "#{relation.other_issue(issue).project} - "
402 405 end
403 406 buf += "#{relation.other_issue(issue).tracker}" +
404 407 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
405 408 buf = truncate(buf, :length => truncate_length)
406 409 pdf.SetFontStyle('', 8)
407 410 pdf.RDMCell(35+155-50,5, buf, "L")
408 411 pdf.SetFontStyle('B',8)
409 412 pdf.RDMCell(10,5, relation.other_issue(issue).status.to_s, "")
410 413 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
411 414 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
412 415 pdf.Ln
413 416 end
414 417 end
415 418 pdf.RDMCell(190,5, "", "T")
416 419 pdf.Ln
417 420
418 421 if issue.changesets.any? &&
419 422 User.current.allowed_to?(:view_changesets, issue.project)
420 423 pdf.SetFontStyle('B',9)
421 424 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
422 425 pdf.Ln
423 426 for changeset in issue.changesets
424 427 pdf.SetFontStyle('B',8)
425 428 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
426 429 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
427 430 pdf.RDMCell(190, 5, csstr)
428 431 pdf.Ln
429 432 unless changeset.comments.blank?
430 433 pdf.SetFontStyle('',8)
431 434 pdf.RDMwriteHTMLCell(190,5,0,0,
432 435 changeset.comments.to_s, issue.attachments, "")
433 436 end
434 437 pdf.Ln
435 438 end
436 439 end
437 440
438 441 pdf.SetFontStyle('B',9)
439 442 pdf.RDMCell(190,5, l(:label_history), "B")
440 443 pdf.Ln
441 444 for journal in issue.journals.find(
442 445 :all, :include => [:user, :details],
443 446 :order => "#{Journal.table_name}.created_on ASC")
444 447 pdf.SetFontStyle('B',8)
445 448 pdf.RDMCell(190,5,
446 449 format_time(journal.created_on) + " - " + journal.user.name)
447 450 pdf.Ln
448 451 pdf.SetFontStyle('I',8)
449 452 for detail in journal.details
450 453 pdf.RDMMultiCell(190,5, "- " + show_detail(detail, true))
451 454 end
452 455 if journal.notes?
453 456 pdf.Ln unless journal.details.empty?
454 457 pdf.SetFontStyle('',8)
455 458 pdf.RDMwriteHTMLCell(190,5,0,0,
456 459 journal.notes.to_s, issue.attachments, "")
457 460 end
458 461 pdf.Ln
459 462 end
460 463
461 464 if issue.attachments.any?
462 465 pdf.SetFontStyle('B',9)
463 466 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
464 467 pdf.Ln
465 468 for attachment in issue.attachments
466 469 pdf.SetFontStyle('',8)
467 470 pdf.RDMCell(80,5, attachment.filename)
468 471 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
469 472 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
470 473 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
471 474 pdf.Ln
472 475 end
473 476 end
474 477 pdf.Output
475 478 end
476 479
477 480 # Returns a PDF string of a single wiki page
478 481 def wiki_to_pdf(page, project)
479 482 pdf = ITCPDF.new(current_language)
480 483 pdf.SetTitle("#{project} - #{page.title}")
481 484 pdf.alias_nb_pages
482 485 pdf.footer_date = format_date(Date.today)
483 486 pdf.AddPage
484 487 pdf.SetFontStyle('B',11)
485 488 pdf.RDMMultiCell(190,5,
486 489 "#{project} - #{page.title} - # #{page.content.version}")
487 490 pdf.Ln
488 491 # Set resize image scale
489 492 pdf.SetImageScale(1.6)
490 493 pdf.SetFontStyle('',9)
491 494 pdf.RDMwriteHTMLCell(190,5,0,0,
492 495 page.content.text.to_s, page.attachments, "TLRB")
493 496 if page.attachments.any?
494 497 pdf.Ln
495 498 pdf.SetFontStyle('B',9)
496 499 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
497 500 pdf.Ln
498 501 for attachment in page.attachments
499 502 pdf.SetFontStyle('',8)
500 503 pdf.RDMCell(80,5, attachment.filename)
501 504 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
502 505 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
503 506 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
504 507 pdf.Ln
505 508 end
506 509 end
507 510 pdf.Output
508 511 end
509 512
510 513 class RDMPdfEncoding
511 514 def self.rdm_from_utf8(txt, encoding)
512 515 txt ||= ''
513 516 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
514 517 if txt.respond_to?(:force_encoding)
515 518 txt.force_encoding('ASCII-8BIT')
516 519 end
517 520 txt
518 521 end
519 522
520 523 def self.attach(attachments, filename, encoding)
521 524 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
522 525 atta = nil
523 526 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
524 527 atta = Attachment.latest_attach(attachments, filename_utf8)
525 528 end
526 529 if atta && atta.readable? && atta.visible?
527 530 return atta
528 531 else
529 532 return nil
530 533 end
531 534 end
532 535 end
533 536 end
534 537 end
535 538 end
General Comments 0
You need to be logged in to leave comments. Login now