How pdf-inspector Determines Heading Levels from Font-Size Ratios

The pdf-inspector markdown module determines heading tiers by comparing each line's font size against the document's base font size, using bold text with ratios between 1.05–1.20 for tier detection and fallback thresholds of 2.0, 1.5, and 1.25 when no tiers are found.

The pdf-inspector repository by Firecrawl provides a Rust-based PDF-to-markdown conversion pipeline that automatically infers document structure without relying on explicit PDF heading tags. Understanding how pdf-inspector heading levels from font-size ratios work is essential for debugging conversion output or customizing the toolchain for specialized document formats.

Heading Tier Detection Through Font-Size Ratios

The core algorithm resides in src/markdown/analysis.rs within the compute_heading_tiers function. This function establishes a base font size—the most frequently occurring size in the document—then evaluates candidate headings against it.

Collecting Candidate Tiers

The tier detection logic applies a strict filter to identify potential heading levels:

let ratio = font_size / base_size;
(1.05..1.2).contains(&ratio) && is_bold

Only bold text with font sizes 5% to 20% larger than the base size qualifies as a heading tier candidate. The pdf-inspector markdown module collects all distinct font sizes meeting this criteria, sorts them from largest to smallest, and assigns hierarchical levels—the largest becomes H1, the next H2, and so forth through the sequence.

Fallback Thresholds for Unstructured Documents

When compute_heading_tiers finds no qualifying tiers, the system falls back to hard-coded ratio thresholds defined in src/markdown/analysis.rs:

Ratio Threshold Heading Level
ratio >= 2.0 H1
ratio >= 1.5 H2
ratio >= 1.25 H3

Any text below the 1.25 threshold renders as normal body text. The bold_heading_level helper function handles edge cases where bold formatting exists without meeting ratio requirements—assigning such lines one level below the lowest discovered tier (defaulting to H2 when no tiers exist).

Applying Tiers During Markdown Conversion

The effective_heading_level function—invoked from merge_heading_lines in src/markdown/preprocess.rs and the main conversion orchestrator in src/markdown/convert.rs—performs the final level assignment:

use pdf_inspector::markdown::convert::process_pdf;

// Extract markdown with automatic heading detection
let md = process_pdf("paper.pdf");

// The detector has already computed the tiers:
//   – Font size 24 pt (ratio ≈ 2.0) → # H1

//   – Font size 18 pt (ratio ≈ 1.5) → ## H2

//   – Font size 15 pt (ratio ≈ 1.25) → ### H3

println!("{}", md);

Given a base size of 12 pt, detected tiers of 24.0, 18.0, and 15.0 would map directly to H1, H2, and H3 respectively. The tier index plus one yields the markdown heading level.

Examining Tier Computation Directly

For debugging or custom tooling, you can invoke compute_heading_tiers independently:

use pdf_inspector::markdown::analysis::compute_heading_tiers;

// Assume a base size of 12 pt (most common size)
let lines = /* … parsed TextLine collection … */;
let tiers = compute_heading_tiers(&lines, 12.0);

// Example result
// tiers == [24.0, 18.0, 15.0]   // → H1, H2, H3 respectively
println!("Detected heading tiers: {:?}", tiers);

This interface allows validation of how specific PDF documents will translate to markdown structure before full conversion.

Key Source Files for Heading Level Logic

File Purpose
src/markdown/analysis.rs Computes font-size rarity, builds heading_tiers, and provides fallback logic including bold_heading_level and ratio checks
src/markdown/convert.rs Orchestrates conversion; calls compute_heading_tiers and merges heading lines
src/markdown/preprocess.rs Merges consecutive heading lines using tiers and determines effective heading level through merge_heading_lines

Summary

  • ** pdf-inspector heading levels from font-size ratios** rely on bold text with sizes 1.05× to 1.20× the base size for tier detection
  • Discovered tiers sort by descending size and map sequentially to H1, H2, H3...
  • Fallback thresholds of 2.0, 1.5, and 1.25 apply when automatic tier detection fails
  • The compute_heading_tiers, effective_heading_level, and bold_heading_level functions in src/markdown/analysis.rs implement the core logic
  • Pipeline integration occurs through src/markdown/convert.rs and src/markdown/preprocess.rs

Frequently Asked Questions

Why does pdf-inspector require bold formatting for heading detection?

The is_bold requirement in (1.05..1.2).contains(&ratio) && is_bold prevents false positives from minor font size variations in body text. Bold formatting serves as a semantic signal that distinguishes intentional headings from incidental sizing differences, aligning with conventional document authoring practices where headings typically receive weight emphasis.

What happens when a document has more than three heading levels?

The tier-based system scales beyond three levels dynamically. compute_heading_tiers collects all distinct bold font sizes in the 1.05–1.20 ratio window and assigns sequential levels. A document with five qualifying tiers would produce H1 through H5 automatically, with each tier's index plus one determining its markdown level.

How does pdf-inspector handle documents with no detectable heading structure?

When no tiers satisfy the bold-and-ratio criteria, the fallback logic in src/markdown/analysis.rs activates. Lines with ratios at or above 2.0, 1.5, and 1.25 receive H1, H2, and H3 tags respectively. Bold lines that miss all ratio gates default to H2 via bold_heading_level, ensuring some structural output even from poorly formatted source PDFs.

Can I customize the ratio thresholds for specialized document types?

The current implementation uses compile-time constants for both the tier detection window (1.05–1.20) and fallback thresholds (2.0, 1.5, 1.25). Modifying src/markdown/analysis.rs and recompiling would allow adaptation for domain-specific conventions—such as academic papers with consistently larger heading ratios or compact technical manuals with tighter size gradations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →