Class: Carve::Hexapdf::Renderer

Inherits:
Object
  • Object
show all
Defined in:
lib/carve/hexapdf/renderer.rb

Overview

Walks a Carve AST (as produced by Carve.parse) and draws it onto a HexaPDF::Composer, producing a laid-out PDF document.

Block nodes become HexaPDF boxes (text, list, table, container, image); inline nodes become the multi-part styled "runs" that HexaPDF::Composer#formatted_text consumes. Emphasis maps to font variants and text decorations, inline code to a monospace font, links to a colored run with a URI overlay.

Math and diagram fences are rendered through optional renderers: callables (which return image bytes); without a matching renderer they degrade to their monospace source. The renderer never raises on an unknown or unsupported node - it degrades to text/children so a document always renders.

Constant Summary collapse

BLOCK_GAP =
8
RENDERER_PROPS =

Style properties this renderer consumes itself. They are not HexaPDF style properties, and the style chain hands each of them down to its nested keys: table.caption inherits table's :cell_padding, figure.group.caption inherits figure.group's column properties. Splatting one into a text box raises NoMethodError, which loses the whole document, so a style destined for a text box drops them (+text_style+). :box is dropped by style_for already.

%i[
  cell_padding column_gap min_column_width title_margin
  definition_indent item_spacing content_indentation
].freeze
DIAGRAM_LANGS =

Code-fence languages that map to a diagram renderer key.

{
  "mermaid" => :mermaid,
  "dot" => :graphviz,
  "graphviz" => :graphviz,
  "chart" => :chart,
  "vega" => :chart,
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(composer, base_font: nil, code_font: nil, link_color: nil, highlight_color: nil, styles: nil, renderers: nil) ⇒ Renderer

Returns a new instance of Renderer.

Parameters:

  • renderers (Hash) (defaults to: nil)

    optional callables that turn a construct's source into raster image bytes (PNG/JPG). Keys: :math -> callable(tex_string, display_bool); +:mermaid+/+:graphviz+/+:chart+ -> callable(source_string). Each callable returns the image bytes as a String, or a Hash with :bytes and optional +:width+/+:height+ (points) to control the drawn size of high-DPI rasters. Any other return, or a missing key, degrades the construct to its source.



56
57
58
59
60
61
62
63
64
65
# File 'lib/carve/hexapdf/renderer.rb', line 56

def initialize(composer, base_font: nil, code_font: nil,
               link_color: nil, highlight_color: nil, styles: nil, renderers: nil)
  @c = composer
  @layout = composer.document.layout
  @styles = StyleMap.new(style_sugar(base_font: base_font, code_font: code_font,
                                     link_color: link_color,
                                     highlight_color: highlight_color,
                                     styles: styles))
  @renderers = renderers || {}
end

Instance Method Details

#block(node, target) ⇒ Object

---- block dispatch ------------------------------------------------



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/carve/hexapdf/renderer.rb', line 79

def block(node, target)
  case node[:type]
  # A definition is RELOCATED, not rendered in place - it belongs in the
  # endnote section, which is what the HTML renderer does with it too.
  # Handled HERE rather than by filtering the root's children, because a
  # definition written inside a container stays inside it (the engines
  # differ on hoisting), and a root-level filter would render that one on
  # the page and again in the endnotes.
  when "footnote"         then nil
  when "heading"          then heading(node, target)
  when "paragraph"        then paragraph(node, target)
  when "code_block"       then code_block(node, target)
  when "list"             then list(node, target)
  when "block_quote"      then block_quote(node, target)
  when "table"            then table(node, target)
  when "thematic_break"   then thematic_break(target)
  when "div"              then container_of(node[:children], target)
  when "admonition"       then admonition(node, target)
  when "definition_list"  then definition_list(node, target)
  when "figure"           then figure(node, target)
  when "figure_group"     then figure_group(node, target)
  when "block_image", "image" then image_block(node, target)
  when "block_extension"  then container_of(node[:children], target)
  when "raw_block", "comment", "abbreviation_def"
    # No meaningful PDF form - drop.
  else
    if inline_children?(node[:children])
      emit_paragraph(node[:children], target)
    elsif node[:children]
      Array(node[:children]).each { |ch| block(ch, target) }
    end
  end
end

#render_document(doc) ⇒ Object



67
68
69
70
71
72
73
74
75
# File 'lib/carve/hexapdf/renderer.rb', line 67

def render_document(doc)
  # Keys arrive as Symbols (symbolized JSON) while node :id is a String.
  @footnote_defs = collect_footnote_defs(doc)
  @footnotes = []
  @footnote_numbers = {}
  Array(doc[:children]).each { |node| block(node, @c) }
  render_footnotes(@c)
  @c
end