Engineering Journal
Pdf Processor
Pdf Processor

The fill rate gate that let prose through

2026-07-09

TLDR

Stream table detection uses fill-rate metrics (occupied grid cells divided by total grid cells) to reject false positives. However, when PDF encoders store text as individual word spans, a single dominant left-margin alignment combined with coincidental word positions creates multiple "column anchors." This inflates the cell count, allowing prose paragraphs to pass the fill-rate gate. Adding a Column Anchor Quality Gate, requiring column anchors to appear across a minimum percentage of rows, eliminates false positives.
Cell Detection MetricTarget EvaluationVulnerabilityGate Optimization
Global Fill RatetaggedCells / (rows * cols)Skewed by single dominant marginPasses prose paragraphs with word spans
Anchor Quality GateRow count presence per anchorRequires broad vertical presenceRejects false word-span column clusters

Problem statement: the prose fill-rate defect

When processing PDF documents generated with granular word-level spans (where every word has an independent X coordinate), stream table detectors discover coincidental X alignments across lines.

A 16-line prose paragraph with a dominant left margin at $X=64\text{px}$ and 8 coincidental word-position alignments evaluated as follows:

Candidate Stream Table:
  • Total Bands (Rows): 16
  • Discovered Column Anchors: 9
  • Anchor Band Counts: [14, 2, 2, 2, 2, 2, 2, 2, 2]
  • Calculated Fill Rate: 0.304 (Exceeds 0.30 threshold!)
Because 14 of 16 rows matched the left-margin anchor, the total item count pushed the global fill rate above 0.30, classifying standard body paragraphs as borderless stream tables.

Technical failure mode: distribution skew in fill-rate math

Global fill-rate calculations measure overall grid density without evaluating column distribution:

// DEFECTIVE IMPLEMENTATION: Global fill rate evaluation only
const totalPossibleCells = rowBands.length * columnAnchors.length;
const fillRate = occupiedCells.length / totalPossibleCells;

if (fillRate >= 0.30) { // Passes prose paragraphs with single dominant left margins! return promoteToStreamTable(rowBands, columnAnchors); }

A single column anchor appearing in 14 rows, combined with 8 noise anchors appearing in only 2 rows, satisfies the global density ratio despite lacking true multi-column structure.


The fix: column anchor quality gating

Filter discovered column anchors by requiring them to span across a minimum percentage of document rows before evaluating table validity:

// REFACTORED: Column Anchor Quality Gate
function validateStreamTableAnchors(rowBands, columnAnchors) {
  // 1. Calculate minimum required row presence threshold
  const minRowPresence = rowBands.length <= 5 
    ? rowBands.length 
    : Math.max(3, Math.floor(rowBands.length * 0.25));

// 2. Count distinct row presence per column anchor const qualifiedAnchors = columnAnchors.filter(anchor => { const distinctRowIndices = new Set(anchor.items.map(item => item.bandIndex)); return distinctRowIndices.size >= minRowPresence; });

// 3. Require at least half of the discovered anchors to qualify const requiredQualifiedCount = Math.ceil(columnAnchors.length / 2); if (qualifiedAnchors.length < requiredQualifiedCount) { return null; // Reject false stream table candidate }

return qualifiedAnchors; }

For the failing prose paragraph:

minRowPresence = Math.max(3, Math.floor(16 0.25)) = 4
Rule of thumb: Combine global grid fill-rate thresholds with column anchor quality gates to prevent single dominant margins from misclassifying prose text as stream tables.

Read this post in the full Engineering Journal →