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