Inspect PDF Fonts
inspect_fontsList all fonts in a PDF with properties like name, type, encoding, and embedded status. Identify font usage per page to verify PDF compliance or troubleshoot font issues.
Instructions
List all fonts used in a PDF document with their properties.
Args:
file_path (string): Absolute path to a local PDF file
response_format ('markdown' | 'json'): Output format (default: 'markdown')
Returns: Font name, type (TrueType, Type1, CIDFont, etc.), encoding, embedded/subset status, and pages where each font is used.
Examples:
Check if all fonts are embedded (required for PDF/A, PDF/X)
Identify font types and encodings
Find which pages use specific fonts
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | Absolute path to a local PDF file (e.g., "/path/to/document.pdf") | |
| response_format | No | Output format: "markdown" for human-readable, "json" for structured data | markdown |
Implementation Reference
- src/tools/tier2/inspect-fonts.ts:13-69 (handler)The tool registration function (handler) for 'inspect_fonts'. Calls analyzeFontsWithPdfLib to get font data, builds a FontsAnalysis object, and returns results in Markdown or JSON format.
export function registerInspectFonts(server: McpServer): void { server.registerTool( 'inspect_fonts', { title: 'Inspect PDF Fonts', description: `List all fonts used in a PDF document with their properties. Args: - file_path (string): Absolute path to a local PDF file - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: Font name, type (TrueType, Type1, CIDFont, etc.), encoding, embedded/subset status, and pages where each font is used. Examples: - Check if all fonts are embedded (required for PDF/A, PDF/X) - Identify font types and encodings - Find which pages use specific fonts`, inputSchema: InspectFontsSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, }, async (params: InspectFontsInput) => { try { const result = await analyzeFontsWithPdfLib(params.file_path); const fonts = Array.from(result.fontMap.values()); const analysis: FontsAnalysis = { fonts, totalFontCount: fonts.length, embeddedCount: fonts.filter((f) => f.isEmbedded).length, subsetCount: fonts.filter((f) => f.isSubset).length, pagesScanned: result.pagesScanned, ...(result.note ? { note: result.note } : {}), }; const raw = params.response_format === ResponseFormat.JSON ? JSON.stringify(analysis, null, 2) : formatFontsMarkdown(analysis); const { text } = truncateIfNeeded(raw); return { content: [{ type: 'text' as const, text }] }; } catch (error) { const err = handleStructuredError(error); return { content: [{ type: 'text' as const, text: JSON.stringify(err, null, 2) }], isError: true, }; } }, ); } - src/schemas/tier2.ts:25-30 (schema)Zod schema for inspect_fonts input: requires file_path and optional response_format.
export const InspectFontsSchema = z .object({ file_path: FilePathSchema, response_format: ResponseFormatSchema, }) .strict(); - src/schemas/tier2.ts:61-61 (schema)TypeScript type inferred from the Zod schema for inspect_fonts input.
export type InspectFontsInput = z.infer<typeof InspectFontsSchema>; - src/types.ts:132-139 (schema)TypeScript interface for individual font information (name, type, encoding, embedded/subset status, pages).
export interface FontInfo { name: string; type: string; encoding: string | null; isEmbedded: boolean; isSubset: boolean; pagesUsed: number[]; } - src/types.ts:142-154 (schema)TypeScript interface for the full font analysis result returned by the handler.
export interface FontsAnalysis { fonts: FontInfo[]; totalFontCount: number; embeddedCount: number; subsetCount: number; pagesScanned: number; /** * Optional human-readable note describing partial / fallback results. * Set when fonts could not be enumerated via pdf-lib (e.g. Linearized PDFs * whose page tree cannot be fully resolved). */ note?: string; } - src/tools/index.ts:18-18 (registration)Import of the registerInspectFonts function in the central tool registration index.
import { registerInspectFonts } from './tier2/inspect-fonts.js'; - src/tools/index.ts:43-43 (registration)Registration call for inspect_fonts in the registerAllTools function.
registerInspectFonts(server); - Core helper that uses pdf-lib to extract font information from every page of the PDF, including font name, subtype, encoding, embedded status, subset detection, and page usage.
export async function analyzeFontsWithPdfLib(filePath: string): Promise<FontAnalysisResult> { return withSuppressedPdfLibLogs(() => analyzeFontsWithPdfLibImpl(filePath)); } async function analyzeFontsWithPdfLibImpl(filePath: string): Promise<FontAnalysisResult> { const doc = await loadWithPdfLib(filePath); const fontMap = new Map<string, FontInfo>(); const pages = trySilently(() => doc.getPages()) ?? []; if (pages.length === 0) { return { fontMap, pagesScanned: 0, note: 'pdf-lib could not enumerate the page tree (typical of Linearized PDFs); ' + 'fonts could not be analyzed. Consider regenerating the PDF without linearization.', }; } for (let pageIdx = 0; pageIdx < pages.length; pageIdx++) { const pageNum = pageIdx + 1; const pageNode = pages[pageIdx].node; const resources = trySilently(() => pageNode.Resources()); if (!resources) continue; const fontDict = trySilently(() => resources.lookupMaybe(PDFName.of('Font'), PDFDict)); if (!fontDict) continue; for (const [fontNameObj, fontRefOrDict] of fontDict.entries()) { const fontKey = fontNameObj.decodeText(); // Resolve to actual font dictionary let actualFont: PDFDict | undefined; if (fontRefOrDict instanceof PDFRef) { const resolved = doc.context.lookup(fontRefOrDict); if (resolved instanceof PDFDict) { actualFont = resolved; } } else if (fontRefOrDict instanceof PDFDict) { actualFont = fontRefOrDict; } if (!actualFont) continue; // Extract font properties const subtypeObj = actualFont.lookupMaybe(PDFName.of('Subtype'), PDFName); const baseFontObj = actualFont.lookupMaybe(PDFName.of('BaseFont'), PDFName); const encodingObj = actualFont.get(PDFName.of('Encoding')); const baseFontName = baseFontObj?.decodeText() ?? fontKey; const subtype = subtypeObj?.decodeText() ?? 'Unknown'; const encoding = encodingObj instanceof PDFName ? encodingObj.decodeText() : null; // Check if font is embedded (has FontDescriptor with FontFile/FontFile2/FontFile3) let isEmbedded = false; const descriptorRef = actualFont.get(PDFName.of('FontDescriptor')); if (descriptorRef) { let descriptor: PDFDict | undefined; if (descriptorRef instanceof PDFRef) { const resolved = doc.context.lookup(descriptorRef); if (resolved instanceof PDFDict) descriptor = resolved; } else if (descriptorRef instanceof PDFDict) { descriptor = descriptorRef; } if (descriptor) { isEmbedded = descriptor.has(PDFName.of('FontFile')) || descriptor.has(PDFName.of('FontFile2')) || descriptor.has(PDFName.of('FontFile3')); } } // Check if subset (name starts with 6 uppercase + '+') const isSubset = /^[A-Z]{6}\+/.test(baseFontName); const existing = fontMap.get(baseFontName); if (existing) { if (!existing.pagesUsed.includes(pageNum)) { existing.pagesUsed.push(pageNum); } } else { fontMap.set(baseFontName, { name: baseFontName, type: subtype, encoding, isEmbedded, isSubset, pagesUsed: [pageNum], }); } } } return { fontMap, pagesScanned: pages.length }; } - src/utils/formatter.ts:226-258 (helper)Helper that formats the FontsAnalysis object into a Markdown table with summary stats and per-font details.
export function formatFontsMarkdown(analysis: FontsAnalysis): string { const lines: string[] = ['# Font Analysis', '']; lines.push(`- **Total Fonts**: ${analysis.totalFontCount}`); lines.push(`- **Embedded**: ${analysis.embeddedCount}`); lines.push(`- **Subset**: ${analysis.subsetCount}`); lines.push(`- **Pages Scanned**: ${analysis.pagesScanned}`); if (analysis.fonts.length === 0) { lines.push('', 'No fonts found in this document.'); if (analysis.note) { lines.push('', '## Note', '', `> ${analysis.note}`); } return lines.join('\n'); } lines.push('', '## Font Details', ''); lines.push('| Name | Type | Encoding | Embedded | Subset | Pages |', '|---|---|---|---|---|---|'); for (const f of analysis.fonts) { const pages = f.pagesUsed.length > 5 ? `${f.pagesUsed.slice(0, 5).join(',')}...` : f.pagesUsed.join(','); lines.push( `| ${f.name} | ${f.type} | ${f.encoding ?? '-'} | ${f.isEmbedded ? 'Yes' : 'No'} | ${f.isSubset ? 'Yes' : 'No'} | ${pages} |`, ); } if (analysis.note) { lines.push('', '## Note', '', `> ${analysis.note}`); } return lines.join('\n'); }