diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 96f9c95a..e336b2bb 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -36,6 +36,7 @@ class WikiController < ApplicationController verify :method => :post, :only => [:protect], :redirect_to => { :action => :show } include AttachmentsHelper + include Redmine::Export::PDF # List of pages, sorted alphabetically and by parent (hierarchy) def index @@ -69,7 +70,10 @@ class WikiController < ApplicationController end @content = @page.content_for_version(params[:version]) if User.current.allowed_to?(:export_wiki_pages, @project) - if params[:format] == 'html' + if params[:format] == 'pdf' + send_data(wiki_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf") + return + elsif params[:format] == 'html' export = render_to_string :action => 'export', :layout => false send_data(export, :type => 'text/html', :filename => "#{@page.title}.html") return diff --git a/app/views/wiki/show.rhtml b/app/views/wiki/show.rhtml index b2d7e2d2..7ff73d4b 100644 --- a/app/views/wiki/show.rhtml +++ b/app/views/wiki/show.rhtml @@ -47,6 +47,7 @@ <% other_formats_links do |f| %> <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %> + <%= f.link_to 'PDF', :url => {:id => @page.title, :version => @content.version} %> <%= f.link_to 'HTML', :url => {:id => @page.title, :version => @content.version} %> <%= f.link_to 'TXT', :url => {:id => @page.title, :version => @content.version} %> <%= call_hook(:view_wiki_show_other_formats, {:link_builder => f, :url_params => {:id => @page.title, :version => @content.version}}) %> diff --git a/lib/redmine/codeset_util.rb b/lib/redmine/codeset_util.rb new file mode 100644 index 00000000..b74a3a9e --- /dev/null +++ b/lib/redmine/codeset_util.rb @@ -0,0 +1,149 @@ +require 'iconv' + +module Redmine + module CodesetUtil + + def self.replace_invalid_utf8(str) + return str if str.nil? + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + if ! str.valid_encoding? + str = str.encode("US-ASCII", :invalid => :replace, + :undef => :replace, :replace => '?').encode("UTF-8") + end + elsif RUBY_PLATFORM == 'java' + begin + ic = Iconv.new('UTF-8', 'UTF-8') + str = ic.iconv(str) + rescue + str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') + end + else + ic = Iconv.new('UTF-8', 'UTF-8') + txtar = "" + begin + txtar += ic.iconv(str) + rescue Iconv::IllegalSequence + txtar += $!.success + str = '?' + $!.failed[1,$!.failed.length] + retry + rescue + txtar += $!.success + end + str = txtar + end + str + end + + def self.to_utf8(str, encoding) + return str if str.nil? + str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding) + if str.empty? + str.force_encoding("UTF-8") if str.respond_to?(:force_encoding) + return str + end + enc = encoding.blank? ? "UTF-8" : encoding + if str.respond_to?(:force_encoding) + if enc.upcase != "UTF-8" + str.force_encoding(enc) + str = str.encode("UTF-8", :invalid => :replace, + :undef => :replace, :replace => '?') + else + str.force_encoding("UTF-8") + if ! str.valid_encoding? + str = str.encode("US-ASCII", :invalid => :replace, + :undef => :replace, :replace => '?').encode("UTF-8") + end + end + elsif RUBY_PLATFORM == 'java' + begin + ic = Iconv.new('UTF-8', enc) + str = ic.iconv(str) + rescue + str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') + end + else + ic = Iconv.new('UTF-8', enc) + txtar = "" + begin + txtar += ic.iconv(str) + rescue Iconv::IllegalSequence + txtar += $!.success + str = '?' + $!.failed[1,$!.failed.length] + retry + rescue + txtar += $!.success + end + str = txtar + end + str + end + + def self.to_utf8_by_setting(str) + return str if str.nil? + str = self.to_utf8_by_setting_internal(str) + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + end + str + end + + def self.to_utf8_by_setting_internal(str) + return str if str.nil? + if str.respond_to?(:force_encoding) + str.force_encoding('ASCII-8BIT') + end + return str if str.empty? + return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + end + encodings = Setting.repositories_encodings.split(',').collect(&:strip) + encodings.each do |encoding| + begin + return Iconv.conv('UTF-8', encoding, str) + rescue Iconv::Failure + # do nothing here and try the next encoding + end + end + str = self.replace_invalid_utf8(str) + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + end + str + end + + def self.from_utf8(str, encoding) + str ||= '' + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + if encoding.upcase != 'UTF-8' + str = str.encode(encoding, :invalid => :replace, + :undef => :replace, :replace => '?') + else + str = self.replace_invalid_utf8(str) + end + elsif RUBY_PLATFORM == 'java' + begin + ic = Iconv.new(encoding, 'UTF-8') + str = ic.iconv(str) + rescue + str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') + end + else + ic = Iconv.new(encoding, 'UTF-8') + txtar = "" + begin + txtar += ic.iconv(str) + rescue Iconv::IllegalSequence + txtar += $!.success + str = '?' + $!.failed[1, $!.failed.length] + retry + rescue + txtar += $!.success + end + str = txtar + end + end + end +end diff --git a/lib/redmine/export/pdf.rb b/lib/redmine/export/pdf.rb index 29e38f78..78bc612b 100644 --- a/lib/redmine/export/pdf.rb +++ b/lib/redmine/export/pdf.rb @@ -74,6 +74,24 @@ module Redmine SetX(-30) RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') end + + def fix_text_encoding(txt) + RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding)) + end + + def formatted_text(text) + html = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) + # Strip {{toc}} tags + html.gsub!(/

