From 83deb9f138b265c0ea22c57c047b92f3c31ca0e7 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sun, 21 Jun 2026 17:10:37 +0200 Subject: [PATCH 1/3] feat(pdf): decode LaTeX math (cid:N) glyphs to Unicode PDFs from pdflatex embed Computer Modern math fonts without a ToUnicode CMap, so pdfminer emits unreadable (cid:N) tokens for sums, integrals, roots, delimiters, and Greek. Add a font-aware decoder that reads each glyph's font name and maps it through per-font CMEX/CMSY/CMMI tables, with cluster-level confidence and a fallback for low-confidence runs. On by default; pass decode_cid=False to keep the raw tokens. Note: this changes default PDF output for math documents. Co-Authored-By: Claude Opus 4.8 --- .../converter_utils/pdf/__init__.py | 0 .../converter_utils/pdf/cid_decoder.py | 119 ++++++ .../converter_utils/pdf/cid_fonts.py | 393 ++++++++++++++++++ .../markitdown/converters/_pdf_converter.py | 7 + packages/markitdown/tests/_test_vectors.py | 10 + .../tests/test_files/test_math_cid.pdf | Bin 0 -> 101742 bytes packages/markitdown/tests/test_pdf_cid.py | 109 +++++ 7 files changed, 638 insertions(+) create mode 100644 packages/markitdown/src/markitdown/converter_utils/pdf/__init__.py create mode 100644 packages/markitdown/src/markitdown/converter_utils/pdf/cid_decoder.py create mode 100644 packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py create mode 100644 packages/markitdown/tests/test_files/test_math_cid.pdf create mode 100644 packages/markitdown/tests/test_pdf_cid.py diff --git a/packages/markitdown/src/markitdown/converter_utils/pdf/__init__.py b/packages/markitdown/src/markitdown/converter_utils/pdf/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/markitdown/src/markitdown/converter_utils/pdf/cid_decoder.py b/packages/markitdown/src/markitdown/converter_utils/pdf/cid_decoder.py new file mode 100644 index 000000000..1a1814d96 --- /dev/null +++ b/packages/markitdown/src/markitdown/converter_utils/pdf/cid_decoder.py @@ -0,0 +1,119 @@ +"""Resolve pdfminer ``(cid:N)`` tokens in extracted PDF text to Unicode. + +Two stages: + +* :func:`build_cid_map` makes a single low-level pdfminer pass over the document, + reading each unmapped glyph's ``fontname`` so the correct per-font encoding table + is used (see :mod:`.cid_fonts`). The result is a document-specific + ``cid -> Unicode`` map; a cid that resolves to conflicting glyphs across fonts in + the same document is dropped (ambiguous -> left for the fallback path). + +* :func:`decode_cids` substitutes those tokens in the already-extracted markdown. + Tokens are grouped into *clusters* (a cluster ≈ one math expression). Each cluster + gets a confidence score = resolved / total tokens; clusters below the threshold are + wrapped in an HTML comment instead of being partly mistranslated, so downstream + consumers still see the raw signal. +""" + +import re +from typing import BinaryIO + +from .cid_fonts import lookup + +# Matches a pdfminer unmapped-glyph token, e.g. "(cid:88)". +_CID_TOKEN = re.compile(r"\(cid:(\d+)\)") +# Same, anchored, for testing a single LTChar's rendered text. +_CID_EXACT = re.compile(r"^\(cid:(\d+)\)$") + +# Max length of intervening (non-blank-line) text for two cid tokens to count as +# the same cluster. Formula fragments like "Q−1 (Q(x))" sit between delimiters. +_MAX_CLUSTER_GAP = 40 + + +def build_cid_map(pdf_bytes: BinaryIO) -> dict[int, str]: + """Build a document-specific ``cid -> Unicode`` map via a font-aware pass. + + Reads ``LTChar.fontname`` for every unmapped ``(cid:N)`` glyph and resolves it + through the per-font tables. Conflicting resolutions for the same cid are + discarded so they are never guessed. + """ + from pdfminer.high_level import extract_pages + from pdfminer.layout import LTChar + + pdf_bytes.seek(0) + + candidates: dict[int, set[str]] = {} + + def walk(obj: object) -> None: + for element in obj: # type: ignore[attr-defined] + if isinstance(element, LTChar): + match = _CID_EXACT.match(element.get_text()) + if match: + cid = int(match.group(1)) + resolved = lookup(element.fontname, cid) + if resolved is not None: + candidates.setdefault(cid, set()).add(resolved) + if hasattr(element, "__iter__"): + walk(element) + + for page in extract_pages(pdf_bytes): + walk(page) + + # Keep only unambiguous resolutions. + return {cid: next(iter(chars)) for cid, chars in candidates.items() if len(chars) == 1} + + +def _same_cluster(gap: str) -> bool: + """True if the text between two cid tokens keeps them in one formula cluster.""" + if "\n\n" in gap: # a blank line ends the formula + return False + return len(gap.strip()) <= _MAX_CLUSTER_GAP + + +def decode_cids( + markdown: str, + cid_map: dict[int, str], + *, + confidence_threshold: float = 0.6, +) -> str: + """Replace ``(cid:N)`` tokens in *markdown* using *cid_map*. + + High-confidence clusters are substituted (unresolved tokens within them are left + untouched). Clusters whose resolved ratio falls below *confidence_threshold* are + wrapped as ```` rather than partially decoded. + """ + matches = list(_CID_TOKEN.finditer(markdown)) + if not matches: + return markdown + + # Group token matches into clusters by proximity. + clusters: list[list[re.Match[str]]] = [[matches[0]]] + for match in matches[1:]: + gap = markdown[clusters[-1][-1].end() : match.start()] + if _same_cluster(gap): + clusters[-1].append(match) + else: + clusters.append([match]) + + out: list[str] = [] + last = 0 + for cluster in clusters: + start, end = cluster[0].start(), cluster[-1].end() + out.append(markdown[last:start]) + span = markdown[start:end] + + resolved = sum(1 for m in cluster if int(m.group(1)) in cid_map) + confidence = resolved / len(cluster) + + if confidence >= confidence_threshold: + out.append( + _CID_TOKEN.sub( + lambda m: cid_map.get(int(m.group(1)), m.group(0)), span + ) + ) + else: + out.append(f"") + last = end + + out.append(markdown[last:]) + return "".join(out) diff --git a/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py b/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py new file mode 100644 index 000000000..5803ed495 --- /dev/null +++ b/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py @@ -0,0 +1,393 @@ +"""Font-keyed CID -> Unicode tables for LaTeX (Computer Modern / Latin Modern) PDFs. + +When a PDF embeds a math font without a ``ToUnicode`` CMap, pdfminer emits the +glyph as the literal token ``(cid:N)``, where ``N`` is the glyph's code in the +font's *native* encoding -- not Unicode. The same code means different glyphs in +different fonts (e.g. code 12 is ``|`` in CMEX10 but unrelated in CMSY10), so +resolution must be keyed by font name. + +Tables are expressed as ``code -> glyph-name`` (the stable native encoding) plus a +shared ``glyph-name -> Unicode`` map. The CMEX10 table is verified glyph-by-glyph +against the bundled fixture (``test_math_cid.pdf``); its encoding was confirmed by +reading the embedded Type1 font's built-in ``Encoding`` array. CMSY10 / CMMI10 +carry the standard Computer Modern encodings (not exercised by the bundled fixture) +to generalise the feature to other LaTeX PDFs. + +These native encodings are stable across documents -- unlike subset *CID* fonts, +CM/LM Type1 math fonts are not renumbered per document -- so a static table is +reliable. Codes that are not present here fall through to the decoder's +confidence/fallback path rather than being mistranslated. +""" + +# --- glyph name -> Unicode ------------------------------------------------- + +_GLYPH_TO_UNICODE: dict[str, str] = { + # delimiters (all size variants collapse to the base character) + "parenleft": "(", + "parenright": ")", + "bracketleft": "[", + "bracketright": "]", + "braceleft": "{", + "braceright": "}", + "floorleft": "⌊", + "floorright": "⌋", + "ceilingleft": "⌈", + "ceilingright": "⌉", + "angbracketleft": "⟨", + "angbracketright": "⟩", + "slash": "/", + "backslash": "\\", + "bar": "|", + "doublebar": "‖", + # operators + "summation": "∑", + "product": "∏", + "integral": "∫", + "union": "∪", + "intersection": "∩", + "radical": "√", + # Greek + "Gamma": "Γ", + "Delta": "Δ", + "Theta": "Θ", + "Lambda": "Λ", + "Xi": "Ξ", + "Pi": "Π", + "Sigma": "Σ", + "Upsilon": "Υ", + "Phi": "Φ", + "Psi": "Ψ", + "Omega": "Ω", + "alpha": "α", + "beta": "β", + "gamma": "γ", + "delta": "δ", + "epsilon": "ϵ", + "zeta": "ζ", + "eta": "η", + "theta": "θ", + "iota": "ι", + "kappa": "κ", + "lambda": "λ", + "mu": "μ", + "nu": "ν", + "xi": "ξ", + "pi": "π", + "rho": "ρ", + "sigma": "σ", + "tau": "τ", + "upsilon": "υ", + "phi": "ϕ", + "chi": "χ", + "psi": "ψ", + "omega": "ω", + "epsilon1": "ε", + "theta1": "ϑ", + "pi1": "ϖ", + "rho1": "ϱ", + "sigma1": "ς", + "phi1": "φ", + # symbols + "minus": "−", + "periodcentered": "·", + "multiply": "×", + "asteriskmath": "∗", + "divide": "÷", + "plusminus": "±", + "minusplus": "∓", + "circleplus": "⊕", + "circleminus": "⊖", + "circletimes": "⊗", + "circlemultiply": "⊗", + "circledivide": "⊘", + "circledot": "⊙", + "circlecopyrt": "©", + "openbullet": "◦", + "bullet": "•", + "partialdiff": "∂", + "nabla": "∇", + "negationslash": "̸", # combining long solidus overlay; renders on the preceding glyph + "vector": "", # \vec accent: dropped, the base letter is emitted separately + "equivalence": "≡", + "lessequal": "≤", + "greaterequal": "≥", + "similar": "∼", + "approxequal": "≈", + "propersubset": "⊂", + "propersuperset": "⊃", + "reflexsubset": "⊆", + "reflexsuperset": "⊇", + "lessmuch": "≪", + "greatermuch": "≫", + "arrowleft": "←", + "arrowright": "→", + "arrowup": "↑", + "arrowdown": "↓", + "arrowboth": "↔", + "similarequal": "≃", + "arrowdblright": "⇒", + "arrowdblleft": "⇐", + "arrowdblboth": "⇔", + "proportional": "∝", + "prime": "′", + "infinity": "∞", + "element": "∈", + "owner": "∋", + "universal": "∀", + "existential": "∃", + "logicalnot": "¬", + "emptyset": "∅", +} + + +# --- native font encodings: code -> glyph name ----------------------------- + +# CMEX10 (math extension): delimiters in graded sizes + big operators. +# Verified against the embedded Type1 encoding of the bundled fixture; the +# 0-47 delimiter block and the scattered Big variants / operators / radicals +# below were all confirmed code-by-code. +_CMEX10_CODES: dict[int, str] = { + 0: "parenleftbig", + 1: "parenrightbig", + 2: "bracketleftbig", + 3: "bracketrightbig", + 4: "floorleftbig", + 5: "floorrightbig", + 6: "ceilingleftbig", + 7: "ceilingrightbig", + 8: "braceleftbig", + 9: "bracerightbig", + 10: "angbracketleftbig", + 11: "angbracketrightbig", + 12: "vextendsingle", + 13: "vextenddouble", + 14: "slashbig", + 15: "backslashbig", + 16: "parenleftBig", + 17: "parenrightBig", + 18: "parenleftbigg", + 19: "parenrightbigg", + 20: "bracketleftbigg", + 21: "bracketrightbigg", + 22: "floorleftbigg", + 23: "floorrightbigg", + 24: "ceilingleftbigg", + 25: "ceilingrightbigg", + 26: "braceleftbigg", + 27: "bracerightbigg", + 28: "angbracketleftbigg", + 29: "angbracketrightbigg", + 30: "slashbigg", + 31: "backslashbigg", + 32: "parenleftBigg", + 33: "parenrightBigg", + 34: "bracketleftBigg", + 35: "bracketrightBigg", + 36: "floorleftBigg", + 37: "floorrightBigg", + 38: "ceilingleftBigg", + 39: "ceilingrightBigg", + 40: "braceleftBigg", + 41: "bracerightBigg", + 42: "angbracketleftBigg", + 43: "angbracketrightBigg", + 44: "slashBigg", + 45: "backslashBigg", + 46: "slashBig", + 47: "backslashBig", + 50: "bracketlefttp", + 51: "bracketrighttp", + 52: "bracketleftbt", + 53: "bracketrightbt", + 54: "bracketleftex", + 55: "bracketrightex", + 68: "angbracketleftBig", + 69: "angbracketrightBig", + 80: "summationtext", + 81: "producttext", + 82: "integraltext", + 83: "uniontext", + 88: "summationdisplay", + 89: "productdisplay", + 90: "integraldisplay", + 91: "uniondisplay", + 92: "intersectiondisplay", + 104: "bracketleftBig", + 105: "bracketrightBig", + 110: "braceleftBig", + 111: "bracerightBig", + 112: "radicalbig", + 113: "radicalBig", + 114: "radicalbigg", + 115: "radicalBigg", +} + +# CMMI10 (math italic): Greek letters live at codes 0-39 (the high-value part; +# ASCII letters carry their own ToUnicode and are left alone). +_CMMI10_CODES: dict[int, str] = { + 0: "Gamma", + 1: "Delta", + 2: "Theta", + 3: "Lambda", + 4: "Xi", + 5: "Pi", + 6: "Sigma", + 7: "Upsilon", + 8: "Phi", + 9: "Psi", + 10: "Omega", + 11: "alpha", + 12: "beta", + 13: "gamma", + 14: "delta", + 15: "epsilon", + 16: "zeta", + 17: "eta", + 18: "theta", + 19: "iota", + 20: "kappa", + 21: "lambda", + 22: "mu", + 23: "nu", + 24: "xi", + 25: "pi", + 26: "rho", + 27: "sigma", + 28: "tau", + 29: "upsilon", + 30: "phi", + 31: "chi", + 32: "psi", + 33: "omega", + 34: "epsilon1", + 35: "theta1", + 36: "pi1", + 37: "rho1", + 38: "sigma1", + 39: "phi1", + 64: "partialdiff", + 126: "vector", +} + +# CMSY10 (math symbols): standard Computer Modern encoding. +_CMSY10_CODES: dict[int, str] = { + 0: "minus", + 1: "periodcentered", + 2: "multiply", + 3: "asteriskmath", + 4: "divide", + 6: "plusminus", + 7: "minusplus", + 8: "circleplus", + 9: "circleminus", + 10: "circlemultiply", + 11: "circledivide", + 12: "circledot", + 13: "circlecopyrt", + 14: "openbullet", + 15: "bullet", + 17: "equivalence", + 18: "reflexsubset", + 19: "reflexsuperset", + 20: "lessequal", + 21: "greaterequal", + 24: "similar", + 25: "approxequal", + 26: "propersubset", + 27: "propersuperset", + 28: "lessmuch", + 29: "greatermuch", + 32: "arrowleft", + 33: "arrowright", + 34: "arrowup", + 35: "arrowdown", + 36: "arrowboth", + 39: "similarequal", + 40: "arrowdblleft", + 41: "arrowdblright", + 44: "arrowdblboth", + 47: "proportional", + 48: "prime", + 49: "infinity", + 50: "element", + 51: "owner", + 54: "negationslash", + 56: "universal", + 57: "existential", + 58: "logicalnot", + 59: "emptyset", + 106: "bar", + 114: "nabla", +} + + +def _build(codes: dict[int, str]) -> dict[int, str]: + """Resolve a code->glyph-name table to code->Unicode, dropping unknown names.""" + table: dict[int, str] = {} + for code, glyph in codes.items(): + unicode_char = _GLYPH_TO_UNICODE.get(glyph) + if unicode_char is None: + # Size-/style-suffixed glyph (e.g. "parenleftbig", + # "summationdisplay"): fall back to the base name by stripping the + # suffix. + for suffix in ( + "bigg", "Bigg", "big", "Big", "display", "text", + "tp", "bt", "ex", "mid", # extensible delimiter pieces + ): + if glyph.endswith(suffix): + unicode_char = _GLYPH_TO_UNICODE.get(glyph[: -len(suffix)]) + break + if unicode_char is None and glyph.startswith("vextend"): + unicode_char = _GLYPH_TO_UNICODE["bar" if glyph == "vextendsingle" else "doublebar"] + if unicode_char is not None: + table[code] = unicode_char + return table + + +CMEX = _build(_CMEX10_CODES) +CMMI = _build(_CMMI10_CODES) +CMSY = _build(_CMSY10_CODES) + +# Tables are keyed by *family*, not design size. A Computer Modern math font +# shares its encoding across every point size (CMSY10, CMSY8, CMSY7, ...) and +# with its Latin Modern equivalent (LMSY10, ...), so all collapse to one table. +FONT_TABLES: dict[str, dict[int, str]] = { + "CMEX": CMEX, + "CMMI": CMMI, + "CMSY": CMSY, +} + +# Family prefix (after subset-prefix stripping) -> FONT_TABLES key. +_FAMILY_PREFIXES = { + "CMEX": "CMEX", + "LMEX": "CMEX", + "CMMI": "CMMI", # also matches CMMIB (bold math italic): same OML encoding + "LMMI": "CMMI", + "CMSY": "CMSY", + "LMSY": "CMSY", +} + + +def normalize_fontname(fontname: str | None) -> str: + """Normalise a pdfminer fontname to a FONT_TABLES family key. + + Strips the 6-letter subset prefix (e.g. ``UXDKUK+CMEX10`` -> ``CMEX10``) and + maps any design-size/Latin-Modern variant to its family + (``CMSY8`` / ``LMSY10`` -> ``CMSY``). Returns ``""`` if no math family matches. + """ + if not fontname: + return "" + name = fontname.split("+", 1)[1] if "+" in fontname else fontname + name = name.upper() + for prefix, family in _FAMILY_PREFIXES.items(): + if name.startswith(prefix): + return family + return "" + + +def lookup(fontname: str | None, cid: int) -> str | None: + """Resolve a single ``(font, cid)`` to Unicode, or ``None`` if unknown.""" + table = FONT_TABLES.get(normalize_fontname(fontname)) + if table is None: + return None + return table.get(cid) diff --git a/packages/markitdown/src/markitdown/converters/_pdf_converter.py b/packages/markitdown/src/markitdown/converters/_pdf_converter.py index ffbcbd990..98ffa0447 100644 --- a/packages/markitdown/src/markitdown/converters/_pdf_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pdf_converter.py @@ -6,6 +6,7 @@ from .._base_converter import DocumentConverter, DocumentConverterResult from .._stream_info import StreamInfo from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE +from ..converter_utils.pdf.cid_decoder import build_cid_map, decode_cids # Pattern for MasterFormat-style partial numbering (e.g., ".1", ".2", ".10") PARTIAL_NUMBERING_PATTERN = re.compile(r"^\.\d+$") @@ -586,4 +587,10 @@ def convert( # Post-process to merge MasterFormat-style partial numbering with following text markdown = _merge_partial_numbering_lines(markdown) + # Resolve (cid:N) tokens from LaTeX math fonts to Unicode. On by + # default; pass decode_cid=False to keep the raw pdfminer tokens. + if kwargs.get("decode_cid", True) and "(cid:" in markdown: + cid_map = build_cid_map(pdf_bytes) + markdown = decode_cids(markdown, cid_map) + return DocumentConverterResult(markdown=markdown) diff --git a/packages/markitdown/tests/_test_vectors.py b/packages/markitdown/tests/_test_vectors.py index 74fa9bd0a..e7de15e30 100644 --- a/packages/markitdown/tests/_test_vectors.py +++ b/packages/markitdown/tests/_test_vectors.py @@ -97,6 +97,16 @@ class FileTestVector(object): ], must_not_include=[], ), + FileTestVector( + filename="test_math_cid.pdf", + mimetype="application/pdf", + charset=None, + url=None, + must_include=[ + "surveys spectral theory and variational calculus on Hilbert" + ], + must_not_include=["(cid:"], + ), FileTestVector( filename="test_blog.html", mimetype="text/html", diff --git a/packages/markitdown/tests/test_files/test_math_cid.pdf b/packages/markitdown/tests/test_files/test_math_cid.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f31940f173659b774afd8efbf2c77a67ae753290 GIT binary patch literal 101742 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%0DGaS}$ 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 Hh0*^nDjQ=E literal 0 HcmV?d00001 diff --git a/packages/markitdown/tests/test_pdf_cid.py b/packages/markitdown/tests/test_pdf_cid.py new file mode 100644 index 000000000..70c336eaa --- /dev/null +++ b/packages/markitdown/tests/test_pdf_cid.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 -m pytest +"""Tests for CID decoding of LaTeX math PDFs (on by default; decode_cid=False to opt out). + +LaTeX engines embed Computer Modern math glyphs without a ToUnicode CMap, so +pdfminer emits them as literal (cid:N) tokens. The decoder resolves them +font-aware. Fixtures: + +* test_math_cid.pdf - a pdflatex document whose math produces CMEX10 (cid:N). +* test.pdf - a clean Unicode PDF with no (cid:N) tokens (control). + +The CMMI/CMSY tables are checked directly against the lookup tables, since a +freshly compiled document only emits (cid:N) for the CMEX delimiters/operators +(modern pdflatex attaches ToUnicode to the symbol and Greek fonts). +""" +import os + +import pytest + +from markitdown import MarkItDown +from markitdown.converter_utils.pdf.cid_fonts import lookup + +TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "test_files") +MATH_CID_PDF = os.path.join(TEST_FILES_DIR, "test_math_cid.pdf") +CLEAN_PDF = os.path.join(TEST_FILES_DIR, "test.pdf") + + +@pytest.fixture +def markitdown(): + return MarkItDown() + + +def test_math_cids_are_resolved(markitdown): + """decode_cid=True replaces every (cid:N) and yields the expected glyphs.""" + if not os.path.exists(MATH_CID_PDF): + pytest.skip(f"Test file not found: {MATH_CID_PDF}") + + result = markitdown.convert(MATH_CID_PDF, decode_cid=True) + text = result.text_content + + assert "(cid:" not in text + for glyph in ("∑", "∫", "√", "∏", "∪", "|", "⟨", "⟩"): + assert glyph in text, f"missing decoded glyph {glyph!r}" + + +def test_font_tables_resolve_cmsy_cmmi(): + """CMSY/CMMI tables map their glyphs, including point-size variants.""" + assert lookup("ABCDEF+CMMI10", 64) == "∂" # partialdiff + assert lookup("ABCDEF+CMMI10", 11) == "α" # alpha + assert lookup("ABCDEF+CMSY10", 114) == "∇" # nabla + assert lookup("ABCDEF+CMSY10", 11) == "⊘" # circledivide + assert lookup("ABCDEF+CMSY10", 10) == "⊗" # circlemultiply + # Design-size and Latin Modern variants share the family table. + assert lookup("ABCDEF+CMSY8", 48) == "′" # prime + assert lookup("ABCDEF+LMMI10", 11) == "α" + + +def test_prose_pdf_unaffected_by_decode(markitdown): + """A CID-free prose PDF must convert identically with the flag on vs off.""" + if not os.path.exists(CLEAN_PDF): + pytest.skip(f"Test file not found: {CLEAN_PDF}") + + off = markitdown.convert(CLEAN_PDF).text_content + on = markitdown.convert(CLEAN_PDF, decode_cid=True).text_content + + assert on == off + assert "(cid:" not in on + + +def test_decode_on_by_default(markitdown): + """Decoding is the default: math glyphs resolve without passing the flag.""" + if not os.path.exists(MATH_CID_PDF): + pytest.skip(f"Test file not found: {MATH_CID_PDF}") + + text = markitdown.convert(MATH_CID_PDF).text_content + + assert "(cid:" not in text + assert "∑" in text + + +def test_decode_can_be_disabled(markitdown): + """decode_cid=False opts out, leaving the raw pdfminer (cid:N) tokens.""" + if not os.path.exists(MATH_CID_PDF): + pytest.skip(f"Test file not found: {MATH_CID_PDF}") + + text = markitdown.convert(MATH_CID_PDF, decode_cid=False).text_content + + assert "(cid:" in text + assert "∑" not in text + + +def test_clean_unicode_not_corrupted(markitdown): + """Running the decoder on a clean Unicode PDF must not mangle its text.""" + if not os.path.exists(CLEAN_PDF): + pytest.skip(f"Test file not found: {CLEAN_PDF}") + + result = markitdown.convert(CLEAN_PDF, decode_cid=True) + text = result.text_content + + # A known prose sentence from the document survives intact, no comments injected. + assert ( + "While there is contemporaneous exploration of multi-agent approaches" in text + ) + assert "") diff --git a/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py b/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py index 5803ed495..5159c3f99 100644 --- a/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py +++ b/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py @@ -331,14 +331,24 @@ def _build(codes: dict[int, str]) -> dict[int, str]: # "summationdisplay"): fall back to the base name by stripping the # suffix. for suffix in ( - "bigg", "Bigg", "big", "Big", "display", "text", - "tp", "bt", "ex", "mid", # extensible delimiter pieces + "bigg", + "Bigg", + "big", + "Big", + "display", + "text", + "tp", + "bt", + "ex", + "mid", # extensible delimiter pieces ): if glyph.endswith(suffix): unicode_char = _GLYPH_TO_UNICODE.get(glyph[: -len(suffix)]) break if unicode_char is None and glyph.startswith("vextend"): - unicode_char = _GLYPH_TO_UNICODE["bar" if glyph == "vextendsingle" else "doublebar"] + unicode_char = _GLYPH_TO_UNICODE[ + "bar" if glyph == "vextendsingle" else "doublebar" + ] if unicode_char is not None: table[code] = unicode_char return table diff --git a/packages/markitdown/tests/_test_vectors.py b/packages/markitdown/tests/_test_vectors.py index e7de15e30..d66db72fd 100644 --- a/packages/markitdown/tests/_test_vectors.py +++ b/packages/markitdown/tests/_test_vectors.py @@ -102,9 +102,7 @@ class FileTestVector(object): mimetype="application/pdf", charset=None, url=None, - must_include=[ - "surveys spectral theory and variational calculus on Hilbert" - ], + must_include=["surveys spectral theory and variational calculus on Hilbert"], must_not_include=["(cid:"], ), FileTestVector( From 9d4f055a46f4bae6e238787170020fcc966de98f Mon Sep 17 00:00:00 2001 From: Jonas Date: Sun, 21 Jun 2026 19:34:45 +0200 Subject: [PATCH 3/3] feat(pdf): complete CM tables and add bold-math + AMS symbol fonts Fill cmex10/cmsy10/cmmi10 to full 0-127 from the Computer Modern AFM metrics (adds contour integrals, coproduct, square union, big floor/ ceil, extensible delimiter pieces; fixes a CMMI epsilon swap where code 15 is epsilon1/varepsilon and 34 is epsilon/lunate). Extend decoding to the other math fonts that lack ToUnicode: - CMBSY10 (bold math symbols) and CMMIB10 (bold math italic), whose encodings are identical to CMSY10/CMMI10. - MSAM10 / MSBM10 (AMS symbols): squares, harpoons, negated relations, blackboard bold A-Z (Letterlike exceptions override the U+1D538 block), Hebrew letters. - LASY10 (LaTeX symbols), mapped conservatively from the published encoding table since its AFM glyph names are opaque. Unmappable or ambiguous codes are omitted so they fall through to the existing confidence/fallback path. Co-Authored-By: Claude Opus 4.8 --- .../converter_utils/pdf/cid_fonts.py | 746 +++++++++++++++--- packages/markitdown/tests/test_pdf_cid.py | 31 + 2 files changed, 673 insertions(+), 104 deletions(-) diff --git a/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py b/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py index 5159c3f99..a18046c25 100644 --- a/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py +++ b/packages/markitdown/src/markitdown/converter_utils/pdf/cid_fonts.py @@ -3,26 +3,29 @@ When a PDF embeds a math font without a ``ToUnicode`` CMap, pdfminer emits the glyph as the literal token ``(cid:N)``, where ``N`` is the glyph's code in the font's *native* encoding -- not Unicode. The same code means different glyphs in -different fonts (e.g. code 12 is ``|`` in CMEX10 but unrelated in CMSY10), so +different fonts (e.g. code 12 is ``|`` in CMEX10 but ``circledot`` in CMSY10), so resolution must be keyed by font name. -Tables are expressed as ``code -> glyph-name`` (the stable native encoding) plus a -shared ``glyph-name -> Unicode`` map. The CMEX10 table is verified glyph-by-glyph -against the bundled fixture (``test_math_cid.pdf``); its encoding was confirmed by -reading the embedded Type1 font's built-in ``Encoding`` array. CMSY10 / CMMI10 -carry the standard Computer Modern encodings (not exercised by the bundled fixture) -to generalise the feature to other LaTeX PDFs. +Tables are expressed as ``code -> glyph-name`` (the native font encoding) plus a +shared ``glyph-name -> Unicode`` map. The ``code -> glyph-name`` encodings are the +complete codes 0-127 of cmex10 / cmsy10 / cmmi10, taken verbatim from the +Computer Modern AFM metrics (the authoritative source). These native encodings are +stable across documents -- unlike subset *CID* fonts, CM/LM Type1 math fonts are +not renumbered per document -- so a static table is reliable. -These native encodings are stable across documents -- unlike subset *CID* fonts, -CM/LM Type1 math fonts are not renumbered per document -- so a static table is -reliable. Codes that are not present here fall through to the decoder's +Extensible delimiter pieces map so that a multi-row delimiter collapses to a single +character: the top cap carries the base glyph and the extender/bottom pieces map to +``""``. Glyphs with no clean single-codepoint Unicode (wide accents, brace tips) are +mapped to ``""``; any code not resolvable falls through to the decoder's confidence/fallback path rather than being mistranslated. """ +import string + # --- glyph name -> Unicode ------------------------------------------------- _GLYPH_TO_UNICODE: dict[str, str] = { - # delimiters (all size variants collapse to the base character) + # delimiters (base; size variants are stripped to these by _build) "parenleft": "(", "parenright": ")", "bracketleft": "[", @@ -38,15 +41,32 @@ "slash": "/", "backslash": "\\", "bar": "|", - "doublebar": "‖", - # operators + "bardbl": "‖", + "vextendsingle": "|", + "vextenddouble": "‖", + # big operators "summation": "∑", "product": "∏", + "coproduct": "∐", "integral": "∫", + "contintegral": "∮", "union": "∪", "intersection": "∩", + "unionsq": "⊔", + "intersectionsq": "⊓", + "unionmulti": "⊎", + "logicaland": "⋀", + "logicalor": "⋁", "radical": "√", - # Greek + "circledot": "⊙", + "circleplus": "⊕", + "circlemultiply": "⊗", + "circleminus": "⊖", + "circledivide": "⊘", + "circlecopyrt": "©", + "openbullet": "◦", + "bullet": "•", + # uppercase Greek "Gamma": "Γ", "Delta": "Δ", "Theta": "Θ", @@ -58,6 +78,7 @@ "Phi": "Φ", "Psi": "Ψ", "Omega": "Ω", + # lowercase Greek "alpha": "α", "beta": "β", "gamma": "γ", @@ -87,65 +108,175 @@ "rho1": "ϱ", "sigma1": "ς", "phi1": "φ", - # symbols + # symbols / relations / arrows "minus": "−", "periodcentered": "·", "multiply": "×", "asteriskmath": "∗", "divide": "÷", + "diamondmath": "⋄", "plusminus": "±", "minusplus": "∓", - "circleplus": "⊕", - "circleminus": "⊖", - "circletimes": "⊗", - "circlemultiply": "⊗", - "circledivide": "⊘", - "circledot": "⊙", - "circlecopyrt": "©", - "openbullet": "◦", - "bullet": "•", - "partialdiff": "∂", - "nabla": "∇", - "negationslash": "̸", # combining long solidus overlay; renders on the preceding glyph - "vector": "", # \vec accent: dropped, the base letter is emitted separately + "equivasymptotic": "≍", "equivalence": "≡", + "reflexsubset": "⊆", + "reflexsuperset": "⊇", "lessequal": "≤", "greaterequal": "≥", + "precedesequal": "⪯", + "followsequal": "⪰", "similar": "∼", "approxequal": "≈", "propersubset": "⊂", "propersuperset": "⊃", - "reflexsubset": "⊆", - "reflexsuperset": "⊇", "lessmuch": "≪", "greatermuch": "≫", + "precedes": "≺", + "follows": "≻", "arrowleft": "←", "arrowright": "→", "arrowup": "↑", "arrowdown": "↓", "arrowboth": "↔", + "arrownortheast": "↗", + "arrowsoutheast": "↘", + "arrownorthwest": "↖", + "arrowsouthwest": "↙", "similarequal": "≃", - "arrowdblright": "⇒", "arrowdblleft": "⇐", + "arrowdblright": "⇒", + "arrowdblup": "⇑", + "arrowdbldown": "⇓", "arrowdblboth": "⇔", + "arrowbothv": "↕", + "arrowdblbothv": "⇕", "proportional": "∝", "prime": "′", "infinity": "∞", "element": "∈", "owner": "∋", + "triangle": "△", + "triangleinv": "▽", + "triangleright": "▹", + "triangleleft": "◃", + "negationslash": "̸", # combining long solidus overlay; renders on preceding glyph + "mapsto": "↦", "universal": "∀", "existential": "∃", "logicalnot": "¬", "emptyset": "∅", + "Rfractur": "ℜ", + "Ifractur": "ℑ", + "latticetop": "⊤", + "perpendicular": "⊥", + "aleph": "ℵ", + "turnstileleft": "⊢", + "turnstileright": "⊣", + "wreathproduct": "≀", + "nabla": "∇", + "subsetsqequal": "⊑", + "supersetsqequal": "⊒", + "section": "§", + "dagger": "†", + "daggerdbl": "‡", + "paragraph": "¶", + "club": "♣", + "diamond": "♦", + "heart": "♥", + "spade": "♠", + "partialdiff": "∂", + "star": "⋆", + "flat": "♭", + "natural": "♮", + "sharp": "♯", + "lscript": "ℓ", + "weierstrass": "℘", + "dotlessi": "ı", + "dotlessj": "ȷ", + "period": ".", + "comma": ",", + "less": "<", + "greater": ">", + "arrowhookleft": "↩", + "arrowhookright": "↪", + # half arrows (harpoons) + "arrowlefttophalf": "↼", + "arrowleftbothalf": "↽", + "arrowrighttophalf": "⇀", + "arrowrightbothalf": "⇁", + # extensible delimiter pieces: visible top cap -> base char, others dropped + "parenlefttp": "(", + "parenrighttp": ")", + "parenleftbt": "", + "parenrightbt": "", + "parenleftex": "", + "parenrightex": "", + "bracketlefttp": "[", + "bracketrighttp": "]", + "bracketleftbt": "", + "bracketrightbt": "", + "bracketleftex": "", + "bracketrightex": "", + "bracelefttp": "{", + "bracerighttp": "}", + "braceleftbt": "", + "bracerightbt": "", + "braceleftmid": "", + "bracerightmid": "", + "braceex": "", + "radicalbt": "√", + "radicaltp": "", + "radicalvertex": "", + "arrowtp": "↑", + "arrowbt": "↓", + "arrowvertex": "", + "arrowvertexdbl": "", + "arrowdbltp": "⇑", + "arrowdblbt": "⇓", + # accents / brace tips with no clean single codepoint -> dropped + "bracehtipdownleft": "", + "bracehtipdownright": "", + "bracehtipupleft": "", + "bracehtipupright": "", + "hatwide": "", + "hatwider": "", + "hatwidest": "", + "tildewide": "", + "tildewider": "", + "tildewidest": "", + "vector": "", # \vec accent: dropped, base letter is emitted separately + "tie": "", + "slurbelow": "", + "slurabove": "", } +# math-italic / calligraphic letters and old-style digits -> plain ASCII +_GLYPH_TO_UNICODE.update({c: c for c in string.ascii_letters}) +_GLYPH_TO_UNICODE.update( + { + name: digit + for name, digit in zip( + ( + "zerooldstyle", + "oneoldstyle", + "twooldstyle", + "threeoldstyle", + "fouroldstyle", + "fiveoldstyle", + "sixoldstyle", + "sevenoldstyle", + "eightoldstyle", + "nineoldstyle", + ), + "0123456789", + ) + } +) + -# --- native font encodings: code -> glyph name ----------------------------- +# --- native font encodings: code -> glyph name (cmex10/cmsy10/cmmi10, codes 0-127, +# verbatim from the Computer Modern AFM metrics) ---------------------------- -# CMEX10 (math extension): delimiters in graded sizes + big operators. -# Verified against the embedded Type1 encoding of the bundled fixture; the -# 0-47 delimiter block and the scattered Big variants / operators / radicals -# below were all confirmed code-by-code. _CMEX10_CODES: dict[int, str] = { 0: "parenleftbig", 1: "parenrightbig", @@ -195,87 +326,95 @@ 45: "backslashBigg", 46: "slashBig", 47: "backslashBig", + 48: "parenlefttp", + 49: "parenrighttp", 50: "bracketlefttp", 51: "bracketrighttp", 52: "bracketleftbt", 53: "bracketrightbt", 54: "bracketleftex", 55: "bracketrightex", + 56: "bracelefttp", + 57: "bracerighttp", + 58: "braceleftbt", + 59: "bracerightbt", + 60: "braceleftmid", + 61: "bracerightmid", + 62: "braceex", + 63: "arrowvertex", + 64: "parenleftbt", + 65: "parenrightbt", + 66: "parenleftex", + 67: "parenrightex", 68: "angbracketleftBig", 69: "angbracketrightBig", + 70: "unionsqtext", + 71: "unionsqdisplay", + 72: "contintegraltext", + 73: "contintegraldisplay", + 74: "circledottext", + 75: "circledotdisplay", + 76: "circleplustext", + 77: "circleplusdisplay", + 78: "circlemultiplytext", + 79: "circlemultiplydisplay", 80: "summationtext", 81: "producttext", 82: "integraltext", 83: "uniontext", + 84: "intersectiontext", + 85: "unionmultitext", + 86: "logicalandtext", + 87: "logicalortext", 88: "summationdisplay", 89: "productdisplay", 90: "integraldisplay", 91: "uniondisplay", 92: "intersectiondisplay", + 93: "unionmultidisplay", + 94: "logicalanddisplay", + 95: "logicalordisplay", + 96: "coproducttext", + 97: "coproductdisplay", + 98: "hatwide", + 99: "hatwider", + 100: "hatwidest", + 101: "tildewide", + 102: "tildewider", + 103: "tildewidest", 104: "bracketleftBig", 105: "bracketrightBig", + 106: "floorleftBig", + 107: "floorrightBig", + 108: "ceilingleftBig", + 109: "ceilingrightBig", 110: "braceleftBig", 111: "bracerightBig", 112: "radicalbig", 113: "radicalBig", 114: "radicalbigg", 115: "radicalBigg", + 116: "radicalbt", + 117: "radicalvertex", + 118: "radicaltp", + 119: "arrowvertexdbl", + 120: "arrowtp", + 121: "arrowbt", + 122: "bracehtipdownleft", + 123: "bracehtipdownright", + 124: "bracehtipupleft", + 125: "bracehtipupright", + 126: "arrowdbltp", + 127: "arrowdblbt", } -# CMMI10 (math italic): Greek letters live at codes 0-39 (the high-value part; -# ASCII letters carry their own ToUnicode and are left alone). -_CMMI10_CODES: dict[int, str] = { - 0: "Gamma", - 1: "Delta", - 2: "Theta", - 3: "Lambda", - 4: "Xi", - 5: "Pi", - 6: "Sigma", - 7: "Upsilon", - 8: "Phi", - 9: "Psi", - 10: "Omega", - 11: "alpha", - 12: "beta", - 13: "gamma", - 14: "delta", - 15: "epsilon", - 16: "zeta", - 17: "eta", - 18: "theta", - 19: "iota", - 20: "kappa", - 21: "lambda", - 22: "mu", - 23: "nu", - 24: "xi", - 25: "pi", - 26: "rho", - 27: "sigma", - 28: "tau", - 29: "upsilon", - 30: "phi", - 31: "chi", - 32: "psi", - 33: "omega", - 34: "epsilon1", - 35: "theta1", - 36: "pi1", - 37: "rho1", - 38: "sigma1", - 39: "phi1", - 64: "partialdiff", - 126: "vector", -} - -# CMSY10 (math symbols): standard Computer Modern encoding. _CMSY10_CODES: dict[int, str] = { 0: "minus", 1: "periodcentered", 2: "multiply", 3: "asteriskmath", 4: "divide", + 5: "diamondmath", 6: "plusminus", 7: "minusplus", 8: "circleplus", @@ -286,69 +425,271 @@ 13: "circlecopyrt", 14: "openbullet", 15: "bullet", + 16: "equivasymptotic", 17: "equivalence", 18: "reflexsubset", 19: "reflexsuperset", 20: "lessequal", 21: "greaterequal", + 22: "precedesequal", + 23: "followsequal", 24: "similar", 25: "approxequal", 26: "propersubset", 27: "propersuperset", 28: "lessmuch", 29: "greatermuch", + 30: "precedes", + 31: "follows", 32: "arrowleft", 33: "arrowright", 34: "arrowup", 35: "arrowdown", 36: "arrowboth", + 37: "arrownortheast", + 38: "arrowsoutheast", 39: "similarequal", 40: "arrowdblleft", 41: "arrowdblright", + 42: "arrowdblup", + 43: "arrowdbldown", 44: "arrowdblboth", + 45: "arrownorthwest", + 46: "arrowsouthwest", 47: "proportional", 48: "prime", 49: "infinity", 50: "element", 51: "owner", + 52: "triangle", + 53: "triangleinv", 54: "negationslash", + 55: "mapsto", 56: "universal", 57: "existential", 58: "logicalnot", 59: "emptyset", + 60: "Rfractur", + 61: "Ifractur", + 62: "latticetop", + 63: "perpendicular", + 64: "aleph", + 65: "A", + 66: "B", + 67: "C", + 68: "D", + 69: "E", + 70: "F", + 71: "G", + 72: "H", + 73: "I", + 74: "J", + 75: "K", + 76: "L", + 77: "M", + 78: "N", + 79: "O", + 80: "P", + 81: "Q", + 82: "R", + 83: "S", + 84: "T", + 85: "U", + 86: "V", + 87: "W", + 88: "X", + 89: "Y", + 90: "Z", + 91: "union", + 92: "intersection", + 93: "unionmulti", + 94: "logicaland", + 95: "logicalor", + 96: "turnstileleft", + 97: "turnstileright", + 98: "floorleft", + 99: "floorright", + 100: "ceilingleft", + 101: "ceilingright", + 102: "braceleft", + 103: "braceright", + 104: "angbracketleft", + 105: "angbracketright", 106: "bar", + 107: "bardbl", + 108: "arrowbothv", + 109: "arrowdblbothv", + 110: "backslash", + 111: "wreathproduct", + 112: "radical", + 113: "coproduct", 114: "nabla", + 115: "integral", + 116: "unionsq", + 117: "intersectionsq", + 118: "subsetsqequal", + 119: "supersetsqequal", + 120: "section", + 121: "dagger", + 122: "daggerdbl", + 123: "paragraph", + 124: "club", + 125: "diamond", + 126: "heart", + 127: "spade", +} + +_CMMI10_CODES: dict[int, str] = { + 0: "Gamma", + 1: "Delta", + 2: "Theta", + 3: "Lambda", + 4: "Xi", + 5: "Pi", + 6: "Sigma", + 7: "Upsilon", + 8: "Phi", + 9: "Psi", + 10: "Omega", + 11: "alpha", + 12: "beta", + 13: "gamma", + 14: "delta", + 15: "epsilon1", + 16: "zeta", + 17: "eta", + 18: "theta", + 19: "iota", + 20: "kappa", + 21: "lambda", + 22: "mu", + 23: "nu", + 24: "xi", + 25: "pi", + 26: "rho", + 27: "sigma", + 28: "tau", + 29: "upsilon", + 30: "phi", + 31: "chi", + 32: "psi", + 33: "omega", + 34: "epsilon", + 35: "theta1", + 36: "pi1", + 37: "rho1", + 38: "sigma1", + 39: "phi1", + 40: "arrowlefttophalf", + 41: "arrowleftbothalf", + 42: "arrowrighttophalf", + 43: "arrowrightbothalf", + 44: "arrowhookleft", + 45: "arrowhookright", + 46: "triangleright", + 47: "triangleleft", + 48: "zerooldstyle", + 49: "oneoldstyle", + 50: "twooldstyle", + 51: "threeoldstyle", + 52: "fouroldstyle", + 53: "fiveoldstyle", + 54: "sixoldstyle", + 55: "sevenoldstyle", + 56: "eightoldstyle", + 57: "nineoldstyle", + 58: "period", + 59: "comma", + 60: "less", + 61: "slash", + 62: "greater", + 63: "star", + 64: "partialdiff", + 65: "A", + 66: "B", + 67: "C", + 68: "D", + 69: "E", + 70: "F", + 71: "G", + 72: "H", + 73: "I", + 74: "J", + 75: "K", + 76: "L", + 77: "M", + 78: "N", + 79: "O", + 80: "P", + 81: "Q", + 82: "R", + 83: "S", + 84: "T", + 85: "U", + 86: "V", + 87: "W", + 88: "X", + 89: "Y", + 90: "Z", + 91: "flat", + 92: "natural", + 93: "sharp", + 94: "slurbelow", + 95: "slurabove", + 96: "lscript", + 97: "a", + 98: "b", + 99: "c", + 100: "d", + 101: "e", + 102: "f", + 103: "g", + 104: "h", + 105: "i", + 106: "j", + 107: "k", + 108: "l", + 109: "m", + 110: "n", + 111: "o", + 112: "p", + 113: "q", + 114: "r", + 115: "s", + 116: "t", + 117: "u", + 118: "v", + 119: "w", + 120: "x", + 121: "y", + 122: "z", + 123: "dotlessi", + 124: "dotlessj", + 125: "weierstrass", + 126: "vector", + 127: "tie", } +# Suffixes denoting graded-size variants of a base delimiter/operator glyph. +_SIZE_SUFFIXES = ("bigg", "Bigg", "big", "Big", "display", "text") + def _build(codes: dict[int, str]) -> dict[int, str]: - """Resolve a code->glyph-name table to code->Unicode, dropping unknown names.""" + """Resolve a code->glyph-name table to code->Unicode. + + A glyph name is looked up directly, then (for graded-size variants such as + ``summationdisplay`` or ``parenleftBig``) with its size suffix stripped. Codes + whose glyph has no mapping are dropped, so they fall through to the decoder's + fallback path instead of being mistranslated. + """ table: dict[int, str] = {} for code, glyph in codes.items(): unicode_char = _GLYPH_TO_UNICODE.get(glyph) if unicode_char is None: - # Size-/style-suffixed glyph (e.g. "parenleftbig", - # "summationdisplay"): fall back to the base name by stripping the - # suffix. - for suffix in ( - "bigg", - "Bigg", - "big", - "Big", - "display", - "text", - "tp", - "bt", - "ex", - "mid", # extensible delimiter pieces - ): + for suffix in _SIZE_SUFFIXES: if glyph.endswith(suffix): unicode_char = _GLYPH_TO_UNICODE.get(glyph[: -len(suffix)]) break - if unicode_char is None and glyph.startswith("vextend"): - unicode_char = _GLYPH_TO_UNICODE[ - "bar" if glyph == "vextendsingle" else "doublebar" - ] if unicode_char is not None: table[code] = unicode_char return table @@ -358,6 +699,196 @@ def _build(codes: dict[int, str]) -> dict[int, str]: CMMI = _build(_CMMI10_CODES) CMSY = _build(_CMSY10_CODES) +# AMS and LaTeX symbol fonts. These carry symbols (and MSBM's blackboard letters) +# with no shared base name, so they are authored as direct code -> Unicode tables +# (from the msam10/msbm10 AFM glyph names, and the gnuplot table for opaque LASY). +# Codes whose glyph has no clean single codepoint are omitted -> fallback path. + +# MSAM10 (AMS symbols "a"). +MSAM10: dict[int, str] = { + 0: "⊡", + 1: "⊞", + 2: "⊠", + 3: "□", + 4: "■", + 5: "▪", + 6: "◊", + 7: "◆", + 8: "↻", + 9: "↺", + 10: "⇋", + 11: "⇌", + 12: "⊟", + 13: "⊩", + 14: "⊪", + 15: "⊨", + 16: "↠", + 17: "↞", + 18: "⇚", + 19: "⇛", + 20: "⇈", + 21: "⇊", + 22: "⇀", + 23: "⇁", + 24: "↼", + 25: "↽", + 26: "↣", + 27: "↢", + 28: "⇄", + 29: "⇆", + 32: "⇝", + 33: "↭", + 34: "↶", + 35: "↷", + 36: "≗", + 37: "≽", + 38: "≳", + 39: "⪆", + 40: "⊸", + 41: "∴", + 42: "∵", + 45: "≼", + 46: "≲", + 47: "⪅", + 48: "⋜", + 49: "⋝", + 50: "⋞", + 51: "⋟", + 53: "≦", + 54: "⩽", + 55: "≶", + 61: "≧", + 62: "⩾", + 63: "≷", + 64: "⊏", + 65: "⊐", + 66: "▷", + 67: "◁", + 68: "⊵", + 69: "⊴", + 70: "⋆", + 71: "≬", + 77: "△", + 78: "▲", + 79: "▽", + 81: "⋚", + 82: "⋛", + 85: "¥", + 88: "✓", + 90: "⊼", + 92: "∠", + 93: "∡", + 94: "∢", + 95: "∝", + 96: "⌣", + 97: "⌢", + 98: "⋐", + 99: "⋑", + 100: "⋓", + 101: "⋒", + 106: "⫅", + 107: "⫆", + 110: "⋘", + 111: "⋙", + 116: "⋔", + 117: "∔", + 118: "∽", + 122: "✠", + 123: "∁", + 124: "⊺", + 125: "⊚", + 126: "⊛", + 127: "⊖", +} + +# MSBM10 (AMS symbols "b"): negated relations, blackboard bold, Hebrew letters. +MSBM10: dict[int, str] = { + 2: "≰", + 3: "≱", + 4: "≮", + 5: "≯", + 6: "⊀", + 7: "⊁", + 12: "≨", + 13: "≩", + 28: "≁", + 29: "≉", + 42: "⊈", + 43: "⊉", + 44: "∦", + 45: "∤", + 52: "⋭", + 53: "⋬", + 54: "⋪", + 55: "⋫", + 56: "↚", + 57: "↛", + 58: "⇍", + 59: "⇏", + 60: "⇎", + 61: "↮", + 63: "∅", + 64: "∄", + # Blackboard bold A-Z (codes 65-90). The seven letters that exist in the + # Letterlike Symbols block override the Mathematical Double-Struck block + # (U+1D538-U+1D551) -- those are exactly the reserved holes in that block. + 65: "𝔸", + 66: "𝔹", + 67: "ℂ", # U+2102 (exception) + 68: "𝔻", + 69: "𝔼", + 70: "𝔽", + 71: "𝔾", + 72: "ℍ", # U+210D (exception) + 73: "𝕀", + 74: "𝕁", + 75: "𝕂", + 76: "𝕃", + 77: "𝕄", + 78: "ℕ", # U+2115 (exception) + 79: "𝕆", + 80: "ℙ", # U+2119 (exception) + 81: "ℚ", # U+211A (exception) + 82: "ℝ", # U+211D (exception) + 83: "𝕊", + 84: "𝕋", + 85: "𝕌", + 86: "𝕍", + 87: "𝕎", + 88: "𝕏", + 89: "𝕐", + 90: "ℤ", # U+2124 (exception) + 103: "ð", + 104: "≂", + 105: "ℶ", + 106: "ℷ", + 107: "ℸ", + 108: "⋖", + 109: "⋗", + 115: "∼", + 116: "≈", + 122: "Ϝ", + 123: "ϰ", + 125: "ℏ", + 126: "ℏ", +} + +# LASY10 (LaTeX symbol font): opaque AFM names; mapped from the gnuplot table and +# the canonical lasy layout. Conservative -- only the well-known symbols. +LASY10: dict[int, str] = { + 0: "⊲", + 1: "⊳", + 2: "⊴", + 3: "⊵", + 4: "⋈", + 5: "□", + 6: "◇", + 7: "⇝", + 8: "⊏", + 9: "⊐", + 10: "℧", +} + # Tables are keyed by *family*, not design size. A Computer Modern math font # shares its encoding across every point size (CMSY10, CMSY8, CMSY7, ...) and # with its Latin Modern equivalent (LMSY10, ...), so all collapse to one table. @@ -365,6 +896,9 @@ def _build(codes: dict[int, str]) -> dict[int, str]: "CMEX": CMEX, "CMMI": CMMI, "CMSY": CMSY, + "MSAM": MSAM10, + "MSBM": MSBM10, + "LASY": LASY10, } # Family prefix (after subset-prefix stripping) -> FONT_TABLES key. @@ -375,6 +909,10 @@ def _build(codes: dict[int, str]) -> dict[int, str]: "LMMI": "CMMI", "CMSY": "CMSY", "LMSY": "CMSY", + "CMBSY": "CMSY", # bold math symbols: encoding identical to CMSY + "MSAM": "MSAM", + "MSBM": "MSBM", + "LASY": "LASY", } diff --git a/packages/markitdown/tests/test_pdf_cid.py b/packages/markitdown/tests/test_pdf_cid.py index 70c336eaa..64d7cf828 100644 --- a/packages/markitdown/tests/test_pdf_cid.py +++ b/packages/markitdown/tests/test_pdf_cid.py @@ -46,6 +46,8 @@ def test_font_tables_resolve_cmsy_cmmi(): """CMSY/CMMI tables map their glyphs, including point-size variants.""" assert lookup("ABCDEF+CMMI10", 64) == "∂" # partialdiff assert lookup("ABCDEF+CMMI10", 11) == "α" # alpha + assert lookup("ABCDEF+CMMI10", 15) == "ε" # epsilon1 (varepsilon) + assert lookup("ABCDEF+CMMI10", 34) == "ϵ" # epsilon (lunate) assert lookup("ABCDEF+CMSY10", 114) == "∇" # nabla assert lookup("ABCDEF+CMSY10", 11) == "⊘" # circledivide assert lookup("ABCDEF+CMSY10", 10) == "⊗" # circlemultiply @@ -54,6 +56,35 @@ def test_font_tables_resolve_cmsy_cmmi(): assert lookup("ABCDEF+LMMI10", 11) == "α" +def test_bold_and_ams_fonts(): + """Bold-math (CMBSY/CMMIB) and AMS symbol fonts (MSAM/MSBM/LASY) resolve.""" + # Bold math fonts share the plain CM encodings. + assert lookup("ABCDEF+CMBSY10", 0) == "−" # bold cmsy minus + assert lookup("ABCDEF+CMMIB10", 11) == "α" # bold math italic alpha + # AMS symbols. + assert lookup("ABCDEF+MSAM10", 3) == "□" # square + assert lookup("ABCDEF+MSBM10", 82) == "ℝ" # blackboard R (Letterlike U+211D) + assert lookup("ABCDEF+MSBM10", 65) == "𝔸" # blackboard A (1D5 block) + # Letterlike exception overrides the double-struck range: Z is U+2124, not 1D56B. + assert lookup("ABCDEF+MSBM10", 90) == "ℤ" + assert lookup("ABCDEF+LASY10", 0) == "⊲" # \lhd + # Fallback path stays active for codes outside an authored table. + assert lookup("ABCDEF+MSAM10", 200) is None + + +def test_cmex_full_coverage_and_operators(): + """Every cmex10 code 0-127 resolves, including the rarer big operators.""" + # Big operators / contour integrals added from the full AFM encoding. + assert lookup("ABCDEF+CMEX10", 72) == "∮" # contintegraltext + assert lookup("ABCDEF+CMEX10", 76) == "⊕" # circleplustext + assert lookup("ABCDEF+CMEX10", 96) == "∐" # coproducttext + assert lookup("ABCDEF+CMEX10", 106) == "⌊" # floorleftBig + # Extensible delimiter extenders collapse to nothing (single delimiter out). + assert lookup("ABCDEF+CMEX10", 66) == "" # parenleftex + # No cmex code in 0-127 is left unresolved. + assert all(lookup("ABCDEF+CMEX10", c) is not None for c in range(128)) + + def test_prose_pdf_unaffected_by_decode(markitdown): """A CID-free prose PDF must convert identically with the flag on vs off.""" if not os.path.exists(CLEAN_PDF):