# 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

new(root, options) click to toggle source
Calls superclass method `Kramdown::Converter::Base::new`

       # File lib/kramdown/converter/pdf.rb
    47 def initialize(root, options)
    48   super
    49   @stack = []
    50   @dests = {}
    51 end

### Public Instance Methods

apply_template_after?() click to toggle source

Returns `false`.

       # File lib/kramdown/converter/pdf.rb
    60 def apply_template_after?
    61   false
    62 end

apply_template_before?() click to toggle source

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
    55 def apply_template_before?
    56   true
    57 end

convert(el, opts = {}) click to toggle source

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
    71 def convert(el, opts = {})
    72   id = el.attr['id']
    73   id = generate_id(el.options[:raw_text]) if !id && @options[:auto_ids] && el.type == :header
    74   if !id.to_s.empty? && !@dests.has_key?(id)
    75     @pdf.add_dest(id, @pdf.dest_xyz(0, @pdf.y))
    76     @dests[id] = @pdf.dest_xyz(0, @pdf.y)
    77   end
    78   send(DISPATCHER_RENDER[el.type], el, opts)
    79 end

### Protected Instance Methods

inner(el, opts) click to toggle source

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
    87 def inner(el, opts)
    88   @stack.push([el, opts])
    89   result = el.children.map do |inner_el|
    90     options = opts.dup
    91     options.update(send(DISPATCHER_OPTIONS[inner_el.type], inner_el, options))
    92     convert(inner_el, options)
    93   end.flatten.compact
    94   @stack.pop
    95   result
    96 end

## Element rendering methods

↑ top

### Protected Instance Methods

