comparison lib/redmine/export/pdf.rb @ 909:cbb26bc654de redmine-1.3

Update to Redmine 1.3-stable branch (Redmine SVN rev 8964)
author Chris Cannam
date Fri, 24 Feb 2012 19:09:32 +0000
parents 0c939c159af4
children 433d4f72a19b
comparison
equal deleted inserted replaced
908:c6c2cbd0afee 909:cbb26bc654de
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'
22 require 'fpdf/chinese' 21 require 'fpdf/chinese'
23 require 'fpdf/japanese' 22 require 'fpdf/japanese'
24 require 'fpdf/korean' 23 require 'fpdf/korean'
24 require 'core/rmagick'
25 25
26 module Redmine 26 module Redmine
27 module Export 27 module Export
28 module PDF 28 module PDF
29 include ActionView::Helpers::TextHelper 29 include ActionView::Helpers::TextHelper
30 include ActionView::Helpers::NumberHelper 30 include ActionView::Helpers::NumberHelper
31 include IssuesHelper
31 32
32 class ITCPDF < TCPDF 33 class ITCPDF < TCPDF
33 include Redmine::I18n 34 include Redmine::I18n
34 attr_accessor :footer_date 35 attr_accessor :footer_date
35 36
36 def initialize(lang) 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 set_language_if_valid lang 40 set_language_if_valid lang
38 pdf_encoding = l(:general_pdf_encoding).upcase 41 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) 42 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
43 case current_language.to_s.downcase 43 case current_language.to_s.downcase
44 when 'vi' 44 when 'vi'
45 @font_for_content = 'DejaVuSans' 45 @font_for_content = 'DejaVuSans'
46 @font_for_footer = 'DejaVuSans' 46 @font_for_footer = 'DejaVuSans'
102 return '('+escape(s)+')' 102 return '('+escape(s)+')'
103 end 103 end
104 end 104 end
105 105
106 def fix_text_encoding(txt) 106 def fix_text_encoding(txt)
107 RDMPdfEncoding::rdm_pdf_iconv(@ic, txt) 107 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
108 end 108 end
109 109
110 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='') 110 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
111 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link) 111 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
112 end 112 end
113 113
114 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1) 114 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
115 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln) 115 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
116 end
117
118 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
119 @attachments = attachments
120 writeHTMLCell(w, h, x, y,
121 fix_text_encoding(
122 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
123 border, ln, fill)
124 end
125
126 def getImageFilename(attrname)
127 # attrname: general_pdf_encoding string file/uri name
128 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
129 if atta
130 return atta.diskfile
131 else
132 return nil
133 end
116 end 134 end
117 135
118 def Footer 136 def Footer
119 SetFont(@font_for_footer, 'I', 8) 137 SetFont(@font_for_footer, 'I', 8)
120 SetY(-15) 138 SetY(-15)
148 # column widths 166 # column widths
149 table_width = page_width - right_margin - 10 # fixed left margin 167 table_width = page_width - right_margin - 10 # fixed left margin
150 col_width = [] 168 col_width = []
151 unless query.columns.empty? 169 unless query.columns.empty?
152 col_width = query.columns.collect do |c| 170 col_width = query.columns.collect do |c|
153 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) && ['string', 'text'].include?(c.custom_field.field_format)))? 4.0 : 1.0 171 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) &&
172 ['string', 'text'].include?(c.custom_field.field_format))) ? 4.0 : 1.0
154 end 173 end
155 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w} 174 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
156 col_width = col_width.collect {|w| w * ratio} 175 col_width = col_width.collect {|w| w * ratio}
157 end 176 end
158 177
180 199
181 # rows 200 # rows
182 pdf.SetFontStyle('',8) 201 pdf.SetFontStyle('',8)
183 pdf.SetFillColor(255, 255, 255) 202 pdf.SetFillColor(255, 255, 255)
184 previous_group = false 203 previous_group = false
185 issues.each do |issue| 204 issue_list(issues) do |issue, level|
186 if query.grouped? && 205 if query.grouped? &&
187 (group = query.group_by_column.value(issue)) != previous_group 206 (group = query.group_by_column.value(issue)) != previous_group
188 pdf.SetFontStyle('B',9) 207 pdf.SetFontStyle('B',9)
189 pdf.RDMCell(277, row_height, 208 pdf.RDMCell(277, row_height,
190 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})", 209 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
197 s = if column.is_a?(QueryCustomFieldColumn) 216 s = if column.is_a?(QueryCustomFieldColumn)
198 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id} 217 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
199 show_value(cv) 218 show_value(cv)
200 else 219 else
201 value = issue.send(column.name) 220 value = issue.send(column.name)
221 if column.name == :subject
222 value = " " * level + value
223 end
202 if value.is_a?(Date) 224 if value.is_a?(Date)
203 format_date(value) 225 format_date(value)
204 elsif value.is_a?(Time) 226 elsif value.is_a?(Time)
205 format_time(value) 227 format_time(value)
206 else 228 else
276 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}") 298 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
277 pdf.alias_nb_pages 299 pdf.alias_nb_pages
278 pdf.footer_date = format_date(Date.today) 300 pdf.footer_date = format_date(Date.today)
279 pdf.AddPage 301 pdf.AddPage
280 pdf.SetFontStyle('B',11) 302 pdf.SetFontStyle('B',11)
281 pdf.RDMMultiCell(190,5, 303 buf = "#{issue.project} - #{issue.tracker} # #{issue.id}"
282 "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}") 304 pdf.RDMMultiCell(190, 5, buf)
305 pdf.Ln
306 pdf.SetFontStyle('',8)
307 base_x = pdf.GetX
308 i = 1
309 issue.ancestors.each do |ancestor|
310 pdf.SetX(base_x + i)
311 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
312 pdf.RDMMultiCell(190 - i, 5, buf)
313 i += 1 if i < 35
314 end
283 pdf.Ln 315 pdf.Ln
284 316
285 pdf.SetFontStyle('B',9) 317 pdf.SetFontStyle('B',9)
286 pdf.RDMCell(35,5, l(:field_status) + ":","LT") 318 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
287 pdf.SetFontStyle('',9) 319 pdf.SetFontStyle('',9)
338 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY) 370 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
339 371
340 pdf.SetFontStyle('B',9) 372 pdf.SetFontStyle('B',9)
341 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) 373 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
342 pdf.SetFontStyle('',9) 374 pdf.SetFontStyle('',9)
343 pdf.RDMMultiCell(35+155, 5, issue.description.to_s, "LRB") 375
376 # Set resize image scale
377 pdf.SetImageScale(1.6)
378 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
379 issue.description.to_s, issue.attachments, "LRB")
380
381 unless issue.leaf?
382 # for CJK
383 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
384
385 pdf.SetFontStyle('B',9)
386 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
387 pdf.Ln
388 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
389 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
390 :length => truncate_length)
391 level = 10 if level >= 10
392 pdf.SetFontStyle('',8)
393 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
394 pdf.SetFontStyle('B',8)
395 pdf.RDMCell(20,5, child.status.to_s, "R")
396 pdf.Ln
397 end
398 end
399
400 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
401 unless relations.empty?
402 # for CJK
403 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
404
405 pdf.SetFontStyle('B',9)
406 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
407 pdf.Ln
408 relations.each do |relation|
409 buf = ""
410 buf += "#{l(relation.label_for(issue))} "
411 if relation.delay && relation.delay != 0
412 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
413 end
414 if Setting.cross_project_issue_relations?
415 buf += "#{relation.other_issue(issue).project} - "
416 end
417 buf += "#{relation.other_issue(issue).tracker}" +
418 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
419 buf = truncate(buf, :length => truncate_length)
420 pdf.SetFontStyle('', 8)
421 pdf.RDMCell(35+155-60, 5, buf, "L")
422 pdf.SetFontStyle('B',8)
423 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
424 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
425 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
426 pdf.Ln
427 end
428 end
429 pdf.RDMCell(190,5, "", "T")
344 pdf.Ln 430 pdf.Ln
345 431
346 if issue.changesets.any? && 432 if issue.changesets.any? &&
347 User.current.allowed_to?(:view_changesets, issue.project) 433 User.current.allowed_to?(:view_changesets, issue.project)
348 pdf.SetFontStyle('B',9) 434 pdf.SetFontStyle('B',9)
354 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s 440 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
355 pdf.RDMCell(190, 5, csstr) 441 pdf.RDMCell(190, 5, csstr)
356 pdf.Ln 442 pdf.Ln
357 unless changeset.comments.blank? 443 unless changeset.comments.blank?
358 pdf.SetFontStyle('',8) 444 pdf.SetFontStyle('',8)
359 pdf.RDMMultiCell(190,5, changeset.comments.to_s) 445 pdf.RDMwriteHTMLCell(190,5,0,0,
446 changeset.comments.to_s, issue.attachments, "")
360 end 447 end
361 pdf.Ln 448 pdf.Ln
362 end 449 end
363 end 450 end
364 451
365 pdf.SetFontStyle('B',9) 452 pdf.SetFontStyle('B',9)
366 pdf.RDMCell(190,5, l(:label_history), "B") 453 pdf.RDMCell(190,5, l(:label_history), "B")
367 pdf.Ln 454 pdf.Ln
455 indice = 0
368 for journal in issue.journals.find( 456 for journal in issue.journals.find(
369 :all, :include => [:user, :details], 457 :all, :include => [:user, :details],
370 :order => "#{Journal.table_name}.created_on ASC") 458 :order => "#{Journal.table_name}.created_on ASC")
459 indice = indice + 1
371 pdf.SetFontStyle('B',8) 460 pdf.SetFontStyle('B',8)
372 pdf.RDMCell(190,5, 461 pdf.RDMCell(190,5,
373 format_time(journal.created_on) + " - " + journal.user.name) 462 "#" + indice.to_s +
463 " - " + format_time(journal.created_on) +
464 " - " + journal.user.name)
374 pdf.Ln 465 pdf.Ln
375 pdf.SetFontStyle('I',8) 466 pdf.SetFontStyle('I',8)
376 for detail in journal.details 467 for detail in journal.details
377 pdf.RDMMultiCell(190,5, "- " + show_detail(detail, true)) 468 pdf.RDMMultiCell(190,5, "- " + show_detail(detail, true))
378 end 469 end
379 if journal.notes? 470 if journal.notes?
380 pdf.Ln unless journal.details.empty? 471 pdf.Ln unless journal.details.empty?
381 pdf.SetFontStyle('',8) 472 pdf.SetFontStyle('',8)
382 pdf.RDMMultiCell(190,5, journal.notes.to_s) 473 pdf.RDMwriteHTMLCell(190,5,0,0,
474 journal.notes.to_s, issue.attachments, "")
383 end 475 end
384 pdf.Ln 476 pdf.Ln
385 end 477 end
386 478
387 if issue.attachments.any? 479 if issue.attachments.any?
398 end 490 end
399 end 491 end
400 pdf.Output 492 pdf.Output
401 end 493 end
402 494
495 # Returns a PDF string of a single wiki page
496 def wiki_to_pdf(page, project)
497 pdf = ITCPDF.new(current_language)
498 pdf.SetTitle("#{project} - #{page.title}")
499 pdf.alias_nb_pages
500 pdf.footer_date = format_date(Date.today)
501 pdf.AddPage
502 pdf.SetFontStyle('B',11)
503 pdf.RDMMultiCell(190,5,
504 "#{project} - #{page.title} - # #{page.content.version}")
505 pdf.Ln
506 # Set resize image scale
507 pdf.SetImageScale(1.6)
508 pdf.SetFontStyle('',9)
509 pdf.RDMwriteHTMLCell(190,5,0,0,
510 page.content.text.to_s, page.attachments, "TLRB")
511 if page.attachments.any?
512 pdf.Ln
513 pdf.SetFontStyle('B',9)
514 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
515 pdf.Ln
516 for attachment in page.attachments
517 pdf.SetFontStyle('',8)
518 pdf.RDMCell(80,5, attachment.filename)
519 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
520 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
521 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
522 pdf.Ln
523 end
524 end
525 pdf.Output
526 end
527
403 class RDMPdfEncoding 528 class RDMPdfEncoding
404 include Redmine::I18n 529 def self.rdm_from_utf8(txt, encoding)
405 def self.rdm_pdf_iconv(ic, txt)
406 txt ||= '' 530 txt ||= ''
531 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
407 if txt.respond_to?(:force_encoding) 532 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') 533 txt.force_encoding('ASCII-8BIT')
416 elsif RUBY_PLATFORM == 'java' 534 end
417 begin 535 txt
418 ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8') 536 end
419 txt = ic.iconv(txt) 537
420 rescue 538 def self.attach(attachments, filename, encoding)
421 txt = txt.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') 539 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
422 end 540 atta = nil
541 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
542 atta = Attachment.latest_attach(attachments, filename_utf8)
543 end
544 if atta && atta.readable? && atta.visible?
545 return atta
423 else 546 else
424 ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8') 547 return nil
425 txtar = "" 548 end
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 549 end
439 end 550 end
440 end 551 end
441 end 552 end
442 end 553 end