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.