#2841·WeasyPrint

Corrupted ToUnicode CMap (empty bfchar entries) for real-GSUB Arabic text; also emits an empty [] TJ

Author: tima100facesCreated Jul 15, 2026Updated Sep 4, 2026
Labelsbug

Environment

  • WeasyPrint: 69.0
  • Pango: 1.58.0
  • HarfBuzz: 14.2.1
  • Python: 3.13.7
  • OS: macOS 26.5.2 (Build 25F84), Darwin 25.5.0, arm64

Summary

Rendering a single Arabic word with a real font that has a GSUB table (Noto Sans Arabic) produces two defects that don't appear when rendering the same font/CSS with unshaped (Latin) text:

  1. Two of the seven ToUnicode bfchar entries for the shaped run map to an empty destination string instead of a Unicode code point. Any tool that extracts text from the PDF then falls back to the raw CID value at those positions, injecting literal control characters into the extracted text — even though the glyphs render correctly on the page. This affects everyone who copies/extracts text from such a PDF, independent of any particular viewer.
  2. Separately, an empty [] TJ operator is emitted right after the run's Tm/Tf. Per the PDF spec this is a harmless no-op (shows nothing, advances by zero) — not a spec violation — so this isn't a "must fix", just worth knowing: it seems to serve no purpose and could simply not be emitted. One consumer we know of, Adobe Illustrator, does crash on documents containing it (see "Notes" below), which is the only reason it caught our attention.

Steps to reproduce

  1. python3 -m venv .venv && .venv/bin/pip install weasyprint==69.0

  2. Get the font: https://github.com/notofonts/arabic/releases/tag/NotoSansArabic-v2.012 (static instance, NotoSansArabic-Regular.ttf, version 2.012 — has a real GSUB table).

  3. Save as arabic.html, with the font file in the same directory:

    كتاب
  4. Render: .venv/bin/python -m weasyprint arabic.html arabic.pdf (macOS + Homebrew: prefix with DYLD_LIBRARY_PATH=/opt/homebrew/lib, per WeasyPrint's own install docs — unrelated to this bug.)

  5. Inspect the font's /ToUnicode CMap (e.g. via pikepdf): two of seven bfchar entries have an empty destination:

    <000e> <> <0012> <>

  6. Inspect the page content stream: a [] TJ appears right after the Tm/Tf for the text object.

Control (proves this is shaping-specific, not general)

Identical HTML/CSS, only the body text changed to the Latin transliteration "ktab" (same font-face, same font file):

  • All five bfchar entries in ToUnicode resolve to a real code point, none empty.
  • No [] TJ anywhere in the content stream.
  • Extracted text (PyMuPDF page.get_text()) is clean: 'ktab \n'.

Whereas the Arabic version extracts as:

'ك\x12تا\x0eب\n'

— literal \x12/\x0e control bytes injected exactly where the empty bfchar entries are.

Observation

Both glyphs with an empty bfchar destination are the same base glyph in different forms: <000e> is glyph uni066E, and <0012> is glyph uni066E.medi.wide — a contextual (medial) form of that same glyph.

Expected

ToUnicode bfchar entries for shaped Arabic glyphs resolve to real Unicode code points, so extracted text matches what's visually rendered.

Notes on the [] TJ

Isolated via single-variable testing in an internal investigation: two otherwise-identical documents (same corrupted-CMap Arabic run in both), one with the empty [] TJ present and one with it stripped — Illustrator opens the file without it, and fails with "Out of memory" on the one with it. That test only varied the [] TJ's presence; whether [] TJ alone, in a document with an otherwise-correct ToUnicode CMap, also triggers this has not been tested. Stripping empty [] TJ operators from the content stream is a pure no-op removal (0.000% pixel diff before/after in that investigation).

Related

  • The ToUnicode corruption looks like the same class of bug covered by the existing regression test test_to_unicode_rtl (tests/test_api.py; originally named test_cmap_rtl when added by #2404, which fixed #1686), but that test uses font-family: weasyprint — the built-in test font — which doesn't have a real GSUB table, so it doesn't catch this case even though it uses the same "اب" input.
  • Confirmed independent of variable- vs static-font-instance — this repro already uses the static instance.
  • Not affected by the full_fonts/hinting options (checked in the original investigation, not re-verified in this minimal repro).