<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jonson800</title>
    <description>The latest articles on DEV Community by Jonson800 (@jonson800).</description>
    <link>https://dev.to/jonson800</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4099714%2Fc4a4d101-6c55-4558-be94-6be4d45dbca3.png</url>
      <title>DEV Community: Jonson800</title>
      <link>https://dev.to/jonson800</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jonson800"/>
    <language>en</language>
    <item>
      <title>Stop OCRing Every PDF: Route It First with pdf-inspector</title>
      <dc:creator>Jonson800</dc:creator>
      <pubDate>Sat, 29 Aug 2026 05:24:16 +0000</pubDate>
      <link>https://dev.to/jonson800/stop-ocring-every-pdf-route-it-first-with-pdf-inspector-501g</link>
      <guid>https://dev.to/jonson800/stop-ocring-every-pdf-route-it-first-with-pdf-inspector-501g</guid>
      <description>&lt;p&gt;OCR is often the most expensive and slowest step in a document-ingestion pipeline. The frustrating part is that many PDFs already contain usable text, yet a naive pipeline sends every document through OCR anyway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/firecrawl/pdf-inspector" rel="noopener noreferrer"&gt;&lt;code&gt;pdf-inspector&lt;/code&gt;&lt;/a&gt; takes a better approach: classify first, extract native text when possible, and route only the pages that actually need OCR.&lt;/p&gt;

&lt;h2&gt;
  
  
  The routing pattern
&lt;/h2&gt;

&lt;p&gt;The core decision is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PDF arrives
  ↓
Classify the document and its pages
  ├─ native text available → extract locally → Markdown
  └─ text missing/broken   → route those pages to OCR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That small decision can remove a large amount of unnecessary OCR work from RAG ingestion, invoice processing, research-paper parsing, and document search.&lt;/p&gt;

&lt;p&gt;The library classifies PDFs as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TextBased&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Scanned&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ImageBased&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Mixed&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also returns a confidence score and the specific pages that need OCR. A 40-page report with one scanned appendix does not have to become a 40-page OCR job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start in Python
&lt;/h2&gt;

&lt;p&gt;Install the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pdf-inspector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then process a PDF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pdf_inspector&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdf_inspector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pdf_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages_needing_ocr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For selective OCR, the native package also exposes an OCR-aware pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ocr_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdf_inspector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_pdf_with_ocr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ocr_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages_routed_to_ocr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OCR runtime remains separate and is only touched when a page is routed to it. That keeps the default extraction path lightweight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Node.js and browser support
&lt;/h2&gt;

&lt;p&gt;The same idea is available for Node.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @firecrawl/pdf-inspector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;processPdf&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@firecrawl/pdf-inspector&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document.pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;processPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pdfType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also a WebAssembly package for running the Rust parser locally in a browser or Web Worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @firecrawl/pdf-inspector-wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful when documents should not be uploaded to a parsing service just to determine whether they contain native text.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the extractor preserves
&lt;/h2&gt;

&lt;p&gt;Classification is only half the project. For text-based PDFs, the extractor attempts to preserve structure such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;headings derived from font-size tiers&lt;/li&gt;
&lt;li&gt;bold and italic text&lt;/li&gt;
&lt;li&gt;numbered and bulleted lists&lt;/li&gt;
&lt;li&gt;code blocks detected from monospace fonts&lt;/li&gt;
&lt;li&gt;tables detected from drawing rectangles and text alignment&lt;/li&gt;
&lt;li&gt;multi-column reading order&lt;/li&gt;
&lt;li&gt;links, page breaks, captions, and common font encodings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The output is Markdown, which makes the library convenient for search indexing and LLM/RAG pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  How classification works
&lt;/h2&gt;

&lt;p&gt;At a high level, the detector inspects PDF content streams for text operators such as &lt;code&gt;Tj&lt;/code&gt; and &lt;code&gt;TJ&lt;/code&gt;, and image operators such as &lt;code&gt;Do&lt;/code&gt;. It can scan all pages, stop early, sample a large document, or inspect a caller-provided page set.&lt;/p&gt;

&lt;p&gt;This is a routing signal, not a promise that every PDF will be perfectly parsed. PDFs with broken encodings, text converted to vector paths, or extremely complex layouts may still need OCR or a specialized parser. The library explicitly reports encoding problems so callers can fall back instead of silently accepting bad text.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the benchmark numbers
&lt;/h2&gt;

&lt;p&gt;The project publishes a reproducible benchmark against a 200-document corpus. Its July 2026 results report strong reading-order and table scores as well as fast local processing. Those are project-published measurements on specified hardware—not a universal latency guarantee—so benchmark your own document mix before committing to production thresholds.&lt;/p&gt;

&lt;p&gt;The more durable takeaway is architectural: &lt;strong&gt;OCR should be a fallback chosen per page, not the default chosen per file.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical production rule
&lt;/h2&gt;

&lt;p&gt;A conservative router might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdf_inspector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pdf_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text_based&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;store_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;send_pages_to_ocr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages_needing_ocr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your threshold should depend on the cost of a false positive. A casual knowledge base can tolerate more extraction noise than a legal or financial workflow.&lt;/p&gt;

&lt;p&gt;If your pipeline currently OCRs every incoming PDF, classification-first routing is a small change with a clear operational payoff.&lt;/p&gt;




&lt;p&gt;The longer version and implementation notes are available on &lt;a href="https://toolgenix.nxtniche.com/posts/article-2026-08-03-qr/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=traffic_pilot_202608&amp;amp;utm_content=pdf_inspector" rel="noopener noreferrer"&gt;ToolGenix&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>rust</category>
      <category>ocr</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
