Class: Arrolio::Renderer::Pdf
- Inherits:
-
Object
- Object
- Arrolio::Renderer::Pdf
- Defined in:
- lib/arrolio/renderer/pdf.rb
Overview
Walks Output::Page and emits PDF bytes via Pdfrb. Supports
font embedding: pass font_paths: as a Hash mapping the
Style#font_name string to a TTF path, and the renderer will
subset + embed each font for the codepoints actually used
in the document.
Constant Summary collapse
- MM_TO_PT =
2.83464567
Instance Attribute Summary collapse
-
#document ⇒ Object
readonly
Returns the value of attribute document.
-
#fonts ⇒ Object
readonly
Returns the value of attribute fonts.
Instance Method Summary collapse
- #apply_metadata(metadata) ⇒ Object
-
#attach_xmp_metadata(metadata) ⇒ Object
Emit an XMP metadata packet (RDF/XML) as a stream on the catalog's /Metadata entry.
- #cover_logo_style ⇒ Object
- #header_footer_style ⇒ Object
-
#initialize ⇒ Pdf
constructor
A new instance of Pdf.
- #rasterize_svg(svg_path, png_path) ⇒ Object
- #register_image(path) ⇒ Object
- #register_logo(path) ⇒ Object
- #render(pages, io:, logo_path: nil, metadata: {}, font_paths: {}, context: nil, layout_spec: nil) ⇒ Object
-
#resolve_image_for_pdfrb(path) ⇒ Object
Pdfrb supports JPEG, PNG, and PDF — not SVG.
- #svg_to_png_cache_path(svg_path) ⇒ Object
Constructor Details
#initialize ⇒ Pdf
Returns a new instance of Pdf.
19 20 21 22 23 24 25 26 27 28 |
# File 'lib/arrolio/renderer/pdf.rb', line 19 def initialize @document = Pdfrb::Document.new @fonts = FontRegistry.new(@document) @images = {} @font_paths = {} @font_embedders = {} @font_encoders = {} @font_refs = {} @link_annotator = LinkAnnotator.new(@document) end |
Instance Attribute Details
#document ⇒ Object (readonly)
Returns the value of attribute document.
17 18 19 |
# File 'lib/arrolio/renderer/pdf.rb', line 17 def document @document end |
#fonts ⇒ Object (readonly)
Returns the value of attribute fonts.
17 18 19 |
# File 'lib/arrolio/renderer/pdf.rb', line 17 def fonts @fonts end |
Instance Method Details
#apply_metadata(metadata) ⇒ Object
44 45 46 47 48 49 50 51 52 |
# File 'lib/arrolio/renderer/pdf.rb', line 44 def () info = @document.catalog.value[:Info] info ||= @document.add({}) @document.catalog.value[:Info] = info info.value[:Title] = [:title] if [:title] info.value[:Author] = [:author] if [:author] info.value[:Creator] = 'Arrolio (Ruby)' info.value[:Producer] = 'Arrolio + Pdfrb' end |
#attach_xmp_metadata(metadata) ⇒ Object
Emit an XMP metadata packet (RDF/XML) as a stream on the catalog's /Metadata entry. PDF readers use this for Dublin Core properties alongside the legacy /Info dict.
57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/arrolio/renderer/pdf.rb', line 57 def () xmp = XmpBuilder.new().build stream = @document.add( { Type: :Metadata, Subtype: :XML, Length: xmp.bytesize }, type: Pdfrb::Model::Cos::Stream ) stream.stream = xmp @document.catalog.value[:Metadata] = Pdfrb::Model::Reference.new(stream.oid, stream.gen) rescue StandardError => e Arrolio::Logger.warn "XMP attach failed: #{e.class}: #{e.[0, 80]}" end |
#cover_logo_style ⇒ Object
137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/arrolio/renderer/pdf.rb', line 137 def cover_logo_style return @cover_logo_style if @cover_logo_style config = @layout_spec&.cover_logo_config || {} width_mm = config['width_mm'] || 35.0 ratio = config['aspect_ratio'] || (1459.0 / 1667.0) margin_mm = config['margin_mm'] || 25.5 @cover_logo_style = { width: width_mm * MM_TO_PT, height: width_mm * ratio * MM_TO_PT, margin: margin_mm * MM_TO_PT }.freeze end |
#header_footer_style ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/arrolio/renderer/pdf.rb', line 121 def return @header_footer_style if @header_footer_style config = @layout_spec&. || {} @header_footer_style = { margin_top: (config['margin_top'] || 26.5) * MM_TO_PT, margin_bottom: (config['margin_bottom'] || 26.5) * MM_TO_PT, margin_lr: (config['margin_lr'] || 25.5) * MM_TO_PT, header_offset: (config['header_offset'] || 4) * MM_TO_PT, footer_offset: (config['footer_offset'] || 4) * MM_TO_PT, font_name: config['font_name'] || 'Helvetica', font_size: config['font_size'] || 9.0, rule_width: config['rule_width'] || 0.5 }.freeze end |
#rasterize_svg(svg_path, png_path) ⇒ Object
110 111 112 113 114 115 116 117 118 119 |
# File 'lib/arrolio/renderer/pdf.rb', line 110 def rasterize_svg(svg_path, png_path) require 'open3' result = Open3.capture3('rsvg-convert', '-d', '96', '-p', '96', '-o', png_path, svg_path) return if result[2].success? Arrolio::Logger.warn "rsvg-convert failed: #{result[1]}" rescue StandardError => e Arrolio::Logger.warn "rasterize_svg failed: #{e.class}: #{e.[0, 80]}" end |
#register_image(path) ⇒ Object
79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/arrolio/renderer/pdf.rb', line 79 def register_image(path) return @images[path] if @images.key?(path) resolved = resolve_image_for_pdfrb(path) return nil unless resolved @images[path] = @document.images.add(resolved) rescue StandardError => e Arrolio::Logger.warn "register_image failed for #{path}: #{e.class}: #{e.[0, 80]}" nil end |
#register_logo(path) ⇒ Object
70 71 72 73 74 75 76 77 |
# File 'lib/arrolio/renderer/pdf.rb', line 70 def register_logo(path) return nil unless path @document.images.add(path) rescue StandardError => e Arrolio::Logger.warn "logo load failed: #{e.class}: #{e.[0, 80]}" nil end |
#render(pages, io:, logo_path: nil, metadata: {}, font_paths: {}, context: nil, layout_spec: nil) ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/arrolio/renderer/pdf.rb', line 30 def render(pages, io:, logo_path: nil, metadata: {}, font_paths: {}, context: nil, layout_spec: nil) @font_paths = font_paths.transform_keys(&:to_s) @layout_spec = layout_spec () () @logo_ref = register_logo(logo_path) (pages) pages.each { |page| render_page(page) } build_outline(context, pages) if context @document.write(io.is_a?(String) ? io : nil, io: io.is_a?(String) ? nil : io) end |
#resolve_image_for_pdfrb(path) ⇒ Object
Pdfrb supports JPEG, PNG, and PDF — not SVG. If path is an
SVG file, rasterize it to PNG via rsvg-convert and cache the
result. Returns the path to a pdfrb-compatible image file.
94 95 96 97 98 99 100 101 102 103 |
# File 'lib/arrolio/renderer/pdf.rb', line 94 def resolve_image_for_pdfrb(path) return path unless path.to_s.end_with?('.svg', '.SVG') return path unless File.exist?(path) png_path = svg_to_png_cache_path(path) return png_path if File.exist?(png_path) rasterize_svg(path, png_path) File.exist?(png_path) ? png_path : path end |
#svg_to_png_cache_path(svg_path) ⇒ Object
105 106 107 108 |
# File 'lib/arrolio/renderer/pdf.rb', line 105 def svg_to_png_cache_path(svg_path) digest = Digest::MD5.file(svg_path).hexdigest File.join(Dir.tmpdir, 'arrolio-svg-' + digest + '.png') end |