Revision 912:5e80956cc792 lib/redmine/export
| lib/redmine/export/pdf.rb | ||
|---|---|---|
| 18 | 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 19 | 19 |
|
| 20 | 20 |
require 'iconv' |
| 21 |
require 'rfpdf/fpdf' |
|
| 22 | 21 |
require 'fpdf/chinese' |
| 23 | 22 |
require 'fpdf/japanese' |
| 24 | 23 |
require 'fpdf/korean' |
| 24 |
require 'core/rmagick' |
|
| 25 | 25 |
|
| 26 | 26 |
module Redmine |
| 27 | 27 |
module Export |
| 28 | 28 |
module PDF |
| 29 | 29 |
include ActionView::Helpers::TextHelper |
| 30 | 30 |
include ActionView::Helpers::NumberHelper |
| 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 |
if RUBY_VERSION < '1.9' |
|
| 40 |
@ic = Iconv.new(pdf_encoding, 'UTF-8') |
|
| 41 |
end |
|
| 42 | 42 |
super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
|
| 43 | 43 |
case current_language.to_s.downcase |
| 44 | 44 |
when 'vi' |
| ... | ... | |
| 104 | 104 |
end |
| 105 | 105 |
|
| 106 | 106 |
def fix_text_encoding(txt) |
| 107 |
RDMPdfEncoding::rdm_pdf_iconv(@ic, txt)
|
|
| 107 |
RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
|
|
| 108 | 108 |
end |
| 109 | 109 |
|
| 110 | 110 |
def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='') |
| ... | ... | |
| 115 | 115 |
MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln) |
| 116 | 116 |
end |
| 117 | 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 |
|
| 134 |
end |
|
| 135 |
|
|
| 118 | 136 |
def Footer |
| 119 | 137 |
SetFont(@font_for_footer, 'I', 8) |
| 120 | 138 |
SetY(-15) |
| ... | ... | |
| 150 | 168 |
col_width = [] |
| 151 | 169 |
unless query.columns.empty? |
| 152 | 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 | 173 |
end |
| 155 | 174 |
ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
|
| 156 | 175 |
col_width = col_width.collect {|w| w * ratio}
|
| ... | ... | |
| 182 | 201 |
pdf.SetFontStyle('',8)
|
| 183 | 202 |
pdf.SetFillColor(255, 255, 255) |
| 184 | 203 |
previous_group = false |
| 185 |
issues.each do |issue|
|
|
| 204 |
issue_list(issues) do |issue, level|
|
|
| 186 | 205 |
if query.grouped? && |
| 187 | 206 |
(group = query.group_by_column.value(issue)) != previous_group |
| 188 | 207 |
pdf.SetFontStyle('B',9)
|
| ... | ... | |
| 199 | 218 |
show_value(cv) |
| 200 | 219 |
else |
| 201 | 220 |
value = issue.send(column.name) |
| 221 |
if column.name == :subject |
|
| 222 |
value = " " * level + value |
|
| 223 |
end |
|
| 202 | 224 |
if value.is_a?(Date) |
| 203 | 225 |
format_date(value) |
| 204 | 226 |
elsif value.is_a?(Time) |
| ... | ... | |
| 278 | 300 |
pdf.footer_date = format_date(Date.today) |
| 279 | 301 |
pdf.AddPage |
| 280 | 302 |
pdf.SetFontStyle('B',11)
|
| 281 |
pdf.RDMMultiCell(190,5, |
|
| 282 |
"#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
|
|
| 303 |
buf = "#{issue.project} - #{issue.tracker} # #{issue.id}"
|
|
| 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 | 315 |
pdf.Ln |
| 284 | 316 |
|
| 285 | 317 |
pdf.SetFontStyle('B',9)
|
| ... | ... | |
| 340 | 372 |
pdf.SetFontStyle('B',9)
|
| 341 | 373 |
pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) |
| 342 | 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 | 430 |
pdf.Ln |
| 345 | 431 |
|
| 346 | 432 |
if issue.changesets.any? && |
| ... | ... | |
| 356 | 442 |
pdf.Ln |
| 357 | 443 |
unless changeset.comments.blank? |
| 358 | 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 | 447 |
end |
| 361 | 448 |
pdf.Ln |
| 362 | 449 |
end |
| ... | ... | |
| 365 | 452 |
pdf.SetFontStyle('B',9)
|
| 366 | 453 |
pdf.RDMCell(190,5, l(:label_history), "B") |
| 367 | 454 |
pdf.Ln |
| 455 |
indice = 0 |
|
| 368 | 456 |
for journal in issue.journals.find( |
| 369 | 457 |
:all, :include => [:user, :details], |
| 370 | 458 |
:order => "#{Journal.table_name}.created_on ASC")
|
| 459 |
indice = indice + 1 |
|
| 371 | 460 |
pdf.SetFontStyle('B',8)
|
| 372 | 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 | 465 |
pdf.Ln |
| 375 | 466 |
pdf.SetFontStyle('I',8)
|
| 376 | 467 |
for detail in journal.details |
| ... | ... | |
| 379 | 470 |
if journal.notes? |
| 380 | 471 |
pdf.Ln unless journal.details.empty? |
| 381 | 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 | 475 |
end |
| 384 | 476 |
pdf.Ln |
| 385 | 477 |
end |
| ... | ... | |
| 400 | 492 |
pdf.Output |
| 401 | 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 | 528 |
class RDMPdfEncoding |
| 404 |
include Redmine::I18n |
|
| 405 |
def self.rdm_pdf_iconv(ic, txt) |
|
| 529 |
def self.rdm_from_utf8(txt, encoding) |
|
| 406 | 530 |
txt ||= '' |
| 531 |
txt = Redmine::CodesetUtil.from_utf8(txt, encoding) |
|
| 407 | 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 | 533 |
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 | 534 |
end |
| 437 | 535 |
txt |
| 438 | 536 |
end |
| 537 |
|
|
| 538 |
def self.attach(attachments, filename, encoding) |
|
| 539 |
filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding) |
|
| 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 |
|
| 546 |
else |
|
| 547 |
return nil |
|
| 548 |
end |
|
| 549 |
end |
|
| 439 | 550 |
end |
| 440 | 551 |
end |
| 441 | 552 |
end |
Also available in: Unified diff