a_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    332 def a_options(el, opts)
    333   hash = {:color => '000088'}
    334   if el.attr['href'].start_with?('#')
    335     hash[:anchor] = el.attr['href'].sub(/\A#/, '')
    336   else
    337     hash[:link] = el.attr['href']
    338   end
    339   hash
    340 end

abbreviation_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    397 def abbreviation_options(el, opts)
    398   {}
    399 end

blockquote_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    180 def blockquote_options(el, opts)
    181   {:styles => [:italic]}
    182 end

br_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    356 def br_options(el, opts)
    357   {}
    358 end

codeblock_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    268 def codeblock_options(el, opts)
    269   {
    270     :font => 'Courier', :color => '880000',
    271     :bottom_padding => opts[:size]
    272   }
    273 end

codespan_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    348 def codespan_options(el, opts)
    349   {:font => 'Courier', :color => '880000'}
    350 end

dd_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    238 def dd_options(el, opts)
    239   {}
    240 end

dl_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    222 def dl_options(el, opts)
    223   {}
    224 end

dt_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    230 def dt_options(el, opts)
    231   {:styles => (opts[:styles] || []) + [:bold], :bottom_padding => 0}
    232 end

em_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    320 def em_options(el, opts)
    321   if opts[:styles] && opts[:styles].include?(:italic)
    322     {:styles => opts[:styles].reject {|i| i == :italic}}
    323   else
    324     {:styles => (opts[:styles] || []) << :italic}
    325   end
    326 end

entity_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    389 def entity_options(el, opts)
    390   {}
    391 end

header_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    116 def header_options(el, opts)
    117   size = opts[:size] * 1.15**(6 - el.options[:level])
    118   {
    119     :font => "Helvetica", :styles => (opts[:styles] || []) + [:bold],
    120     :size => size, :bottom_padding => opts[:size], :top_padding => opts[:size]
    121   }
    122 end

hr_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    258 def hr_options(el, opts)
    259   {:top_padding => opts[:size], :bottom_padding => opts[:size]}
    260 end

img_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    405 def img_options(el, opts)
    406   {}
    407 end

li_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    214 def li_options(el, opts)
    215   {}
    216 end

math_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    246 def math_options(el, opts)
    247   {}
    248 end

ol_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    201 def ol_options(el, opts)
    202   {:bottom_padding => opts[:size]}
    203 end

p_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    128 def p_options(el, opts)
    129   bpad = (el.options[:transparent] ? opts[:leading] : opts[:size])
    130   {:align => :justify, :bottom_padding => bpad}
    131 end

render_a(el, opts)
Alias for: render_em
render_abbreviation(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    401 def render_abbreviation(el, opts)
    402   text_hash(el.value, opts)
    403 end

render_blockquote(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    184 def render_blockquote(el, opts)
    185   @pdf.indent(mm2pt(10), mm2pt(10)) { inner(el, opts) }
    186 end

render_br(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    360 def render_br(el, opts)
    361   text_hash("\n", opts, false)
    362 end

render_codeblock(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    275 def render_codeblock(el, opts)
    276   with_block_padding(el, opts) do
    277     @pdf.formatted_text([text_hash(el.value, opts, false)], block_hash(opts))
    278   end
    279 end

render_codespan(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    352 def render_codespan(el, opts)
    353   text_hash(el.value, opts)
    354 end

render_dd(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    242 def render_dd(el, opts)
    243   @pdf.indent(mm2pt(10)) { inner(el, opts) }
    244 end

render_dl(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    226 def render_dl(el, opts)
    227   inner(el, opts)
    228 end

render_dt(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    234 def render_dt(el, opts)
    235   render_padded_and_formatted_text(el, opts)
    236 end

render_em(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    342 def render_em(el, opts)
    343   inner(el, opts)
    344 end

Also aliased as: render_strong, render_a
render_entity(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    393 def render_entity(el, opts)
    394   text_hash(el.value.char, opts)
    395 end

render_header(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    124 def render_header(el, opts)
    125   render_padded_and_formatted_text(el, opts)
    126 end

render_hr(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    262 def render_hr(el, opts)
    263   with_block_padding(el, opts) do
    264     @pdf.stroke_horizontal_line(@pdf.bounds.left + mm2pt(5), @pdf.bounds.right - mm2pt(5))
    265   end
    266 end

render_li(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    218 def render_li(el, opts)
    219   inner(el, opts)
    220 end

render_math(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    250 def render_math(el, opts)
    251   if el.options[:category] == :block
    252     @pdf.formatted_text([{:text => el.value}], block_hash(opts))
    253   else
    254     {:text => el.value}
    255   end
    256 end

render_ol(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    205 def render_ol(el, opts)
    206   with_block_padding(el, opts) do
    207     el.children.each_with_index do |li, index|
    208       @pdf.float { @pdf.formatted_text([text_hash("#{index+1}.", opts)]) }
    209       @pdf.indent(mm2pt(6)) { convert(li, opts) }
    210     end
    211   end
    212 end

render_p(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    133 def render_p(el, opts)
    134   if el.children.size == 1 && el.children.first.type == :img
    135     render_standalone_image(el, opts)
    136   else
    137     render_padded_and_formatted_text(el, opts)
    138   end
    139 end

render_root(root, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    108 def render_root(root, opts)
    109   @pdf = setup_document(root)
    110   inner(root, root_options(root, opts))
    111   create_outline(root)
    112   finish_document(root)
    113   @pdf.render
    114 end

render_smart_quote(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    368 def render_smart_quote(el, opts)
    369   text_hash(smart_quote_entity(el).char, opts)
    370 end

render_standalone_image(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    141 def render_standalone_image(el, opts)
    142   img = el.children.first
    143   line = img.options[:location]
    144
    145   if img.attr['src'].empty?
    146     warning("Rendering an image without a source is not possible#{line ? " (line #{line})" : ''}")
    147     return nil
    148   elsif img.attr['src'] !~ /\.jpe?g$|\.png$/
    149     warning("Cannot render images other than JPEG or PNG, got #{img.attr['src']}#{line ? " on line #{line}" : ''}")
    150     return nil
    151   end
    152
    153   img_dirs = (@options[:image_directories] || ['.']).dup
    154   begin
    155     img_path = File.join(img_dirs.shift, img.attr['src'])
    156     image_obj, image_info = @pdf.build_image_object(open(img_path))
    157   rescue
    158     img_dirs.empty? ? raise : retry
    159   end
    160
    161   options = {:position => :center}
    162   if img.attr['height'] && img.attr['height'] =~ /px$/
    163     options[:height] = img.attr['height'].to_i / (@options[:image_dpi] || 150.0) * 72
    164   elsif img.attr['width'] && img.attr['width'] =~ /px$/
    165     options[:width] = img.attr['width'].to_i / (@options[:image_dpi] || 150.0) * 72
    166   else
    167     options[:scale] =[(@pdf.bounds.width - mm2pt(20)) / image_info.width.to_f, 1].min
    168   end
    169
    170   if img.attr['class'] =~ /\bright\b/
    171     options[:position] = :right
    172     @pdf.float { @pdf.embed_image(image_obj, image_info, options) }
    173   else
    174     with_block_padding(el, opts) do
    175       @pdf.embed_image(image_obj, image_info, options)
    176     end
    177   end
    178 end

render_strong(el, opts)
Alias for: render_em
render_table(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    285 def render_table(el, opts)
    286   data = []
    287   el.children.each do |container|
    288     container.children.each do |row|
    289       data << []
    290       row.children.each do |cell|
    291         if cell.children.any? {|child| child.options[:category] == :block}
    292           line = el.options[:location]
    293           warning("Can't render tables with cells containing block elements#{line ? " (line #{line})" : ''}")
    294           return
    295         end
    296         cell_data = inner(cell, opts)
    297         data.last << cell_data.map {|c| c[:text]}.join('')
    298       end
    299     end
    300   end
    301   with_block_padding(el, opts) do
    302     @pdf.table(data, :width => @pdf.bounds.right) do
    303       el.options[:alignment].each_with_index do |alignment, index|
    304         columns(index).align = alignment unless alignment == :default
    305       end
    306     end
    307   end
    308 end

render_text(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    316 def render_text(el, opts)
    317   text_hash(el.value.to_s, opts)
    318 end

render_typographic_sym(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    376 def render_typographic_sym(el, opts)
    377   str = if el.value == :laquo_space
    378           ::Kramdown::Utils::Entities.entity('laquo').char +
    379             ::Kramdown::Utils::Entities.entity('nbsp').char
    380         elsif el.value == :raquo_space
    381           ::Kramdown::Utils::Entities.entity('raquo').char +
    382             ::Kramdown::Utils::Entities.entity('nbsp').char
    383         else
    384           ::Kramdown::Utils::Entities.entity(el.value.to_s).char
    385         end
    386   text_hash(str, opts)
    387 end

render_ul(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    192 def render_ul(el, opts)
    193   with_block_padding(el, opts) do
    194     el.children.each do |li|
    195       @pdf.float { @pdf.formatted_text([text_hash("•", opts)]) }
    196       @pdf.indent(mm2pt(6)) { convert(li, opts) }
    197     end
    198   end
    199 end

root_options(root, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    104 def root_options(root, opts)
    105   {:font => 'Times-Roman', :size => 12, :leading => 2}
    106 end

smart_quote_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    364 def smart_quote_options(el, opts)
    365   {}
    366 end

strong_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    328 def strong_options(el, opts)
    329   {:styles => (opts[:styles] || []) + [:bold]}
    330 end

table_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    281 def table_options(el, opts)
    282   {:bottom_padding => opts[:size]}
    283 end

text_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    312 def text_options(el, opts)
    313   {}
    314 end

typographic_sym_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    372 def typographic_sym_options(el, opts)
    373   {}
    374 end

ul_options(el, opts) click to toggle source

        # File lib/kramdown/converter/pdf.rb
    188 def ul_options(el, opts)
    189   {:bottom_padding => opts[:size]}
    190 end

## Helper methods

↑ top

### Protected Instance Methods

block_hash(opts) click to toggle source

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
    612 def block_hash(opts)
    613   hash = {}
    614   [:align, :valign, :mode, :final_gap, :leading, :fallback_fonts,
    615    :direction, :indent_paragraphs].each do |key|
    616     hash[key] = opts[key] if opts.has_key?(key)
    617   end
    618   hash
    619 end

render_padded_and_formatted_text(el, opts) click to toggle source

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
    592 def render_padded_and_formatted_text(el, opts)
    593   with_block_padding(el, opts) { @pdf.formatted_text(inner(el, opts), block_hash(opts)) }
    594 end

text_hash(text, opts, squeeze_whitespace = true) click to toggle source

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
    600 def text_hash(text, opts, squeeze_whitespace = true)
    601   text = text.gsub(/\s+/, ' ') if squeeze_whitespace
    602   hash = {:text => text}
    603   [:styles, :size, :character_spacing, :font, :color, :link,
    604    :anchor, :draw_text_callback, :callback].each do |key|
    605     hash[key] = opts[key] if opts.has_key?(key)
    606   end
    607   hash
    608 end

with_block_padding(el, opts) { || ... } click to toggle source

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
    584 def with_block_padding(el, opts)
    585   @pdf.move_down(opts[:top_padding]) if opts.has_key?(:top_padding)
    586   yield
    587   @pdf.move_down(opts[:bottom_padding]) if opts.has_key?(:bottom_padding)
    588 end

## Organizational methods

↑ top

### Protected Instance Methods

create_outline(root) click to toggle source

Create the PDF outline from the header elements in the TOC.

        # File lib/kramdown/converter/pdf.rb
    546 def create_outline(root)
    547   toc = ::Kramdown::Converter::Toc.convert(root).first
    548
    549   text_of_header = lambda do |el|
    550     if el.type == :text
    551       el.value
    552     else
    553       el.children.map {|c| text_of_header.call(c)}.join('')
    554     end
    555   end
    556
    557   add_section = lambda do |item, parent|
    558     text = text_of_header.call(item.value)
    559     destination = @dests[item.attr[:id]]
    560     if !parent
    561       @pdf.outline.page(:title => text, :destination => destination)
    562     else
    563       @pdf.outline.add_subsection_to(parent) do
    564         @pdf.outline.page(:title => text, :destination => destination)
    565       end
    566     end
    567     item.children.each {|c| add_section.call(c, text)}
    568   end
    569
    570   toc.children.each do |item|
    571     add_section.call(item, nil)
    572   end
    573 end

document_options(root) click to toggle source

Return a hash with options that are suitable for Prawn::Document.new.

Used in `setup_document`.

        # File lib/kramdown/converter/pdf.rb
    515 def document_options(root)
    516   {
    517     :page_size => 'A4', :page_layout => :portrait, :margin => mm2pt(20),
    518     :info => {
    519       :Creator => 'kramdown PDF converter',
    520       :CreationDate => Time.now
    521     },
    522     :compress => true, :optimize_objects => true
    523   }
    524 end

finish_document(root) click to toggle source

Used in `render_root`.

        # File lib/kramdown/converter/pdf.rb
    541 def finish_document(root)
    542   # no op
    543 end

setup_document(root) click to toggle source

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
    531 def setup_document(root)
    532   doc = Prawn::Document.new(document_options(root))
    533   doc.extend(PrawnDocumentExtension)
    534   doc.converter = self
    535   doc
    536 end