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
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
Returns false
.
# File lib/kramdown/converter/pdf.rb 60 def apply_template_after? 61 false 62 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 55 def apply_template_before? 56 true 57 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 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
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
↑ topProtected Instance Methods
# 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
# File lib/kramdown/converter/pdf.rb 397 def abbreviation_options(el, opts) 398 {} 399 end
# File lib/kramdown/converter/pdf.rb 180 def blockquote_options(el, opts) 181 {:styles => [:italic]} 182 end
# File lib/kramdown/converter/pdf.rb 356 def br_options(el, opts) 357 {} 358 end
# 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
# File lib/kramdown/converter/pdf.rb 348 def codespan_options(el, opts) 349 {:font => 'Courier', :color => '880000'} 350 end
# File lib/kramdown/converter/pdf.rb 238 def dd_options(el, opts) 239 {} 240 end
# File lib/kramdown/converter/pdf.rb 222 def dl_options(el, opts) 223 {} 224 end
# File lib/kramdown/converter/pdf.rb 230 def dt_options(el, opts) 231 {:styles => (opts[:styles] || []) + [:bold], :bottom_padding => 0} 232 end
# 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
# File lib/kramdown/converter/pdf.rb 389 def entity_options(el, opts) 390 {} 391 end
# 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
# File lib/kramdown/converter/pdf.rb 258 def hr_options(el, opts) 259 {:top_padding => opts[:size], :bottom_padding => opts[:size]} 260 end
# File lib/kramdown/converter/pdf.rb 405 def img_options(el, opts) 406 {} 407 end
# File lib/kramdown/converter/pdf.rb 214 def li_options(el, opts) 215 {} 216 end
# File lib/kramdown/converter/pdf.rb 246 def math_options(el, opts) 247 {} 248 end
# File lib/kramdown/converter/pdf.rb 201 def ol_options(el, opts) 202 {:bottom_padding => opts[:size]} 203 end
# 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
# File lib/kramdown/converter/pdf.rb 401 def render_abbreviation(el, opts) 402 text_hash(el.value, opts) 403 end
# File lib/kramdown/converter/pdf.rb 184 def render_blockquote(el, opts) 185 @pdf.indent(mm2pt(10), mm2pt(10)) { inner(el, opts) } 186 end
# File lib/kramdown/converter/pdf.rb 360 def render_br(el, opts) 361 text_hash("\n", opts, false) 362 end
# 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
# File lib/kramdown/converter/pdf.rb 352 def render_codespan(el, opts) 353 text_hash(el.value, opts) 354 end
# File lib/kramdown/converter/pdf.rb 242 def render_dd(el, opts) 243 @pdf.indent(mm2pt(10)) { inner(el, opts) } 244 end
# File lib/kramdown/converter/pdf.rb 226 def render_dl(el, opts) 227 inner(el, opts) 228 end
# File lib/kramdown/converter/pdf.rb 234 def render_dt(el, opts) 235 render_padded_and_formatted_text(el, opts) 236 end
# File lib/kramdown/converter/pdf.rb 342 def render_em(el, opts) 343 inner(el, opts) 344 end
# File lib/kramdown/converter/pdf.rb 393 def render_entity(el, opts) 394 text_hash(el.value.char, opts) 395 end
# File lib/kramdown/converter/pdf.rb 124 def render_header(el, opts) 125 render_padded_and_formatted_text(el, opts) 126 end
# 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
# File lib/kramdown/converter/pdf.rb 218 def render_li(el, opts) 219 inner(el, opts) 220 end
# 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
# 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
# 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
# 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
# File lib/kramdown/converter/pdf.rb 368 def render_smart_quote(el, opts) 369 text_hash(smart_quote_entity(el).char, opts) 370 end
# 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
# 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
# File lib/kramdown/converter/pdf.rb 316 def render_text(el, opts) 317 text_hash(el.value.to_s, opts) 318 end
# 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
# 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
# File lib/kramdown/converter/pdf.rb 104 def root_options(root, opts) 105 {:font => 'Times-Roman', :size => 12, :leading => 2} 106 end
# File lib/kramdown/converter/pdf.rb 364 def smart_quote_options(el, opts) 365 {} 366 end
# File lib/kramdown/converter/pdf.rb 328 def strong_options(el, opts) 329 {:styles => (opts[:styles] || []) + [:bold]} 330 end
# File lib/kramdown/converter/pdf.rb 281 def table_options(el, opts) 282 {:bottom_padding => opts[:size]} 283 end
# File lib/kramdown/converter/pdf.rb 312 def text_options(el, opts) 313 {} 314 end
# File lib/kramdown/converter/pdf.rb 372 def typographic_sym_options(el, opts) 373 {} 374 end
# File lib/kramdown/converter/pdf.rb 188 def ul_options(el, opts) 189 {:bottom_padding => opts[:size]} 190 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 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 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
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
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
↑ topProtected Instance Methods
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
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
Used in render_root
.
# File lib/kramdown/converter/pdf.rb 541 def finish_document(root) 542 # no op 543 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 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