Engineering Journal
Pdf Processor
Pdf Processor

A Containment Check Passes Because the List It Checks Is Empty

2026-05-31

TLDR

A containment check designed to filter out horizontal line segments inside paragraph text was silently failing, inserting unwanted <hr> divider rules in extracted HTML output. The math (containsBbox(region, segment)) was correct, but the divider classifier ran before paragraph text regions were generated. Moving the divider detection call to execute after text classification resolved the defect immediately without changing a single line of detection logic.
Pipeline Execution OrderContainment Query TargetQuery Result for Paragraph LinesExtracted Output
Dividers BEFORE Textregions (Contains only tables/images)False (Evaluated as "Unclaimed")Spurious <hr> inside paragraphs
Dividers AFTER Textregions (Contains paragraphs & headings)True (Correctly identified as contained)Clean paragraphs without <hr> noise

Technical defect analysis

Horizontal line segments inside paragraph blocks were being misclassified as standalone section dividers:

<!-- DEFECTIVE OUTPUT: Spurious divider inserted mid-paragraph -->
<p>Unexpectedly, such degradation is not caused by overfitting,</p>
<hr />
<p>and adding more layers leads to higher training error.</p>

The faulty execution order

// DEFECTIVE IMPLEMENTATION: Dividers evaluated before text regions exist
export function classifyPageMonolith(segments, textItems) {
  const regions = [];

// Step 1: Detect tables regions.push(...detectLatticeTables(segments));

// Step 2: Detect dividers (FAILS: regions array contains NO text blocks yet!) const dividers = segments.hLines.filter(seg => { const isContained = regions.some(r => isBBoxContained(r.bbox, seg)); return !isContained; // Always returns true for lines inside paragraphs! }); regions.push(...dividers);

// Step 3: Classify text blocks (Paragraphs created TOO LATE) regions.push(...classifyTextBlocks(textItems));

return regions; }

Because regions contained zero paragraph entries at Step 2, every horizontal segment inside a paragraph passed the check as "unclaimed", generating a spurious divider.


Remediation: re-ordering pipeline execution

Re-order the orchestrator sequence so text classification runs before divider detection:

// REFACTORED: Correct Ordering in Pipeline Orchestrator
export function classifyPagePipeline(segments, textItems) {
  const regions = [];

// Step 1: Detect tables and images regions.push(...detectLatticeTables(segments)); regions.push(...detectImages(textItems));

// Step 2: Classify text blocks (Paragraphs, Headings, Lists) FIRST const textBlocks = classifyTextBlocks(textItems); regions.push(...textBlocks);

// Step 3: Detect dividers AFTER text regions exist in regions // Dependency: Requires textBlocks to be populated in regions const dividers = segments.hLines.filter(seg => { const isContained = regions.some(r => isBBoxContained(r.bbox, seg)); return !isContained; // Correctly identifies lines inside paragraph boxes! }); regions.push(...dividers);

return regions; }

Rule of thumb: Ensure shared state collection arrays are fully populated by upstream producers before executing downstream containment queries.
Read this post in the full Engineering Journal →