\{\{([<>]?)toc\}\}<\/p>/i, '') + html + end + + def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0) + @attachments = attachments + writeHTMLCell(w, h, x, y, + fix_text_encoding(formatted_text(txt)), + border, ln, fill) + end end class IFPDF < FPDF @@ -441,6 +459,67 @@ module Redmine pdf.Output end + # Returns a PDF string of a single wiki page + def wiki_to_pdf(page, project) + pdf = ITCPDF.new(current_language) + pdf.SetTitle("#{project} - #{page.title}") + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.AddPage + pdf.SetFontStyle('B',11) + pdf.RDMMultiCell(190,5, + "#{project} - #{page.title} - # #{page.content.version}") + pdf.Ln + + # Set resize image scale + pdf.SetImageScale(1.6) + + pdf.SetFontStyle('',9) + pdf.RDMwriteHTMLCell(190,5,0,0, + page.content.text.to_s, page.attachments, "TLRB") + + if page.attachments.any? + pdf.Ln + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_attachment_plural), "B") + pdf.Ln + for attachment in page.attachments + pdf.SetFontStyle('',8) + pdf.RDMCell(80,5, attachment.filename) + pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") + pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") + pdf.RDMCell(65,5, attachment.author.name,0,0,"R") + pdf.Ln + end + end + + pdf.Output + end end + + class RDMPdfEncoding + def self.rdm_from_utf8(txt, encoding) + txt ||= '' + txt = Redmine::CodesetUtil.from_utf8(txt, encoding) + if txt.respond_to?(:force_encoding) + txt.force_encoding('ASCII-8BIT') + end + txt + end + + def self.attach(attachments, filename, encoding) + filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding) + atta = nil + if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i + atta = Attachment.latest_attach(attachments, filename_utf8) + end + if atta && atta.readable? && atta.visible? + return atta + else + return nil + end + end + end + end end diff --git a/vendor/plugins/rfpdf/lib/tcpdf.rb b/vendor/plugins/rfpdf/lib/tcpdf.rb index 85a78afa..68e690ad 100755 --- a/vendor/plugins/rfpdf/lib/tcpdf.rb +++ b/vendor/plugins/rfpdf/lib/tcpdf.rb @@ -3377,7 +3377,7 @@ class TCPDF #Extract attributes # get tag name tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0} - tag = tag[0].downcase; + tag = tag[0].to_s.downcase; # get attributes attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/)