AIFontGen research · 2026-11-05-v1

Unicode Combining Characters: A 61-Font NFC/NFD Audit

Font files observed on . This is the measurement time.

This audit tests Unicode combining characters in a dated snapshot of 61 public AIFontGen font files. It compares each file's character map with HarfBuzz output for composed and decomposed text. Use the results to investigate a specific font and sequence; download the input strings, file hashes and method to repeat the checks.

What a missing character-map entry tells you

A missing combining-mark entry does not, by itself, tell you the output of every sequence containing that mark. A shaping engine may use a precomposed character available in the font for a particular sequence. Another sequence with the same mark can still produce a missing glyph.

That distinction is the focus of this audit. We record nominal character-map coverage, output containing glyph 0, and equality of the resulting glyph IDs and positions as separate observations. None of these is a certificate of complete language support.

The test set contains 497 Latin-letter canonical pairs derived from Unicode 15.0.0 and four explicit controls without a precomposed form. Each of 61 files is tested against all 501 cases, giving 30,561 font–case combinations. The downloadable results identify every file and case rather than treating that total as a quality score.

Across the 30,317 canonical font–case pairs, 4,816 pairs in 51 files lacked complete direct NFD character-map coverage yet produced the same nonmissing glyph IDs and positions as NFC. Eight pairs in two files had nonmissing but different output. Five pairs in two other files had no missing glyph from NFC but did from NFD. Separately, 232 of the 244 control font–case pairs produced glyph 0.

NFC, NFD and the actual shaping output

The same abstract letter can have different codepoint sequences. For example, é can be U+00E9, or U+0065 followed by U+0301 COMBINING ACUTE ACCENT. Unicode's NFC and NFD normalization forms provide defined ways to compose and decompose canonically equivalent text. They do not make every arbitrary base-and-mark sequence into a precomposed character. Unicode Standard Annex #15

A font's cmap table answers a narrower question: which nominal glyph does this font map to a codepoint? HarfBuzz also processes the sequence and its shaping context. Its normalization implementation can use a supported precomposed form when a combining mark lacks its own nominal mapping. That is a possible mechanism for a measured result, rather than evidence that a font supports the mark in every context. HarfBuzz 14.4.0 normalization implementation

A measured pair and a control

In Bubble Heart Sprinkle version 0.1.3, the tested file has a nominal mapping for U+00E9 and none for U+0301. HarfBuzz 14.4.0 nevertheless returned glyph 79 for both U+00E9 and U+0065 U+0301, with an x advance of 604 font units, zero y advance and zero offsets. The file uses 1,000 units per em.

The control q́ — U+0071 U+0301 — returned glyph 121 followed by glyph 0 in the same environment. It has no precomposed counterpart in this test. Successful output for é therefore did not establish support for arbitrary acute-accent combinations.

File SHA-256: e12d2184032f9690a69b5aac2ca2aa30784899dade09f2f6c9864e5a2a4753f9.

There is also a reason to keep “no glyph 0” separate from “identical output.” In Acorn Birch version 0.1.2, four canonical pairs produced no glyph 0 in either form but different glyph sequences. One is U+01EC versus U+004F U+0328 U+0304. The composed input returned glyphs 114, 99 and 90; the decomposed input returned glyphs 329 and 99. Both had a total horizontal advance of 311 font units. These observations do not establish whether their rendered outlines look the same.

The opposite limitation also appeared: five canonical cases produced no glyph 0 from the NFC input but did produce glyph 0 from NFD. They were U+1EB4 and U+1EB5 in Amber Brook, and U+1EAF, U+1EB1 and U+1EB5 in Gothic Tattoo Iron. These counterexamples are part of the results, so the audit does not claim universal NFC/NFD compatibility even within the measured snapshot.

Unicode combining characters: results by font

Each canonical-case column has a denominator of 497 per file. “Same, no glyph 0” requires both forms to avoid the missing glyph and return the same glyph IDs, advances and offsets. “Missing cmap, same output” is its subset with incomplete nominal NFD coverage. The last column covers four separate controls. These are finite observations, not font quality scores.

