The OS/2 table
Last updated 2026-11-02
On this page
OS/2 began as a Microsoft addition to TrueType for Windows typography (named after an operating system that no longer exists). Today it is the main table describing how heavy, how wide and how tall a font is, and almost every layout program reads it.
It is also a classic "pile of compatibility patches": each version added fields, newer fields partly replaced the meaning of older ones, so the same concept often has two sets of values.
Weight and width
| Field | Range | Notes |
|---|---|---|
usWeightClass |
1–1000 | Conventionally multiples of 100: 100 Thin, 300 Light, 400 Regular, 500 Medium, 700 Bold, 900 Black |
usWidthClass |
1–9 | 1 Ultra-condensed … 5 Normal … 9 Ultra-expanded |
usWeightClass is a suggestion, not a scale. A designer can call their Regular 400 or 350. So "this font looks heavier on one system than another" is sometimes not a rendering bug but a differently filled field.
Width is a separate axis: a Condensed face usually has a smaller usWidthClass, and Condensed in its family name too. Both need to agree, or width-aware matching picks the wrong face.
fsSelection: the italic and bold switches
fsSelection is a bit field. The bits you will meet:
| Bit | Meaning |
|---|---|
| 0 | ITALIC — this face is italic |
| 5 | BOLD — this face is bold |
| 6 | REGULAR — the regular face (mutually exclusive with the two above) |
| 7 | USE_TYPO_METRICS — use the sTypo* metrics rather than usWin* |
| 8 | WWS — the names follow the WWS convention |
Bit 7 explains a lot of "why is the line height different on macOS and Windows": it tells the renderer which set of extents to trust. Without it, engines pick different defaults.
Two sets of vertical metrics
OS/2 carries two sets, and this is historical baggage:
| Field | Sign | Origin |
|---|---|---|
sTypoAscender / sTypoDescender / sTypoLineGap |
ascender positive, descender usually negative | The metrics Microsoft and Apple agreed on for typesetting |
usWinAscent / usWinDescent |
both positive | Early Windows clipping and scrolling bounds, sized for the worst Latin glyph |
That is why the metrics drawing on this site negates winDescent before plotting it below the baseline. And if you see a positive descender, the table is not misread — that is the rule.
x-height and cap height
Available from version 2:
sxHeight: height of a lowercasex;sCapHeight: height of uppercase letters.
Both are declarations, not measurements taken from outlines. Some fonts fill them inaccurately, some leave 0. To make different fonts look the same size at the same pixel size, the reliable approach is the real x-height, not these fields.
Declaring character coverage
ulUnicodeRange1..4 is 128 bits declaring roughly which ranges a font covers (Latin, Greek, CJK…). ulCodePageRange1..2 does the same per code page.
They cannot answer "does this font have this character": the bits are range-level and frequently over-declared. The only reliable answer comes from cmap — which is exactly how coverage is computed on this site.
The odds and ends
achVendID: a four-byte vendor code (APPL,GOOG), not a trademark statement;panose: ten bytes of "style fingerprint" for similar-font suggestions, often all zero;fsType: embedding permission bits (PDF embedding, read-only embedding); older fonts often say "no embedding";usFirstCharIndex/usLastCharIndex: hints about the character range of non-symbol fonts — hints only.
How this site uses it
- Weight: shows
usWeightClassand says it may be approximate; - Italic: reads bit 0 of
fsSelectioninstead of looking forItalicin the family name; - Metrics drawing: plots both the
hheaandOS/2extents, because which one counts depends on the renderer.
Further reading
- OpenType spec: the
OS/2table — each version documents the fields it added - OpenType spec:
fsSelectionbit definitions andUSE_TYPO_METRICS