cd /news/developer-tools/what-the-fstype-field-in-a-font-mean… · home › topics › developer-tools › article
[ARTICLE · art-160129] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

What the fsType field in a font means, and how to read it with fontTools

A developer documented how to read the fsType embedding-permission field in the OS/2 table of TrueType and OpenType fonts using fontTools, showing that the low four bits hold a single usage value (0, 2, 4 or 8) while bits 8 and 9 are separate no-subsetting and bitmap-only flags. Scanning fonts installed on a Mac turned up only values 0, 4 and 8, and extracting fonts from a PDF with PyMuPDF revealed that Type 1 fonts such as PlantinMTStd have no OS/2 table, so the field reads as None rather than zero.

by read4 min views1 publishedOct 29, 2026

Font tooling keeps mentioning fsType, and it is easy to treat it as a magic number where 0 is fine and anything else is a problem. The spec says something more specific, and a few things in it are easy to misread. So I read the field on fonts installed on my Mac, then on the fonts inside a PDF, and wrote down what each reading means.

fsType lives in the OS/2 table of a TrueType or OpenType font. The OpenType spec (the OS/2 page on learn.microsoft.com, read on 2026-10-28) splits the 16 bits into parts. Bits 0 to 3 hold the usage permission. Bit 8 means the font must not be subsetted. Bit 9 means only bitmap embedding is allowed. The rest are reserved.

The permission part is not a set of independent switches. It holds one value, and the valid ones are 0, 2, 4 and 8. Zero is installable embedding, 2 is restricted license embedding, 4 is preview and print embedding, and 8 is editable embedding. Value 2 is the most restrictive of the four.

This is the part I had to slow down for. The usage value 8 is what you get when bit 3 is set, so in binary it is 0000000000001000. Bit 8 is a different thing entirely: it is the value 256, 0000000100000000, and it says "no subsetting". Bit 9 is 512, "bitmap only". If you only look at the decimal number and never at the binary form, 8, 256 and 512 all feel like the same kind of flag, and they are not.

That is why the snippet below prints the binary string and splits the value with masks instead of comparing against a list of numbers. v & 0xF isolates the permission part, and v & 0x100 and v & 0x200 test the two flags.

The script takes font paths and prints one line per font. It handles .ttc collections by opening the first font inside, so that a collection does not need special handling. The names are placeholders for my own files: A is Noto Serif SC, B is Arial, C is Palatino, D is Helvetica.

python3 - 'FONT-A.otf' 'FONT-B.ttf' 'FONT-C.ttc' 'FONT-D.ttc' <<'P'
import sys
from fontTools.ttLib import TTFont, TTCollection
for p in sys.argv[1:]:
    f = TTCollection(p, lazy=True).fonts[0] if p.endswith('.ttc') else TTFont(p, lazy=True)
    v = f['OS/2'].fsType
    print(f"{f['name'].getBestFullName():<18} fsType={v:<4} {v:016b}  usage={v & 0xF} no_subset={bool(v & 0x100)} bitmap_only={bool(v & 0x200)}")
P

Noto Serif SC      fsType=0    0000000000000000  usage=0 no_subset=False bitmap_only=False
Arial              fsType=8    0000000000001000  usage=8 no_subset=False bitmap_only=False
Palatino           fsType=4    0000000000000100  usage=4 no_subset=False bitmap_only=False
Helvetica          fsType=0    0000000000000000  usage=0 no_subset=False bitmap_only=False

Across the fonts I scanned on this one machine, the values were only ever 0, 4 or 8. Pacifico, Liberation Sans, Noto Sans, Inter, Helvetica, Times and Menlo gave 0. Palatino, Avenir Book and PingFang SC gave 4. Arial, Georgia and Impact gave 8. I found no installed font with 2, 256 or 512. Anything I say below about those three values comes from copies where I changed the field myself, not from fonts I found in the wild.

Ask the same question of a PDF and there is one more trap. A PDF usually carries a subset of each font, and PyMuPDF can pull those bytes back out with extract_font, after which fontTools reads the OS/2 table as above. My sample was the first four pages of the "Evaluating Wikipedia" brochure from Wiki Education Foundation and others, licensed CC BY-SA 3.0. Its embedded fonts were NewsGothicBT in three weights with fsType 4, ArialMT with 8, and PlantinMTStd, which printed None.

That None is not a zero and it does not mean "no restriction". PlantinMTStd is a Type 1 font, and Type 1 fonts have no OS/2 table at all, so there is no field to read. If the script treats None as 0, it claims a permission the file never stated. In my version I only call fontTools when the extracted font is a TrueType or OpenType file, and everything else stays None on purpose. A Type 1 font in a PDF therefore tells you nothing about license through this route, so any restriction on it would have to be checked somewhere else, for example in the foundry's terms for that typeface.

The field is a statement of the font's license. What software does with it varies, and I only observed two cases. Chromium 149 printing to PDF, using my modified copies of Liberation Sans, with each value: for 2 and 512 it did not embed the font and wrote the glyphs as Type 3 outlines. For 256 it embedded the whole font without subsetting, and the PDF grew from 85,024 to 502,702 bytes. For 4 and 8 it embedded a normal subset. That is one machine and two TrueType fonts (Liberation Sans and Pacifico), and I did not read Chromium's source to see why.

I also ran the PDFs through ImgIng (https://imging.ai/), a Beta tool that runs in a desktop browser and processes files locally. Its output turned TrueType subsets into inlined woff fonts, drew Type 3 glyphs as paths, and fell back to a system font for the Type 1 face. How it makes those choices internally I don't know, and I would not claim it reads fsType to do it.

── more in #developer-tools 4 stories · sorted by recency
── more on @fonttools 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/what-the-fstype-fiel…] indexed:0 read:4min 2026-10-29 · —