Included Modules

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
# File lib/kramdown/converter/pdf.rb, line 46
def initialize(root, options)
  super
  @stack = []
  @dests = {}
end

Public Instance Methods

apply_template_after?() click to toggle source

Returns false.

# File lib/kramdown/converter/pdf.rb, line 59
def apply_template_after?
  false
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, line 54
def apply_template_before?
  true
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, line 70
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

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, line 86
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 ↑ top

Protected Instance Methods

a_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 331
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
abbreviation_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 396
def abbreviation_options(el, opts)
  {}
end
blockquote_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 179
def blockquote_options(el, opts)
  {:styles => [:italic]}
end
br_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 355
def br_options(el, opts)
  {}
end
codeblock_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 267
def codeblock_options(el, opts)
  {
    :font => 'Courier', :color => '880000',
    :bottom_padding => opts[:size]
  }
end
codespan_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 347
def codespan_options(el, opts)
  {:font => 'Courier', :color => '880000'}
end
dd_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 237
def dd_options(el, opts)
  {}
end
dl_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 221
def dl_options(el, opts)
  {}
end
dt_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 229
def dt_options(el, opts)
  {:styles => (opts[:styles] || []) + [:bold], :bottom_padding => 0}
end
em_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 319
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
entity_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 388
def entity_options(el, opts)
  {}
end
header_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 115
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
hr_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 257
def hr_options(el, opts)
  {:top_padding => opts[:size], :bottom_padding => opts[:size]}
end
img_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 404
def img_options(el, opts)
  {}
end
li_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 213
def li_options(el, opts)
  {}
end
math_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 245
def math_options(el, opts)
  {}
end
ol_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 200
def ol_options(el, opts)
  {:bottom_padding => opts[:size]}
end
p_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 127
def p_options(el, opts)
  bpad = (el.options[:transparent] ? opts[:leading] : opts[:size])
  {:align => :justify, :bottom_padding => bpad}
end
render_a(el, opts) click to toggle source
Alias for: render_em
render_abbreviation(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 400
def render_abbreviation(el, opts)
  text_hash(el.value, opts)
end
render_blockquote(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 183
def render_blockquote(el, opts)
  @pdf.indent(mm2pt(10), mm2pt(10)) { inner(el, opts) }
end
render_br(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 359
def render_br(el, opts)
  text_hash("\n", opts, false)
end
render_codeblock(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 274
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
render_codespan(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 351
def render_codespan(el, opts)
  text_hash(el.value, opts)
end
render_dd(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 241
def render_dd(el, opts)
  @pdf.indent(mm2pt(10)) { inner(el, opts) }
end
render_dl(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 225
def render_dl(el, opts)
  inner(el, opts)
end
render_dt(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 233
def render_dt(el, opts)
  render_padded_and_formatted_text(el, opts)
end
render_em(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 341
def render_em(el, opts)
  inner(el, opts)
end
Also aliased as: render_strong, render_a
render_entity(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 392
def render_entity(el, opts)
  text_hash(el.value.char, opts)
end
render_header(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 123
def render_header(el, opts)
  render_padded_and_formatted_text(el, opts)
end
render_hr(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 261
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
render_li(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 217
def render_li(el, opts)
  inner(el, opts)
end
render_math(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 249
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
render_ol(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 204
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
render_p(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 132
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
render_root(root, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 107
def render_root(root, opts)
  @pdf = setup_document(root)
  inner(root, root_options(root, opts))
  create_outline(root)
  finish_document(root)
  @pdf.render
end
render_smart_quote(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 367
def render_smart_quote(el, opts)
  text_hash(smart_quote_entity(el).char, opts)
end
render_standalone_image(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 140
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
render_strong(el, opts) click to toggle source
Alias for: render_em
render_table(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 284
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
render_text(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 315
def render_text(el, opts)
  text_hash(el.value.to_s, opts)
end
render_typographic_sym(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 375
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
render_ul(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 191
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
root_options(root, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 103
def root_options(root, opts)
  {:font => 'Times-Roman', :size => 12, :leading => 2}
end
smart_quote_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 363
def smart_quote_options(el, opts)
  {}
end
strong_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 327
def strong_options(el, opts)
  {:styles => (opts[:styles] || []) + [:bold]}
end
table_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 280
def table_options(el, opts)
  {:bottom_padding => opts[:size]}
end
text_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 311
def text_options(el, opts)
  {}
end
typographic_sym_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 371
def typographic_sym_options(el, opts)
  {}
end
ul_options(el, opts) click to toggle source
# File lib/kramdown/converter/pdf.rb, line 187
def ul_options(el, opts)
  {:bottom_padding => opts[:size]}
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, line 611
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_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, line 591
def render_padded_and_formatted_text(el, opts)
  with_block_padding(el, opts) { @pdf.formatted_text(inner(el, opts), block_hash(opts)) }
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, line 599
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
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, line 583
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 ↑ top

These methods are used, for example, to up the needed Prawn::Document instance or to create a PDF outline.

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, line 545
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
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, line 514
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
finish_document(root) click to toggle source

Used in render_root.

# File lib/kramdown/converter/pdf.rb, line 540
def finish_document(root)
  # no op
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, line 530
def setup_document(root)
  doc = Prawn::Document.new(document_options(root))
  doc.extend(PrawnDocumentExtension)
  doc.converter = self
  doc
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.