Matterhorn 31-013 · PDF/UA-1 UA1:7.21.4.2-2

CharSet lists glyphs the embedded Type 1 font lacks

BlockerCheckpoint 31: FontsWCAG 1.3.1 (A)

CharSet promises glyphs the font does not contain.

The condition, as the protocol states it. “The FontDescriptor dictionary of an embedded Type 1 font contains a CharSet string, but at least one of the glyphs listed in the CharSet string is not present in the font program.”

Matterhorn Protocol 1.1, checkpoint 31, index 31-013, section UA1:7.21.4.2-2. Software can decide this one on its own.

What a screen reader actually does

Processors may mis-handle the subset.

What the condition is

A Type 1 font’s descriptor may carry /CharSet: a string of glyph names listing what the embedded subset contains. PDF/UA-1 lets a file leave it out, but if it is there it has to match the program exactly. Condition 31-012 is a list that leaves a glyph out. This one is a list that names a glyph the program does not have.

The file Taggart’s test suite uses for this page embeds a Type 1 font as CFF with three real glyphs, and promises four:

<< /Type /FontDescriptor
   /FontName /JBATCH+CharSetLong
   /FontFile3 9 0 R          % /Subtype /Type1C — glyphs .notdef, A, B, C
   /CharSet (/A/B/C/D)       % there is no D in the program
>>

The page shows ABC, drawn and extracted correctly. D is a promise with nothing behind it.

Why a promise matters more than an omission

An incomplete list (31-012) undersells the font, and the worst a processor that trusts it can do is fail to reuse a glyph it could have used. An over-full list oversells it. A processor that decided from CharSet whether the embedded subset can draw some new text — rather than opening the program to find out — would conclude that it can set a D, and set one. The viewer would then look for D in the program, not find it, and draw .notdef: a blank or a box, where the text says there is a letter. That is 31-011, produced by trusting this one.

What a screen-reader user hears

In the file as it stands, nothing different. The list is not read when text is drawn or extracted, and every character on the page is set in a glyph that exists. The condition is a false statement waiting for something to act on it.

Where it comes from

A subset that shrank after its list was written. A tool that slims files by removing glyphs no text uses — or re-subsets fonts while merging documents — and copies the old descriptor across leaves a list naming glyphs that have just been taken out. The extra names are then glyphs the document used to have.

A list written from intentions rather than results. A producer that writes CharSet from the characters it meant to embed, and then embeds a font that turns out not to contain some of them, lists names the program never had.

A descriptor shared between two subsets. If two subsets of one font end up pointing at the same descriptor, at most one of them can match its list.

How to check it free

Compare the list with the program. In Acrobat Pro, following Adobe’s guide to advanced preflight inspections: select Use print production → Preflight from the All tools menu, then Options → Browse Internal PDF Structure, and read the CharSet string in the font descriptor. From the same Options menu, Create Inventory lists the glyphs each embedded font actually contains — Adobe’s page on Preflight reports describes it. A name in the string that is not in the inventory is this condition.

veraPDF checks CharSet under clause 7.21.4.2 for fonts with a subset tag. Its rule is phrased in terms of glyphs the list fails to name, which is 31-012; see the FAQ below before relying on it for this direction.

How to fix it free

Delete the entry, if the file does not also claim PDF/A-1. CharSet is optional and the condition only exists when it is present, so removing it resolves this without changing what is drawn or read. Acrobat cannot edit font descriptors — Adobe’s page says the structure browser is for looking, not editing — but the free Python library pikepdf can, in a few lines:

import pikepdf

with pikepdf.open("in.pdf") as pdf:
    for obj in pdf.objects:
        if isinstance(obj, pikepdf.Dictionary) and "/CharSet" in obj:
            print("removing CharSet from", obj.get("/FontName"))
            del obj["/CharSet"]
    pdf.save("out.pdf")

If the file claims PDF/A-1, which requires CharSet on Type 1 subsets, shorten the list to the names the inventory shows instead of removing it.

If the subset itself is wrong — text was meant to use a glyph that was dropped — deleting the list only removes the warning. Re-export from the source so the subset and its description are built together from the final text.

What Taggart does

Taggart does not detect this condition. It reads font dictionaries and content streams but does not open embedded font programs, so it never learns which glyphs a program holds and has nothing to compare a CharSet with. On the test file it reports nothing about the font. It does not pass the condition either: 31-013 is on the list of conditions Taggart does not check, and the conformance claim waits until a person confirms them.

The only CharSet problem Taggart reports is a CharSet on a font with no embedded program at all, which it files under 31-012 together with 31-009. For a list that promises glyphs the program lacks, the inventory comparison above is the check to do before you confirm it.

How Taggart handles it

Software could decide this one, and Taggart does not check it yet. A clean report from Taggart says nothing about it either way. It is on the list of conditions Taggart asks you to confirm before it writes the PDF/UA claim into the file; check it by hand or with another validator, as described above.

Whatever Taggart changes, the page still looks identical. Fixes edit the PDF object model, not the content streams, and every remediation is re-rendered and compared against the original at structural similarity ≥ 0.999 before you get the file back. A tool that quietly reflows your document is worse than no tool.

Frequently asked questions

If the extra names are never used, what is the harm?

Nobody reading the document hears anything different. The harm is to software that decides what a subset can draw by reading the list instead of the program: it will believe the font has glyphs it does not, and anything it sets in them comes out as blanks — condition 31-011.

How is this different from 31-012?

It is the same list wrong in the other direction. 31-012 is a CharSet that leaves out a glyph the program contains; this is a CharSet that names a glyph the program does not contain. A list copied from an older subset often fails both ways at once.

Does veraPDF catch this direction?

veraPDF's rule for clause 7.21.4.2 is worded around glyphs present in the program that the list fails to name, and only applies to fonts with a subset tag. Its documentation does not say whether a name with no glyph behind it fails it too, so do not take a veraPDF pass as proof on this one; compare the list with the program directly.

Where this sits in the standards

Standards this condition maps to
StandardReference
Matterhorn Protocol 1.1Checkpoint 31 (Fonts), index 31-013
PDF/UA-1 (ISO 14289-1)Clause 7.21.4.2-2
WCAG 2.11.3.1 Info and Relationships — Level A
EN 301 549 / Section 508 / ADA Title IIAll three point at WCAG 2.1 Level AA for non-web documents, so a Level A or AA criterion here is in scope for each of them.

Other conditions in checkpoint 31

Checkpoint 31 covers fonts. These are its other failure conditions; the ones without a link are in the rule set but do not have a written page yet.

Check your own file. Taggart does not check this condition yet, and lists it for you to confirm. Its validator is free and checks 75 of the 87 Matterhorn conditions software can decide — no watermark, no expiry, and no account needed up to 60 pages.

Check a PDF free

Taggart does not check this condition yet. A generated file that fails it is in the repository, with a test that fails the day Taggart starts catching it. Written on . The Acrobat steps follow Adobe’s documentation and were not walked through by hand. Last updated .