Engineering Journal
Pdf Processor
Pdf Processor

How to Detect WARNING and CAUTION Boxes in PDFs Using Only Vector Geometry

2026-05-15

TLDR

Keyword text matching (searching for "WARNING") incorrectly flags prose that mentions warnings rather than callout containers. Ground-truth callout box extraction tracks graphics state stacks (q/Q), records fillColor active during rectangle paint operations (re, f), filters out neutral background fills (near-black PDF defaults and near-white page surfaces), and pairs enclosed text nodes to emit semantic HTML <aside class="pdf-box pdf-box--warning"> elements.
Extraction StepGraphics State MechanismClassification CriteriaOutput Element
Color Stack Trackingq (Save) / Q (Restore)Captures active fillColor on refilledRect record
Neutral Fill Filteringc > 0.92 (White) / c < 0.08 (Black)Filters PDF defaults & page backgroundsChromatic callout fills
Role AssignmentContainment matching + Text RegexEncloses text matching /warning/i<aside class="pdf-box">

Technical architecture & implementation

1. Graphics state stack color tracking

Maintain a color state stack across q (save) and Q (restore) operator executions inside ctmAdapter:
// ctmAdapter: Color state stack tracking
const colorStateStack = [{ fill: [0, 0, 0], stroke: [0, 0, 0] }];
let currentColorState = { fill: [0, 0, 0], stroke: [0, 0, 0] };

export function processColorOperator(op, args) { if (op === OPS.save) { colorStateStack.push({ fill: currentColorState.fill.slice(), stroke: currentColorState.stroke.slice() }); } else if (op === OPS.restore) { const restored = colorStateStack.pop(); if (restored) { currentColorState = { fill: restored.fill.slice(), stroke: restored.stroke.slice() }; } } }


2. Neutral fill color filtering & role classification

Filter out neutral fills (near-white page surfaces and near-black PDF defaults) before classifying callout roles:
// Filter neutral fill colors and assign callout roles based on enclosed text
export function classifySemanticBoxRegion(filledRect, enclosedTextItems) {
  const fc = filledRect.fillColor;

// Filter out neutral fill colors: near-white (page) and near-black (PDF default) const isNeutralColor = !fc || fc.every(c => c > 0.92) || fc.every(c => c < 0.08);

if (isNeutralColor) return null;

const combinedText = enclosedTextItems.map(item => item.str).join(' '); let role = 'generic';

if (/warning|danger/i.test(combinedText)) role = 'warning'; else if (/caution/i.test(combinedText)) role = 'caution'; else if (/note|notice|important/i.test(combinedText)) role = 'note'; else if (/tip|hint/i.test(combinedText)) role = 'tip';

return { type: 'BOX', role, fillColorRgb: rgb(${Math.round(fc[0]255)}, ${Math.round(fc[1]255)}, ${Math.round(fc[2]*255)}), textItems: enclosedTextItems }; }

<!-- Assembled HTML Callout Element -->
<aside class="pdf-box pdf-box--warning" style="background: rgb(255, 245, 245)">
  <p><strong>WARNING</strong></p>
  <p>Do not operate equipment without proper ventilation.</p>
</aside>
Rule of thumb: Track graphics state stacks (q/Q) to capture active fill colors, filtering out neutral page/default fills before classifying callout roles.
Read this post in the full Engineering Journal →