@adrienlcp/measure-font
A dev-time bin that measures a self-hosted font against Arial for the fallback faces of @adrienlcp/styles — per weight and width, over the app's own text, its dictionaries or its prerendered pages, with the digits apart and the ratio range that keeps each edge line wrapped — and converts a ch to em
Works with
- TypeScript
Install
pnpm add -D @adrienlcp/measure-fontExports
Grouped by the section of the documentation that explains them. A row leads to its section.
The library7 exports
openFontOpens awoff2,woff,ttforotffile; a collection is refused.FunctionatVariationThe font read atvariation, on the axes it has: a static file, or an axis the file lacks, is read as it is.FunctiontextWidthThe width oftextin em, shaped and kerned as a browser sets it, at the variation and with the OpenType features ofstyle—tnumandlnumfortabular-nums lining-nums.FunctionzeroWidthThe advance of the zero in em — what onechis in this font atvariation, son chconverts ton × zeroWidthem.FunctionmissingFeaturesThe OpenType features offeaturesthe file holds no lookup for.FunctionverticalMetricsAscent and descent fromhhea, as fontaine reads them, and the capital height.FunctionfontaineSizeAdjustThesize-adjustfontaine computes forfontoverfallback: the ratio of their average character widths, weighted by letter frequency, each read at its default instance.Function
Documented in the source only6 exports
VariationAxisOne axis of a variable font.TypeOpenedFontA font file opened for measuring, decompressed when it came aswoff2.TypeVariationWhere a variable font is read: itswght, and any other axis by tag ({ wdth: 70 }).TypeTextStyleA variation and the OpenType features the text is set with (['tnum', 'lnum']).TypeVerticalMetricsA font's vertical metrics in em, asfonts.fallback-facestakes them.TypeFontFailureType
Documentation
The README and the documents beside it, as written in the repository.
OverviewREADME.md
A dev-time bin that measures a self-hosted font against Arial for the
fonts.fallback-faces mixin of @adrienlcp/styles, and converts
a ch to em — from the font files the app serves, shaped and kerned as
Chromium sets them, a variable file at any weight and width of its axes.
pnpm add -D @adrienlcp/measure-fontA dev dependency: it reads fonts with fontkit, wawoff2, capsize and linkedom,
which an app's runtime never needs. It writes includes for
@adrienlcp/styles 0.18 and later.
measure-fontREADME.md
pnpm exec measure-font public/fonts/onest-latin.woff2 public/fonts/onest-latin-ext.woff2 --weight 400 --weight 700 --text-from-dist distonest-latin.woff2 + onest-latin-ext.woff2 at 400: zero 0.665em, width ratio 1.023 over Arial
onest-latin.woff2 + onest-latin-ext.woff2 at 700: zero 0.6602em, width ratio 1.0497 over Arial Bold
cap height from the OS/2 table
size-adjust 1.052039, fontaine's, computed from the first file
@include fonts.fallback-faces('Onest', (ascent: 0.97, descent: 0.305, cap-height: 0.707), 1.052039, (400: 1.023, 700: 1.0497))Warnings go to stderr, prefixed warning:; the report and the include to
stdout.
The filesREADME.md
- List the family's files in the order of its
@font-facerules, the regular first: fontaine measures that one. A static file is measured at its own weight;--weight(repeated) picks the weights of a variable one. Widen each weight of the include to the band it stands for:(300 449: …, 650 800: …). - The
unicode-rangesubsets of one face are one font. Files of one family, style, weight and axes —latinandlatin-ext— are measured as a browser draws them: each character from the first subset that has it. A face that draws none of the text is refused: list alatin-extfile after itslatinone, never alone. A character no file draws is left out of both widths and listed — the next font of the stack draws it in either face. --axis wdth=70reads another axis of a variable file, and writes$stretch: 70%: one run per width band the app sets, each widened to its band of stretches ($stretch: 62% 70%). A face without that axis is refused.--italicmeasures italic files over the italic cuts of Arial and writes$style: italic. Two faces at one weight — two families, or two styles — are refused: one family in one style per run.- Arial is found where Windows, macOS and Linux keep it — Liberation Sans,
drawn on Arial's widths, will do —, or passed with
--fallbackand--fallback-bold;--bold-from(650) and--familymatch the include.
The textREADME.md
Measure the app's own text, as it shows. Every source given is read, one after the other; without any, a pangram in English and French, whose ratios say how the font stands to Arial in general — a warning says so.
--text <text>and--text-file <path>, repeated. Spaces, tabs and newlines collapse to one space as a browser lays them out — a newline measured raw is a glyph and widens every ratio —, and a text that is spaces alone is refused.--text-from <path>reads an i18n dictionary: a.jsontree, or a.tsor.jsmodule Node imports, its default export or else every export. Every string leaf is a message; an@adrienlcp/i18ndefineTranslationgives its message, its plural forms and its enum members.{placeholders}and<tags>are left out: their values are the app's.--text-from-dist <dir>reads every prerendered.htmlpage under a directory — the body's text nodes, without scripts, styles, templates andnoscript.--select 'h1, h2'reads only the elements a selector matches, for the text set at one weight.--uppercasemeasures it astext-transform: uppercaseshows it.- One band, one text. A weight band is best measured over the text set in
it: run once per band —
--weight 700 --text-from-dist dist --select 'h1, h2' --uppercase— and gather the entries in one include.
The ratio range of edge linesREADME.md
A mean ratio keeps most lines; the one that fills its box to a few pixels
wants its own. --lines <path> reads those lines:
[
{ "text": "Print the family sheet", "box": 327, "fontSize": 16, "weight": 400 },
{ "text": "COLOR DOTS", "box": 288, "fontSize": 48, "weight": 800, "letterSpacing": -0.02 }
]boxis the content width the line is laid out in andfontSizeits font size, both in px as DevTools shows them;letterSpacingis in em;weightdefaults to the first weight measured, and a weight not measured is refused. Write the text as it shows — uppercased where it is.- Each line the web font fits must fit the fallback, and each it wraps must
wrap there too. The report gives the range per band —
1.0041 ≤ ratio < 1.0091 keeps its 3 lines wrapped as the web font wraps them— and the include writes the text's ratio when it is inside, else the nearest value that is, with up to six decimals for a narrow range. - When no ratio keeps every line, the report names the two lines in conflict and the include keeps the text's ratio.
The digitsREADME.md
--figuresalso measures the digits alone and writes$figures, once for every band when every weight gives the same ratio (within 0.1 %).--figure-feature(repeated) names the OpenType features they are set with:tnumandlnumfortabular-nums lining-nums. A file without one is refused — a browser sets its digits without it, so the measure would describe figures the page never shows.tnumpasses on a file whose digits already share one advance.--figure-separatorsmeasures: . ,with the digits and writes$figure-separators: true.--figures-onlymeasures the digits alone and prints the$figuresargument alone, to add to an include written before: the letter ratios over the default pangram are not corrections to apply.- A warning says when, at some weight, the digits stand more than 10 % off
the letters: a figures face is owed, or — with
--figures— something in the measure deserves a second look. - A monospace family needs no
$figures: its digits are as wide as its letters.
The metricsREADME.md
--size-adjusttakes the value fontaine wrote in the built CSS. Without it, the bin computes fontaine's formula from the first file; fontaine reads a Google font from capsize's collection instead, so the two can differ in the fourth decimal. The value only sets the scale the ratios are read on:--size-adjust 1leaves it to the mixin's default and writes$widths:by name.--ascentand--descentreplace thehheametrics in the include. A font that setsUSE_TYPO_METRICSdraws its line box from itsOS/2typo metrics in a browser that honours the flag; the bin warns and prints them.- Cap height: read from the
OS/2table, and from the top of theHoutline when the table has none — anOS/2table older than version 2, where fontaine'sreadMetricsreturnscapHeight: null. That value is the one$trimmed-to-capitalsneeds. - The zero is a
ch: a measure given as46chis46 × zeroem. Read it at the weight of the elements that read the measure — a prose column at the body weight, not at the heading's.
The libraryREADME.md
What the bin runs on, for a script or a test of the app's own:
import { readFileSync } from 'node:fs'
import { openFont, zeroWidth } from '@adrienlcp/measure-font'
const font = await openFont(readFileSync('public/fonts/onest-latin.woff2'))
if (font.status === 'success') console.log(`46ch is ${46 * zeroWidth(font.data, { weight: 400 })}em`)openFont(bytes)reads awoff2,woff,ttforotffile, awoff2decompressed first, and failsunreadableorcollection.axesholds every axis of a variable file,weightAxisitswght.zeroWidth(font, variation?)is the zero's advance in em;textWidth(font, text, style?)the width of a text, shaped and kerned. A variation is{ weight, axes: { wdth: 70 } }, read on the axes the file has; a style addsfeatures: ['tnum', 'lnum'].atVariationis the fontkit font at a variation.missingFeatures(font, features)lists the OpenType features the file holds no lookup for.verticalMetrics(font)is the ascent and descent fromhheaand the capital height, withcapHeightFrom:'OS/2', or'H'when the table has none, andtypo: the typo metrics whenUSE_TYPO_METRICSmakes them the ones a browser may use.fontaineSizeAdjust(font, fallback)is fontaine'ssize-adjustformula.