Class: Pdf::Utility::PageRasterizer

Inherits:
Object
  • Object
show all
Defined in:
app/services/pdf/utility/page_rasterizer.rb

Overview

Service object: rasterize a PDF's pages to images for vision-LLM input.

A PDF whose embedded subset CID fonts carry a broken (or absent) ToUnicode
CMap renders perfectly on-screen but extracts as mojibake — an em-dash comes
out as "4", the ohm sign as "«", an inch-prime as "3", fractions as ""/"".
Sending the rendered page image instead of the PDF's text layer lets the
model read the glyphs as drawn, sidestepping the corrupted text entirely
(Sunny conv 3547).

Pages render through the bounded Poppler subprocess used by Dragonfly PDF
thumbnails. Libvips only resizes the resulting PNG, so the process-wide
untrusted-loader block remains enabled. The caller owns the returned
Tempfiles (e.g. AssistantChatWorker tracks them in +@attachment_tempfiles+
and unlinks them after the turn).

Constant Summary collapse

DEFAULT_DPI =

Render resolution. 150 DPI on US Letter ≈ 1650px on the long edge; the result
is then clamped to MAX_EDGE so we never exceed the vision API's effective
resolution (Anthropic downscales past ~1568px anyway) and PNGs stay modest.

150
MAX_EDGE =

Long-edge clamp in pixels (matches Anthropic's image-downscale threshold).

1568
DEFAULT_MAX_PAGES =

Hard cap on pages rendered, to bound token cost on large PDFs. A 9-page sell
sheet renders fully; a 200-page contract renders the first 20.

20

Class Method Summary collapse

Class Method Details

.render(pdf_path, dpi: DEFAULT_DPI, max_pages: DEFAULT_MAX_PAGES) ⇒ Array<Tempfile>

Render up to +max_pages+ pages of a PDF to PNG Tempfiles.

Parameters:

  • pdf_path (String)

    local path to a PDF file

  • dpi (Integer) (defaults to: DEFAULT_DPI)

    render resolution

  • max_pages (Integer) (defaults to: DEFAULT_MAX_PAGES)

    cap on pages rendered

Returns:

  • (Array<Tempfile>)

    one PNG Tempfile per rendered page (possibly
    empty if nothing could be rendered). Caller owns cleanup.

Raises:

  • (ArgumentError)

    when +pdf_path+ is nil or missing



39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'app/services/pdf/utility/page_rasterizer.rb', line 39

def render(pdf_path, dpi: DEFAULT_DPI, max_pages: DEFAULT_MAX_PAGES)
  require 'vips'
  raise ArgumentError, "pdf_path does not exist: #{pdf_path.inspect}" unless pdf_path && File.exist?(pdf_path)

  total    = page_count(pdf_path)
  render_n = [total, max_pages].min
  if defined?(Rails)
    Rails.logger.info { "[PageRasterizer] Rendering #{render_n}/#{total} page(s) of #{pdf_path} at #{dpi} DPI" }
    Rails.logger.warn { "[PageRasterizer] PDF has #{total} pages; capping at #{max_pages}" } if total > max_pages
  end

  (0...render_n).filter_map { |i| render_page(pdf_path, i, dpi) }
end