Mercurial > hg > soundsoftware-site
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 |