Module: Pikuri::Extractors::PDF
- Defined in:
- lib/pikuri/extractors/pdf.rb
Overview
PDF → text extractor over the pdf-reader gem: each page emits a
"--- Page N ---" marker line then its text, blocks joined by
single newlines. The markers carry page provenance downstream (a
cited answer, a vectordb hit's page number). A page with no
extractable text contributes nothing — no marker — so a fully
scanned PDF extracts to the empty String, a deliberate silent
skip callers detect by length. No OCR here: pdf-reader gives
clean text from digitally-generated PDFs, nothing from scans.
Why a separate gem
Split from pikuri-core to keep pdf-reader's five-gem dependency
tail (Ascii85, afm, hashery, ruby-rc4, ttfunk) out of the core
audit tree — five gems for one format nothing else in core uses.
Hosts opt in with one PDF.register call. Being in-process, it pages
lazily (PDF.extract_lines parses pages on demand) — a property a
subprocess converter structurally cannot have. (The full
this-gem-vs-pikuri-extractors PDF trade-off is in
pikuri-extractors/DESIGN.md.)
Registration is explicit
Requiring the gem registers nothing; a host opts in with
Pikuri::Extractors::PDF.register, which front-inserts into the
registry (unlike pikuri-extractors' before-terminal insert)
because the %PDF- magic sniff is the registry's strongest
signal — it must beat +HTML+'s content-type match so a PDF under a
lying header still extracts, and it never misfires on text.
Class Method Summary collapse
-
.extract(io) ⇒ String
Render the PDF behind
ioas plain text, one +"--- Page N ---"+-headed block per page that carries text. -
.extract_lines(io) ⇒ Enumerator<String>
The lazy line stream behind PDF.extract: a marker line per text-carrying page, then that page's lines.
-
.kind ⇒ Symbol
Pikuri::Extractor::Page#kind tag.
- .matches?(sample:, content_type:) ⇒ Boolean
-
.register ⇒ Module
Insert this extractor at the front of
Pikuri::Extractor.registry(see "Registration is explicit" above for why the front).
Class Method Details
.extract(io) ⇒ String
Render the PDF behind io as plain text, one
+"--- Page N ---"+-headed block per page that carries text.
Defined as extract_lines.to_a.join so the two duck-type
shapes cannot drift apart.
72 73 74 |
# File 'lib/pikuri/extractors/pdf.rb', line 72 def self.extract(io) extract_lines(io).to_a.join("\n") end |
.extract_lines(io) ⇒ Enumerator<String>
The lazy line stream behind extract: a marker line per
text-carrying page, then that page's lines. pdf-reader
parses a page's content stream only when Page#text is
called, so a consumer that stops early (the
Pikuri::Extractor.extract_paged window) never pays for the
pages past its window.
The three typed pdf-reader parse failures — broken xrefs
(PDF::Reader::MalformedPDFError), bad page refs
(PDF::Reader::InvalidPageError), encrypted/XFA
(PDF::Reader::UnsupportedFeatureError) — are document
properties the LLM can react to, so they re-raise as
Pikuri::Extractor::Error, from inside the enumerator: at
consumption time, which for a broken xref means the first
next. Other pdf-reader errors are bugs and crash loud.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/pikuri/extractors/pdf.rb', line 99 def self.extract_lines(io) Enumerator.new do |lines| ::PDF::Reader.new(io).pages.each_with_index do |page, idx| text = page.text.strip next if text.empty? lines << "--- Page #{idx + 1} ---" text.split("\n").each { |line| lines << line } end rescue ::PDF::Reader::MalformedPDFError, ::PDF::Reader::InvalidPageError, ::PDF::Reader::UnsupportedFeatureError => e raise Pikuri::Extractor::Error, "PDF rendering failed: #{e.class.name.split('::').last}: #{e.message}" end end |
.kind ⇒ Symbol
Returns Pikuri::Extractor::Page#kind tag.
48 49 50 |
# File 'lib/pikuri/extractors/pdf.rb', line 48 def self.kind :pdf end |
.matches?(sample:, content_type:) ⇒ Boolean
56 57 58 |
# File 'lib/pikuri/extractors/pdf.rb', line 56 def self.matches?(sample:, content_type:) content_type == 'application/pdf' || sample.start_with?(FileType::PDF_MAGIC) end |
.register ⇒ Module
Insert this extractor at the front of
Pikuri::Extractor.registry (see "Registration is explicit"
above for why the front). Idempotent.
41 42 43 44 45 |
# File 'lib/pikuri/extractors/pdf.rb', line 41 def self.register registry = Pikuri::Extractor.registry registry.unshift(self) unless registry.include?(self) self end |