Engineering Journal
Pdf Processor
Pdf Processor

Under the Hood: Building a Correct PDF Page Assembler

2026-06-04

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 ComponentDefective ImplementationRemediated Architectural Implementation
Column Width TemplateFixed 1fr 1fr (Equal columns)Measured fraction: calc(var(--left-col) * 100%) 1fr
Zone Boundary CalculationMidpoint math (yCenter)Region top edges (Math.floor(bbox.y))
Column Rescue CheckBinary left/right checkN+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 →