1.25.2 — Script sizes and ToUnicode repair checks
Changes since 1.25.1:
- Size a super- or subscript run by its letters and digits: an exponent whose minus sign a symbol font sets a design size above its digits (as TeX does) keeps its
<sup>markup; a run of signs alone, or with signs more than a quarter larger than its letters and digits, is still sized by its largest glyph (#603). - Skip a subset font's ToUnicode repairs where the font itself shows the CMap is right: the repair through a
/CIDToGIDMapfor a CMap keyed by code, and the renumbering of a subset that kept its glyph indexes (#605).
No API changes.
Release preparation: #606.
1.25.1 — Font decoding fixes
Changes since 1.25.0:
- Read simple fonts (Type1, TrueType, Type3) one byte per code when their ToUnicode CMap declares a two-byte codespace: text shown as a kerned run of short strings no longer loses every two-byte string (
Income Statementread asIometatent). The stale-CMap check for Type1 fonts judges such a CMap too, and the page detector counts such a font's text byte by byte (#595). - Read a Type1 font whose
/Encodingnames no base encoding through the built-in encoding of its embedded program (PDF 32000-1:2008, Table 114): TeX's fonts read their ligatures (efficiency, waseciency), curly quotes, dashes and math symbols. A code the program leaves at.notdefreads as nothing, the word space excepted, and the base-14 width fallback measures such fonts as they read (#596). - Take a repaired ToUnicode CMap for a string of one or two glyphs only on evidence beyond short common words:
engagementno longer reads asengageaent,ANDasANaorThreeasThaTe(#597).
No API changes.
Release preparation: #604.
1.25.0 — Async positions and underline detection speed
Changes since 1.24.0:
- Node:
extractTextWithPositionsAsync, the async variant ofextractTextWithPositions: the same arguments and items, run on the libuv thread pool so a slow page no longer blocks the caller's event loop (#592). - Underline and strikeout detection no longer takes quadratic time on pages drawn from many thin filled rects or short strokes: a page of about 200,000 thin rects went from about 40 s to under 0.5 s, with the same marks (#592).
- The
linux-x64-gnuNode binary is cross-built for a GLIBC_2.17 floor again, so it loads on Amazon Linux 2023 (the managed AWS Lambda Node runtimes), RHEL 9 and Debian 11 (#586).
API: Node extractTextWithPositionsAsync.
Release preparation: #593.
1.24.0 — Text paint and document information
Changes since 1.23.0:
- Report the paint each text run was shown with:
TextItemgainsfill_colorandstroke_color(8-bit sRGB, read from DeviceRGB, DeviceGray, DeviceCMYK, ICCBased by component count and Indexed palettes;Nonefor Separation, DeviceN, Pattern and CIE-based spaces) andrender_mode(theTrmode, so invisible text and clipping-only text can be told apart). Extraction and markdown are unchanged (#579). - Read the document information dictionary's
/Author,/Subject,/Keywords,/Creator,/Producer,/CreationDateand/ModDatebeside/Title, each decoded as a PDF text string (#579). - Decode the document
/Titleas a PDF text string: titles in PDFDocEncoding no longer read with U+FFFD, titles in UTF-16LE no longer read as mojibake, and titles given by reference are read (#579). - Extract text a page shows with the
"operator, which the page parser skipped together with the spacing and line move it makes (#579). - Take an ActualText span's weight from the paint of its first painted glyph rather than from the paint in force at the span's end (#579).
- Keep two lines of small type apart when their baselines lie less than 5 pt apart: a stacked table header at 4.7 pt on a 4.5 pt pitch no longer comes out as words zipped from both lines, and type of 8⅓ pt and above groups exactly as before (#580).
API: TextItem::fill_color, stroke_color and render_mode (Node fillColor, strokeColor, renderMode; Python fill_color, stroke_color, render_mode; the pdf2md --items-json fields), and the PdfProcessResult / PdfTypeResult fields author, subject, keywords, creator, producer, creation_date and mod_date (Node and WebAssembly in camelCase, Python as attributes, and the pdf2md --json / detect-pdf --json members). Rust literals of these types must include the new fields.
Release preparation: #582.
1.23.0 — Text-layer repairs
Changes since 1.22.1:
-
Read a Form XObject whose
/BBoxnumerals are too large for any parser: such numerals are saturated in place before the document is read, so the form's text is extracted and the OCR pipeline renders it instead of an empty page (#560). -
Read right-to-left runs shown in reading order forwards: a visible run of right-to-left letters painted forwards now votes for visual storage, so pages that came out word-mirrored read correctly; the logical-order reading is kept for invisible text layers (#561).
-
Read a re-encoded simple font by its
/Differencesnames when it kept a stale ToUnicode CMap, and keep one glyph's characters together through the visual-order read-back (#562). -
Compose a detached spacing accent with the letter it is painted over instead of leaving a stray accent a word on (#563).
-
Treat a return from a zero-advance dependent sign placed behind the pen as no word gap, so scripts that attach signs by stepping the pen back keep their words whole (#564).
-
Classify a page whose text layer is drawn invisibly under a covering image as a scan with the OCR reason
invisible_text_layer, following the text rendering mode, the transformation, the graphics state stack and image draws through form content within a per-page budget (#566). -
Interpolate single-glyph gaps in a ToUnicode CMap when the neighbouring codes make the reading unambiguous, mark the rest as U+FFFD instead of dropping them, and report each font's coverage (#568).
-
A ToUnicode entry whose destination is a control character is a miss, not text: the marker replaces it and the font program, the declared encoding or a blank glyph's advance recovers the letter where it can (#567).
-
pdf2mdanddetect-pdftake the PDF path as the first non-option argument, so flags may come before or after it, and a missing input names the path that could not be opened; the built-in CMaps are compiled into the binary instead of being read from the crate checkout at run time, socargo installbinaries and published wheels find them (PDF_INSPECTOR_BCMAPS_DIRstill overrides) (#570).
API: PdfProcessResult::cmap_gaps (Node cmapGaps, Python cmap_gaps, WebAssembly cmapGaps, and the pdf2md --json / detect-pdf --analyze --json field) lists, per font with gaps among the two-byte codes shown, the codes shown, interpolated and left unmapped (#568). The detector's invisible_text_layer OCR reason is new (#566).
Release preparation: #571.
1.22.1 — Ligature glyph names
Changes since 1.22.0:
- Read ligature glyph names by their components instead of dropping them: names that spell a ligature by components (
f_t,f_f_i,T_h), as aunisequence of several code points (uni00660069), or with a suffix (a.sc,f_i.liga) decode to their letters per the Adobe Glyph List specification, in/Differencesarrays and in embedded font programs' own glyph names; multi-letter results carry their widths through the base-14 fallback. A name that still cannot be read keeps reading as nothing, as in 1.22.0 (#558).
No API changes.
Release preparation: #559.
1.22.0 — Right-to-left text, symbolic fonts, tracked titles and font metadata
Changes since 1.21.0:
- Read right-to-left lines back through the Unicode Bidirectional Algorithm: Hebrew and Arabic text stored in visual order comes out in reading order even when the line holds numbers, Latin phrases or punctuation; glyph-by-glyph positioned words cluster into words by the line's own gaps; presentation forms normalize to their letters once the text is in reading order (#552).
- Decode simple fonts through their
/BaseEncoding, the Symbol and ZapfDingbats built-in encodings and their glyph names — Adobe Glyph List,uniXXXXand numberedgNN/cidNNnames resolved against the embedded program — so symbol bullets, arrows and math operators read as the glyphs they are. A code the/Differencesarray names but cannot map reads as nothing, and a Type3 font's glyph-procedure names are never taken for glyph indexes (#553). - Keep tracked display text whole when its letter spacing is written as
TJoffsets, so a spaced-out title reads as words rather than one letter per word (#548). - Add
bold_source(whereis_boldcame from) andfixed_pitchto text items, andbold_weight_thresholdto the positioned-text options; withbold_from_weightadjacent runs merge by the bold verdict;is_bold_fontrecognises Demi, Semibold, Ultra, Heavy and Black faces and the foundry abbreviations (#547). - Widen the zero-area
/BBoxof Form XObjects at load so their text is extracted and the page renders; Rust gainswiden_degenerate_form_bboxes_mem(bytes)for the repaired document (#550). - Judge a CID-keyed font page's text decoded before calling it vector text, and bound Form XObject content inflation (#551).
- Regression fixtures for the boxes of text drawn with a reflected matrix (#549).
bold_from_weight and bold_weight_threshold stay opt-in; the default is_bold now also reads the weight names above as bold.
Rust migration: TextItem literals need the new bold_source and fixed_pitch fields.
Release preparation: #556.
1.21.0 — Font weight metadata, clipped-text and loading fixes
Changes since 1.20.0:
- Add
font_weight, the font's 100..=900 weight class, to text items in Rust, Node, Python andpdf2md --items-json, with an opt-inbold_from_weighton the positioned-text and region APIs that reads bold from a weight of 600 or more and keeps runs of different weight as separate items (#536). - Leave out text painted wholly outside its rectangular clip, so off-canvas labels no longer leak into the surrounding text (#539).
- Tolerate leading bytes before the
%PDF-header, locating it within the first 1024 bytes as mupdf, pdfium and poppler do (#538).
bold_from_weight is opt-in; is_bold and item merging are unchanged by default.
Rust migration: TextItem literals need font_weight: None for items whose weight class is unknown.
Release preparation: #546.
1.20.0 — Display coordinates and extraction fixes
Changes since 1.19.0:
- Add display-frame coordinates to the Rust and Node positioning/region APIs (#533).
- Improve word spacing and font decoding (#517, #521, #522, #530).
- Fix ActualText/dot leaders and TJ glyph positions (#523, #527).
- Render contents lists one entry per line (#493).
- Upgrade
lopdfto 0.45.0 for malformed cross-reference recovery (#534).
The display frame is opt-in; existing sheet-frame coordinates remain the default.
Release preparation: #535.
1.19.0 — Text extraction fixes and symbol metadata
Changes since 1.18.0:
- Preserve stroke-simulated bold. #511
- Honor horizontal text scaling. #510
- Recover stale single-byte font mappings. #516
- Expose legacy symbol-rewrite metadata in Rust and Node. #519
- Keep separately clipped text runs apart. #518
Rust migration: TextItem literals need legacy_symbol_rewrite: false for items without symbol cleanup. The Node legacySymbolRewrite field is optional and emitted only when cleanup changed a character.