Engineering Journal
Pdf Processor
Pdf Processor

Detecting PDF Headers and Footers Without Relying on Page Margins

2026-05-15

TLDR

Position-only Y-coordinate thresholds ($Y < 8\%$ or $Y > 92\%$) fail on non-standard PDF layouts (full-bleed titles, large figure captions near margins). Combining three structural signals, relative font size ($< 0.78 \times \text{body average}$), colored background bands (filledRects), and regex pattern matching (Page 1 of 5), yields reliable header/footer classification. Resetting columnIndex = -1 on reclassified regions ensures headers render full-width outside multi-column grid containers.
Detection SignalTechnical ImplementationDetection Purpose
Relative Font SizeavgFontPt < bodyFontPt * 0.78Identifies small-print running headers
Background Color BandfilledRect.width > viewportWidth * 0.6Detects background header banner fills
Regex Pattern Match/\bpage\s+\d+\b\d+\s+of\s+\d+/i

Technical implementation architecture

// Header and Footer Classification Post-Pass
export function classifyHeadersAndFooters(regions, bodyFontPt, viewportHeight, viewportWidth, filledRects) {
  for (const region of regions) {
    const isTopZone = region.yCenter < viewportHeight * 0.15;
    const isBottomZone = region.yCenter > viewportHeight * 0.85;

if (!isTopZone && !isBottomZone) continue;

const regionText = region.text.trim(); const nonSpaceCount = regionText.replace(/\s/g, '').length;

// Signal 1: Relative font size const isSmallFont = region.avgFontPt < bodyFontPt * 0.78;

// Signal 2: Colored background band overlap const inColoredBand = filledRects.some(rect => rect.w > viewportWidth * 0.6 && rect.y <= region.bbox.y + 4 && rect.y + rect.h >= (region.bbox.y + region.bbox.h) - 4 );

// Signal 3: Pagination or metadata regex pattern const isPatternMatch = /\bpage\s+\d+|\bpg\.?\s*\d+|\b\d+\s+of\s+\d+|\b\d{4}-\d{2}-\d{2}/i.test(regionText);

// Filter out single-character stray glyphs unless matching page numbers if (nonSpaceCount < 2 && !isPatternMatch) continue;

if (isSmallFont || inColoredBand || isPatternMatch) { region.type = isTopZone ? 'HEADER' : 'FOOTER'; // CRITICAL: Reset columnIndex = -1 so header renders full-width outside column grid region.columnIndex = -1; } } }

Rule of thumb: Combine relative font sizes, background fill banners, and regex patterns to detect headers/footers, always resetting columnIndex = -1 to prevent grid containment bugs.
Read this post in the full Engineering Journal →