class Kramdown::Converter::Pdf
Converts an element tree to a PDF using the prawn PDF library.
This basic version provides a nice starting point for customizations but can also be used directly.
There can be the following two methods for each element type:
render_TYPE(el, opts) and TYPE_options(el, opts) where el
is a
kramdown element and opts
an hash with rendering options.
The render_TYPE(el, opts) is used for rendering the specific element. If the element is a span element, it should return a hash or an array of hashes that can be used by the formatted_text method of Prawn::Document. This method can then be used in block elements to actually render the span elements.
The rendering options are passed from the parent to its child elements. This allows one to define general options at the top of the tree (the root element) that can later be changed or amended.
Currently supports the conversion of all elements except those of the following types:
:html_element, :img, :footnote
Public Class Methods
# File lib/kramdown/converter/pdf.rb, line 47 def initialize(root, options) super @stack = [] @dests = {} end
Public Instance Methods
Returns false
.
# File lib/kramdown/converter/pdf.rb, line 60 def apply_template_after? false end
PDF templates are applied before conversion. They should contain code to augment the converter object (i.e. to override the methods).
# File lib/kramdown/converter/pdf.rb, line 55 def apply_template_before? true end
Invoke the special rendering method for the given element el
.
A PDF destination is also added at the current location if th element has an ID or if the element is of type :header and the :auto_ids option is set.
# File lib/kramdown/converter/pdf.rb, line 71 def convert(el, opts = {}) id = el.attr['id'] id = generate_id(el.options[:raw_text]) if !id && @options[:auto_ids] && el.type == :header if !id.to_s.empty? && !@dests.has_key?(id) @pdf.add_dest(id, @pdf.dest_xyz(0, @pdf.y)) @dests[id] = @pdf.dest_xyz(0, @pdf.y) end send(DISPATCHER_RENDER[el.type], el, opts) end
Protected Instance Methods
Render the children of this element with the given options and return the results as array.
Each time a child is rendered, the TYPE_options
method is
invoked (if it exists) to get the specific options for the element with
which the given options are updated.
# File lib/kramdown/converter/pdf.rb, line 87 def inner(el, opts) @stack.push([el, opts]) result = el.children.map do |inner_el| options = opts.dup options.update(send(DISPATCHER_OPTIONS[inner_el.type], inner_el, options)) convert(inner_el, options) end.flatten.compact @stack.pop result end
Element rendering methods
↑ topProtected Instance Methods
# File lib/kramdown/converter/pdf.rb, line 332 def a_options(el, opts) hash = {:color => '000088'} if el.attr['href'].start_with?('#') hash[:anchor] = el.attr['href'].sub(/\A#/, '') else hash[:link] = el.attr['href'] end hash end
# File lib/kramdown/converter/pdf.rb, line 397 def abbreviation_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 180 def blockquote_options(el, opts) {:styles => [:italic]} end
# File lib/kramdown/converter/pdf.rb, line 356 def br_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 268 def codeblock_options(el, opts) { :font => 'Courier', :color => '880000', :bottom_padding => opts[:size] } end
# File lib/kramdown/converter/pdf.rb, line 348 def codespan_options(el, opts) {:font => 'Courier', :color => '880000'} end
# File lib/kramdown/converter/pdf.rb, line 238 def dd_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 222 def dl_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 230 def dt_options(el, opts) {:styles => (opts[:styles] || []) + [:bold], :bottom_padding => 0} end
# File lib/kramdown/converter/pdf.rb, line 320 def em_options(el, opts) if opts[:styles] && opts[:styles].include?(:italic) {:styles => opts[:styles].reject {|i| i == :italic}} else {:styles => (opts[:styles] || []) << :italic} end end
# File lib/kramdown/converter/pdf.rb, line 389 def entity_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 116 def header_options(el, opts) size = opts[:size] * 1.15**(6 - el.options[:level]) { :font => "Helvetica", :styles => (opts[:styles] || []) + [:bold], :size => size, :bottom_padding => opts[:size], :top_padding => opts[:size] } end
# File lib/kramdown/converter/pdf.rb, line 258 def hr_options(el, opts) {:top_padding => opts[:size], :bottom_padding => opts[:size]} end
# File lib/kramdown/converter/pdf.rb, line 405 def img_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 214 def li_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 246 def math_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 201 def ol_options(el, opts) {:bottom_padding => opts[:size]} end
# File lib/kramdown/converter/pdf.rb, line 128 def p_options(el, opts) bpad = (el.options[:transparent] ? opts[:leading] : opts[:size]) {:align => :justify, :bottom_padding => bpad} end
# File lib/kramdown/converter/pdf.rb, line 401 def render_abbreviation(el, opts) text_hash(el.value, opts) end
# File lib/kramdown/converter/pdf.rb, line 184 def render_blockquote(el, opts) @pdf.indent(mm2pt(10), mm2pt(10)) { inner(el, opts) } end
# File lib/kramdown/converter/pdf.rb, line 360 def render_br(el, opts) text_hash("\n", opts, false) end
# File lib/kramdown/converter/pdf.rb, line 275 def render_codeblock(el, opts) with_block_padding(el, opts) do @pdf.formatted_text([text_hash(el.value, opts, false)], block_hash(opts)) end end
# File lib/kramdown/converter/pdf.rb, line 352 def render_codespan(el, opts) text_hash(el.value, opts) end
# File lib/kramdown/converter/pdf.rb, line 242 def render_dd(el, opts) @pdf.indent(mm2pt(10)) { inner(el, opts) } end
# File lib/kramdown/converter/pdf.rb, line 226 def render_dl(el, opts) inner(el, opts) end
# File lib/kramdown/converter/pdf.rb, line 234 def render_dt(el, opts) render_padded_and_formatted_text(el, opts) end
# File lib/kramdown/converter/pdf.rb, line 342 def render_em(el, opts) inner(el, opts) end
# File lib/kramdown/converter/pdf.rb, line 393 def render_entity(el, opts) text_hash(el.value.char, opts) end
# File lib/kramdown/converter/pdf.rb, line 124 def render_header(el, opts) render_padded_and_formatted_text(el, opts) end
# File lib/kramdown/converter/pdf.rb, line 262 def render_hr(el, opts) with_block_padding(el, opts) do @pdf.stroke_horizontal_line(@pdf.bounds.left + mm2pt(5), @pdf.bounds.right - mm2pt(5)) end end
# File lib/kramdown/converter/pdf.rb, line 218 def render_li(el, opts) inner(el, opts) end
# File lib/kramdown/converter/pdf.rb, line 250 def render_math(el, opts) if el.options[:category] == :block @pdf.formatted_text([{:text => el.value}], block_hash(opts)) else {:text => el.value} end end
# File lib/kramdown/converter/pdf.rb, line 205 def render_ol(el, opts) with_block_padding(el, opts) do el.children.each_with_index do |li, index| @pdf.float { @pdf.formatted_text([text_hash("#{index+1}.", opts)]) } @pdf.indent(mm2pt(6)) { convert(li, opts) } end end end
# File lib/kramdown/converter/pdf.rb, line 133 def render_p(el, opts) if el.children.size == 1 && el.children.first.type == :img render_standalone_image(el, opts) else render_padded_and_formatted_text(el, opts) end end
# File lib/kramdown/converter/pdf.rb, line 108 def render_root(root, opts) @pdf = setup_document(root) inner(root, root_options(root, opts)) create_outline(root) finish_document(root) @pdf.render end
# File lib/kramdown/converter/pdf.rb, line 368 def render_smart_quote(el, opts) text_hash(smart_quote_entity(el).char, opts) end
# File lib/kramdown/converter/pdf.rb, line 141 def render_standalone_image(el, opts) img = el.children.first line = img.options[:location] if img.attr['src'].empty? warning("Rendering an image without a source is not possible#{line ? " (line #{line})" : ''}") return nil elsif img.attr['src'] !~ /\.jpe?g$|\.png$/ warning("Cannot render images other than JPEG or PNG, got #{img.attr['src']}#{line ? " on line #{line}" : ''}") return nil end img_dirs = (@options[:image_directories] || ['.']).dup begin img_path = File.join(img_dirs.shift, img.attr['src']) image_obj, image_info = @pdf.build_image_object(open(img_path)) rescue img_dirs.empty? ? raise : retry end options = {:position => :center} if img.attr['height'] && img.attr['height'] =~ /px$/ options[:height] = img.attr['height'].to_i / (@options[:image_dpi] || 150.0) * 72 elsif img.attr['width'] && img.attr['width'] =~ /px$/ options[:width] = img.attr['width'].to_i / (@options[:image_dpi] || 150.0) * 72 else options[:scale] =[(@pdf.bounds.width - mm2pt(20)) / image_info.width.to_f, 1].min end if img.attr['class'] =~ /\bright\b/ options[:position] = :right @pdf.float { @pdf.embed_image(image_obj, image_info, options) } else with_block_padding(el, opts) do @pdf.embed_image(image_obj, image_info, options) end end end
# File lib/kramdown/converter/pdf.rb, line 285 def render_table(el, opts) data = [] el.children.each do |container| container.children.each do |row| data << [] row.children.each do |cell| if cell.children.any? {|child| child.options[:category] == :block} line = el.options[:location] warning("Can't render tables with cells containing block elements#{line ? " (line #{line})" : ''}") return end cell_data = inner(cell, opts) data.last << cell_data.map {|c| c[:text]}.join('') end end end with_block_padding(el, opts) do @pdf.table(data, :width => @pdf.bounds.right) do el.options[:alignment].each_with_index do |alignment, index| columns(index).align = alignment unless alignment == :default end end end end
# File lib/kramdown/converter/pdf.rb, line 316 def render_text(el, opts) text_hash(el.value.to_s, opts) end
# File lib/kramdown/converter/pdf.rb, line 376 def render_typographic_sym(el, opts) str = if el.value == :laquo_space ::Kramdown::Utils::Entities.entity('laquo').char + ::Kramdown::Utils::Entities.entity('nbsp').char elsif el.value == :raquo_space ::Kramdown::Utils::Entities.entity('raquo').char + ::Kramdown::Utils::Entities.entity('nbsp').char else ::Kramdown::Utils::Entities.entity(el.value.to_s).char end text_hash(str, opts) end
# File lib/kramdown/converter/pdf.rb, line 192 def render_ul(el, opts) with_block_padding(el, opts) do el.children.each do |li| @pdf.float { @pdf.formatted_text([text_hash("•", opts)]) } @pdf.indent(mm2pt(6)) { convert(li, opts) } end end end
# File lib/kramdown/converter/pdf.rb, line 104 def root_options(root, opts) {:font => 'Times-Roman', :size => 12, :leading => 2} end
# File lib/kramdown/converter/pdf.rb, line 364 def smart_quote_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 328 def strong_options(el, opts) {:styles => (opts[:styles] || []) + [:bold]} end
# File lib/kramdown/converter/pdf.rb, line 281 def table_options(el, opts) {:bottom_padding => opts[:size]} end
# File lib/kramdown/converter/pdf.rb, line 312 def text_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 372 def typographic_sym_options(el, opts) {} end
# File lib/kramdown/converter/pdf.rb, line 188 def ul_options(el, opts) {:bottom_padding => opts[:size]} end
Helper methods
↑ topProtected Instance Methods
Helper function that returns a hash with valid options for the prawn text_box extracted from the given options.
# File lib/kramdown/converter/pdf.rb, line 612 def block_hash(opts) hash = {} [:align, :valign, :mode, :final_gap, :leading, :fallback_fonts, :direction, :indent_paragraphs].each do |key| hash[key] = opts[key] if opts.has_key?(key) end hash end
Render the children of the given element as formatted text and respect the top/bottom padding (see with_block_padding).
# File lib/kramdown/converter/pdf.rb, line 592 def render_padded_and_formatted_text(el, opts) with_block_padding(el, opts) { @pdf.formatted_text(inner(el, opts), block_hash(opts)) } end
Helper function that returns a hash with valid “formatted text” options.
The text
parameter is used as value for the :text key and if
squeeze_whitespace
is true
, all whitespace is
converted into spaces.
# File lib/kramdown/converter/pdf.rb, line 600 def text_hash(text, opts, squeeze_whitespace = true) text = text.gsub(/\s+/, ' ') if squeeze_whitespace hash = {:text => text} [:styles, :size, :character_spacing, :font, :color, :link, :anchor, :draw_text_callback, :callback].each do |key| hash[key] = opts[key] if opts.has_key?(key) end hash end
Move the prawn document cursor down before and/or after yielding the given block.
The :top_padding and :bottom_padding options are used for determinig the padding amount.
# File lib/kramdown/converter/pdf.rb, line 584 def with_block_padding(el, opts) @pdf.move_down(opts[:top_padding]) if opts.has_key?(:top_padding) yield @pdf.move_down(opts[:bottom_padding]) if opts.has_key?(:bottom_padding) end
Organizational methods
↑ topProtected Instance Methods
Create the PDF outline from the header elements in the TOC.
# File lib/kramdown/converter/pdf.rb, line 546 def create_outline(root) toc = ::Kramdown::Converter::Toc.convert(root).first text_of_header = lambda do |el| if el.type == :text el.value else el.children.map {|c| text_of_header.call(c)}.join('') end end add_section = lambda do |item, parent| text = text_of_header.call(item.value) destination = @dests[item.attr[:id]] if !parent @pdf.outline.page(:title => text, :destination => destination) else @pdf.outline.add_subsection_to(parent) do @pdf.outline.page(:title => text, :destination => destination) end end item.children.each {|c| add_section.call(c, text)} end toc.children.each do |item| add_section.call(item, nil) end end
Return a hash with options that are suitable for Prawn::Document.new.
Used in setup_document.
# File lib/kramdown/converter/pdf.rb, line 515 def document_options(root) { :page_size => 'A4', :page_layout => :portrait, :margin => mm2pt(20), :info => { :Creator => 'kramdown PDF converter', :CreationDate => Time.now }, :compress => true, :optimize_objects => true } end
Used in render_root.
# File lib/kramdown/converter/pdf.rb, line 541 def finish_document(root) # no op end
Create a Prawn::Document object and return it.
Can be used to define repeatable content or register fonts.
Used in render_root.
# File lib/kramdown/converter/pdf.rb, line 531 def setup_document(root) doc = Prawn::Document.new(document_options(root)) doc.extend(PrawnDocumentExtension) doc.converter = self doc end