Pdf Processor
Under the Hood: Building a Correct PDF Page Assembler
TLDR
Assembling classified PDF spatial regions into semantically reflowable HTML requires three critical architectural mechanisms: driving CSS Grid column templates using measured gutter split fractions (calc(var(--left-col) * 100%) 1fr), establishing zone membership boundaries using region top edges (bbox.y) rather than text midpoints (yCenter), and extending full-width index rescue checks to support 3-column layouts.
| Page Assembly Component | Defective Implementation | Remediated Architectural Implementation |
|---|---|---|
| Column Width Template | Fixed 1fr 1fr (Equal columns) | Measured fraction: calc(var(--left-col) * 100%) 1fr |
| Zone Boundary Calculation | Midpoint math (yCenter) | Region top edges (Math.floor(bbox.y)) |
| Column Rescue Check | Binary left/right check | N+1 boundary checks ([-Infinity, ...splits, Infinity]) |
Technical architecture & implementation
1. Dynamic measured column fractions
Pass measured split ratios to CSS Grid containers via custom properties:// Calculate column split fraction relative to total viewport width
export function createProportionalZoneStyle(columnSplits, viewportWidth) {
if (columnSplits.length === 1) {
const leftFraction = columnSplits[0].x / viewportWidth;
return style="--left-col: ${leftFraction.toFixed(4)};";
}
return '';
}
/ Apply measured proportional grid template columns /
.pdf-zone--cols-2 {
display: grid;
grid-template-columns: calc(var(--left-col, 0.5) * 100%) 1fr;
column-gap: 20px;
}
2. Top-Edge zone boundary invariant
Use region top-edge Y-coordinates (bbox.y) to determine zone membership, eliminating sub-pixel midpoint rounding errors:
// Determine zone Y-boundaries using region top edges
export function filterZoneRegions(renderedRegions, zoneY0, zoneY1) {
return renderedRegions.filter(region => {
const regionTopY = Math.round(region.bbox?.y ?? region.yCenter);
return regionTopY >= zoneY0 && regionTopY < zoneY1;
});
}
3. N+1 column boundary rescue check
Support arbitrary column counts (1, 2, or 3 columns) by constructing $N+1$ boundary pairs for full-width rescue evaluation:export function isItemInsideSingleColumn(itemVx, itemWidth, columnSplits, tolerancePx = 4) {
const itemEnd = itemVx + itemWidth;
const colBoundaries = [-Infinity, ...columnSplits.map(s => s.x), Infinity];
// Check if item fits completely inside ANY single column boundary pair return colBoundaries.slice(0, -1).some((loBoundary, idx) => { const hiBoundary = colBoundaries[idx + 1]; return itemVx >= loBoundary - tolerancePx && itemEnd <= hiBoundary + tolerancePx; }); }
Rule of thumb: Drive CSS Grid column templates using measured gutter split fractions, and compute zone boundaries using region top edges (bbox.y).
Read this post in the full Engineering Journal →