61 public current-version files; 60 detail pages were in the sitemap. Bubble Pillow Square was public with noindex, follow.
Font / manifest versionNFC no glyph 0 / 497NFD no glyph 0 / 497Same, no glyph 0 / 497Missing cmap, same output / 497Different, no glyph 0 / 497Controls with glyph 0 / 4
abbey-textura0.1.35353535304
acorn-birch0.1.2333333329041
acorn-vetch0.1.116116116116104
agate-acorn0.1.1000004
agate-gantry0.1.15858585804
amber-brook0.1.117917717717704
amore-nib0.1.25353535304
amour-shadow0.1.35353535304
anchor-script0.1.25353535304
atelier-light0.1.69191915701
atelier-wide0.1.29191915701
atrium-sans0.1.45353535304
bandit-slab0.1.25353535304
banner-fraktur0.1.25353535304
basalt-lumen0.1.1333333329041
beacon-slab0.1.35353535304
bellbottom-serif0.1.35353535304
birch-quill0.1.1000004
birthday-candle-caps0.1.222222222222204
bobbin-trillium0.1.116116116116104
bouquet-script0.1.25353535304
bracken-knoll0.1.1000004
bracken-quill0.1.116116116116104
brigade-wide-stencil0.1.25353535304
brook-thistle0.1.15858585804
brush-roman-press0.1.220520520520504
brush-shelf-slab0.1.221621621621604
bubble-balloon-party0.1.35353535304
bubble-confetti-round0.1.35353535304
bubble-cookie-soft0.1.35353535304
bubble-heart-sprinkle0.1.35353535304
bubble-jelly-bean0.1.35353535304
bubble-pebble-sans0.1.323023023023004
bubble-pillow-square0.1.25353535304
bubble-puffy-serif0.1.25353535304
bubble-ribbon-knot0.1.25353535304
bubble-squircle-block0.1.25353535304
buttress-bowl-sans0.1.222022022022004
candy-heart-slab0.1.221321321321304
crest-fraktur0.1.25353535304
cursive-letterpress-slant0.1.25353535304
cursive-ribbon-flow0.1.35353535304
cursive-school-upright0.1.25353535304
cursive-soft-bounce0.1.25353535304
dovetail-quill0.1.15858585804
dugout-script0.1.25353535304
executive-sign0.1.25353535304
gothic-blackline0.3.2000004
gothic-fraktur-split0.1.25353535304
gothic-lombardic-round0.3.1000004
gothic-modern-open0.1.220720720720704
gothic-rotunda-broad0.1.221521521521504
gothic-tattoo-iron0.1.320620320320304
gothic-tattoo-spire0.1.35353535304
hull-block0.1.25353535304
kite-grotesk0.1.2000004
margrave-fraktur0.1.25353535304
marker-broad-flat0.3.2000004
marker-quick0.3.2000004
needle-formal0.1.224124124124104
saga-rotunda-shadow0.1.25353535304

How to read the per-font results

Download the per-font summary or all per-case flags. The page table reports each of the 61 files.

For each font, the downloadable summary distinguishes direct nominal NFC and NFD coverage from shaped output. The page table shows NFC and NFD output without glyph 0, identical nonmissing glyph-position output, incomplete NFD cmap with equal output, differing nonmissing paths and the controls. Open the per-case results to see the actual input codepoints and output records.

An equality result compares glyph ID, x/y advance and x/y offset within the same file. Glyph IDs are local to a font: matching numbers in two different fonts have no comparative meaning. Raw records retain cluster information and glyph flags, but those fields are not used to require identical input representation from strings with different codepoint counts. Identical output containing a missing glyph is not counted as a successful canonical match.

The four controls are reported separately from the canonical pairs. Their inputs are unchanged by NFC/NFD; they test specified nonprecomposable combinations rather than adding four more canonical equivalents.

Reproduce the audit

The public manifest identifies each measured file, its font version and SHA-256. The cases file records the UnicodeData source hash, construction rule and exact input strings. The results include nominal mappings and full shaped output for each case.

The recorded runtime uses Python 3.12.13, FontTools 4.65.0, uharfbuzz 0.56.1 and HarfBuzz 14.4.0. The Python binding and shaping engine versions are different fields. Shaping uses the OpenType shaper, left-to-right direction, Latin script, English language, default buffer flags and features, and no variation overrides. Both font-scale axes use units per em. Each call uses one font without cross-font fallback. HarfBuzz default normalization and fallback mark positioning remain enabled; this audit does not attribute every position to font GPOS data.

Download the manifest, test cases, methodology, runtime, compressed raw output and reproduction script. From a directory containing the frozen inputs, run python reproduce.py --root . --output ./recomputed --download in the recorded environment. The script downloads the pinned public preview files and rejects a hash mismatch; the data package does not redistribute font binaries.

The full experiment was repeated from the frozen inputs, with all 66 generated output files matching byte for byte. Independent checking also re-downloaded six files and compared all 501 cases for each.

Scope and limitations

This is an audit of an owned commercial inventory, not a random sample of fonts or a comparison of AI font services. The results apply to the hashed files, finite test set and recorded shaping settings. Replacing a file or changing the engine, features, language or script can change an observation.

We did not score rasterized appearance, accent aesthetics, browser behavior or application compatibility. HarfBuzz selects and positions glyphs; the output still needs to be rendered. A nonzero glyph can have an unsuitable outline, and different glyph sequences can draw similar shapes. HarfBuzz manual

The audit does not establish complete language coverage, all combining-character combinations, every NFD string, or industry-wide prevalence. Before using a font in a finished design, test the actual text in the intended application.

Data versions and corrections

Snapshot: 5 November 2026, 12:56:33 UTC. Data version: 2026-11-05-v1. Checksums identify the published supporting files. The limitations and exception records remain part of the result. Report a reproducible correction by email at support@aifontgenerator.net, including the file hash and case ID.

The snapshot includes the 61 files linked from public catalog pages 1–3, using one primary current-version preview file per family. Sixty detail pages were in the sitemap; Bubble Pillow Square was publicly listed but omitted from it. Sitemap membership was not used as a substitute for public availability.

The earlier language character reference compares CLDR-derived character sets with a synthetic ASCII baseline. This audit uses real font files and shaped sequence output. Its measurements are independent of that reference's simulated coverage counts.

Data downloads and verification

Download the files into one directory. The manifest pins the original public preview URLs and byte hashes; font binaries are not redistributed here. The raw gzip contains 61 font headers and 30,561 shape records. The checksum file covers every other attachment.