Extracting Hyperlinks from PDFs as Markdown Links Using pdf-inspector
Use pdf-inspector to parse PDF link annotations and render them as [text](url) Markdown syntax through a three-stage pipeline: annotation detection, TextItem creation with ItemType::Link(url), and final Markdown conversion.
The pdf-inspector library by Firecrawl provides a robust Rust-based solution for extracting hyperlinks from PDF documents and converting them to standard Markdown link format. Unlike simple text extraction tools, it preserves the semantic structure of links by parsing PDF annotation dictionaries directly.
How pdf-inspector Extracts PDF Hyperlinks
The extraction pipeline operates in three distinct stages, each isolated to handle any document type—text-based, scanned, or mixed.
Stage 1: PDF Parsing and Link Annotation Detection
The extractor walks each page's /Annots array in src/extractor/links.rs, filtering for annotations where Subtype equals Link. The extract_page_links function reads the rectangle (Rect) for positioning and calls extract_link_uri to retrieve the destination URI from the annotation's A dictionary.
let annots = page_dict.get(b"Annots") …; // line 56-64
Only annotations matching the Link subtype are processed:
if let Ok(subtype) = annot_dict.get(b"Subtype") {
if let Ok(name) = subtype.as_name() {
if name != b"Link" { continue; }
}
}
Stage 2: TextItem Creation with Link Metadata
For every valid link, pdf-inspector builds a TextItem with position fields (x, y, width, height) and item_type set to ItemType::Link(url). This allows the rest of the pipeline to treat links like normal text while preserving the URL.
links.push(TextItem {
text: url.clone(),
// geometry fields...
item_type: ItemType::Link(url),
// ...
});
Stage 3: Markdown Conversion
During final rendering in src/markdown/convert.rs, the renderer inspects each TextItem. When it encounters ItemType::Link(_), it emits standard Markdown link syntax:
ItemType::Link(url) => format!("[{}]({})", escaped_text, url);
Extracting Links via Command Line
The pdf2md CLI tool provides the fastest way to extract PDF hyperlinks as Markdown:
pdf2md --json my-document.pdf > output.md
The --json flag returns structured output where each link appears as:
{
"text": "https://example.com",
"page": 2,
"item_type": { "Link": "https://example.com" },
...
}
Extracting Links Programmatically in Rust
Basic Link Extraction
Use extract_text_with_positions to pull links and print them as Markdown:
use pdf_inspector::extractor::extract_text_with_positions;
use pdf_inspector::types::ItemType;
fn main() -> Result<(), pdf_inspector::PdfError> {
let items = extract_text_with_positions("my-document.pdf")?;
for item in items {
if let ItemType::Link(url) = &item.item_type {
// Use surrounding text as label; here we reuse the URL
println!("[{}]({})", url, url);
}
}
Ok(())
}
Full PDF-to-Markdown Conversion
Let the library handle all link formatting automatically:
use pdf_inspector::markdown::convert::to_markdown;
use pdf_inspector::extractor::extract_text_with_positions;
let items = extract_text_with_positions("my-document.pdf")?;
let markdown = to_markdown(items, pdf_inspector::markdown::MarkdownOptions::default());
// `markdown` now contains all text with `[label](url)` links
Key Source Files and Their Roles
| File | Purpose |
|---|---|
src/extractor/links.rs |
Detects link annotations, extracts URIs via extract_link_uri, creates TextItem with ItemType::Link |
src/markdown/convert.rs |
Renders ItemType::Link as [text](url) Markdown syntax |
src/bin/pdf2md.rs |
CLI front-end calling the extractor and outputting Markdown/JSON |
src/types.rs |
Defines TextItem struct and ItemType enum including the Link variant |
src/extractor/mod.rs |
Public API wiring link extraction into the overall text-extraction flow |
Safety and Performance Considerations
The link extraction is deliberately isolated from text-layout logic, ensuring compatibility across document types. Budget constants MAX_FORM_FIELD_NODES and MAX_FORM_FIELD_DEPTH protect against pathological PDFs that could cause stack overflows during AcroForm field traversal.
Summary
- Link detection happens in
src/extractor/links.rsviaextract_page_linksandextract_link_uri - URI extraction reads the
Adictionary'sURIentry from PDF annotation objects - Markdown output uses
ItemType::Link(url)pattern matching insrc/markdown/convert.rs - CLI usage:
pdf2md --json document.pdfoutputs structured links - Library usage:
extract_text_with_positionsfollowed byto_markdownfor full conversion
Frequently Asked Questions
Does pdf-inspector handle both text-based and scanned PDFs?
Yes. The link extraction operates on PDF annotation dictionaries, which exist independently of whether the page content is text or images. The position-based TextItem system treats links uniformly regardless of underlying content type.
What URL formats does pdf-inspector support?
The library extracts any URI string present in the annotation's A dictionary URI entry. This includes http://, https://, mailto:, file://, and custom scheme URLs as encoded in the PDF.
How are link labels determined in the Markdown output?
The renderer uses the surrounding or associated text from the TextItem as the link label. If the link annotation has no explicit text content, the raw URL becomes the label. You can customize this by manipulating TextItem fields before calling to_markdown.
Can I extract links without converting the full document to Markdown?
Yes. Call extract_text_with_positions directly and filter for ItemType::Link(url) variants. This bypasses the Markdown renderer entirely while still giving you access to all hyperlink data including positions and page numbers.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →