惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
Stack Overflow Blog
Stack Overflow Blog
L
LangChain Blog
Jina AI
Jina AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
雷峰网
雷峰网
T
Tailwind CSS Blog
MongoDB | Blog
MongoDB | Blog
博客园 - 【当耐特】
博客园 - 聂微东
V
Visual Studio Blog
博客园_首页
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
Apple Machine Learning Research
Apple Machine Learning Research
阮一峰的网络日志
阮一峰的网络日志
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
F
Fortinet All Blogs
C
Check Point Blog
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Stop Parsing PDFs at Render Time: A Better Architecture f...
Bonzai2Carn · 2026-06-16 · via DEV Community

TLDR: The reason most frontend PDF extraction is wrong is that developers try to infer document structure from the rendered visual output instead of from the operator stream. De Casteljau subdivision, pixel-based column detection, and raster-scan zone boundaries are workarounds for not reading the data correctly in the first place.


PDF Processor

The Industry Default Is Backwards

Here is what most frontend PDF tools do: render the page to a canvas at some scale, read text positions from getTextContent(), optionally run a computer vision model over the canvas, then try to infer whether those text positions form columns, tables, or lists based on pixel proximity.

This is backwards. The PDF already contains explicit structural information in the operator stream. The table you are trying to infer from pixel columns is drawn with literal path operators: moveTo, lineTo, rectangle, stroke. The zone boundaries you are trying to detect from text Y-positions are encoded in the CTM stack and fill operations. You are trying to reconstruct from the rendered artifact what was always explicit in the source.


The De Casteljau Example

I proposed de Casteljau subdivision for Bezier bounding boxes in my own architecture document and then rejected it during stress testing.

De Casteljau is a subdivision algorithm. You split the curve at its midpoint, recursively subdivide both halves, and stop when the control point hull is small enough. Then you use that hull as an approximation of the bounding box.

This is the right algorithm when you need to render the curve or find the nearest point on it. It is the wrong algorithm for bounding boxes because:

  1. You choose a stopping tolerance. Too loose and the bbox is wrong for diagonal curves. Too tight and you recurse deeply on every curve, including simple arcs.
  2. The segments you produce go through downstream filtering. A minLen guard eats the short segments from the recursion tail. Now your bbox is even more wrong.
  3. There is no reason for any of this. The analytical solution is one application of the quadratic formula. It is exact. It does not recurse. It does not allocate segments.

The correct algorithm has been in every graphics textbook for 40 years. We still reach for subdivision because it feels intuitive: you can watch it work visually, you can tune the tolerance, it feels like you understand what it is doing. But "feels intuitive" is not a correctness argument.


The Zone Boundary Example

The page assembly design philosophy document I updated this session contains three zone detection bugs. One of them: _detectAutoZones computes zone boundaries as midpoints between yCenter values of adjacent region groups.

The yCenter of a region is the midpoint of its bounding box. The midpoint between two region yCenter values is some coordinate inside the gap between them, maybe. But a region's ry (used in the zone filter) is Math.round(region.yCenter), not the region's top edge. Sub-pixel rounding can place a region in the wrong zone.

The fix is simple: use bbox.y (the region's actual top edge) for zone boundaries, and compare against rBboxY (the region's top edge rounded to pixel) in the filter. Every region's top edge is the natural zone membership criterion, because it is where the region starts.

The midpoint heuristic exists because it sounds reasonable: "put the boundary halfway between the two groups." But zone membership should be determined by where content starts, not by a geometric midpoint between content centers. The former is structural. The latter is visual.


What the Correct Approach Requires

It requires reading the operator stream correctly, not just getTextContent(). It requires building a CTM stack and tracking matrix state per subpath. It requires classifying subpaths geometrically (RECT vs FREE_PATH) before doing any analysis. It requires emitting canonical segments with provenance, not just (x1, y1, x2, y2) tuples.

This is more work than pixel-based heuristics. But it produces deterministic output for the same PDF across renders, across scale factors, across pdfjs versions. Pixel-based output is not deterministic: it depends on anti-aliasing, subpixel positioning, font hinting.

A PDF extractor that gives you different columns on the same document at 100% and 150% zoom is not extracting structure. It is pattern-matching visual artifacts and calling it structure.


The Uncomfortable Implication

If you are building a PDF extraction tool for production use and you are not parsing the operator stream, you are building a demo. You can get it to work on the 20 PDFs in your test suite. You cannot get it to work reliably on the PDFs your users will upload.

The path through the operator stream is harder. It requires understanding the CTM, the fill/stroke state machine, the constructPath compound op, the difference between setFillRGBColor and setFillColorN, the fact that curveTo2 and curveTo3 are shorthand variants with implicit control points. These are not documented anywhere obvious; you read the PDF spec and the pdfjs source.

But you only have to understand it once. Then it works for every PDF, not just the ones you tested on.