Take the typescale's tracking from Compose, and reset roundness

Tracking now comes from androidx's TypeScaleTokens.kt as sp/16 rem, so Display
Large is -0.0125rem, Title Medium and Body Medium 0.0125rem where material-web's
pre-Expressive numbers stood. The emphasized set gets its own
--md-sys-typescale-emphasized-<style>-tracking, which the emphasized utilities
read: M3 widens four of them and takes Display Large back to zero. Every regular
utility sets `font-variation-settings: normal`, because the property inherits and
body copy inside an emphasized heading is meant to read as itself.

bin/check-font.mjs (fontkit) prints the packaged woff2's fvar axes, so a
re-subset cannot silently drop the ROND axis the emphasized styles depend on;
`npm run check:font` runs it and tests/Feature/FontTest.php runs it through the
configured node binary, skipping when node cannot run (PHP cannot open a woff2
without Brotli).

Plan: docs/plans/material-3-alignment.md step 4, findings core C5, C6, C7.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 05:29:09 +02:00
co-authored by Claude Fable 5.1
parent b1fc0c9cfa
commit 2feb623ec0
7 changed files with 366 additions and 25 deletions
+60
View File
@@ -0,0 +1,60 @@
/**
* Checks that the packaged Google Sans Flex subset still carries the variable axes the CSS uses.
*
* Run from the repository root with `npm run check:font` (or `node bin/check-font.mjs [woff2]`).
* tests/Feature/FontTest.php runs it through the configured `node` binary as well, because PHP
* cannot open a woff2 without the Brotli extension.
*
* wght 400700 — every typescale weight (font.css `font-weight: 400 700`, type.css's regular,
* medium and bold reference tokens).
* ROND 0100 — the roundness axis every `type-emphasized-*` utility sets to 100. Re-subset
* the font without it and the emphasized styles quietly stop being round.
*
* Output is one JSON object on stdout — {file, postscriptName, numGlyphs, axes: {tag: {name, min,
* default, max}}} — and the exit status is 1, with the reason on stderr, when an axis is missing
* or narrower than the range above.
*/
import { openSync } from 'fontkit'
import { fileURLToPath } from 'node:url'
/** The axes the stylesheets depend on, and the range each one has to cover. */
const REQUIRED = {
wght: { min: 400, max: 700 },
ROND: { min: 0, max: 100 },
}
const file = process.argv[2] ?? fileURLToPath(new URL('../resources/fonts/google-sans-flex/GoogleSansFlex-Latin.woff2', import.meta.url))
let font
try {
font = openSync(file)
} catch (error) {
process.stderr.write(`${file}: ${error.message}\n`)
process.exit(1)
}
const axes = font.variationAxes ?? {}
const problems = Object.entries(REQUIRED).flatMap(([tag, range]) => {
const axis = axes[tag]
if (!axis) {
return [`${tag} is missing; the subset has ${Object.keys(axes).join(', ') || 'no variable axes'}.`]
}
return axis.min > range.min || axis.max < range.max
? [`${tag} covers ${axis.min}${axis.max}, not the ${range.min}${range.max} the stylesheets ask for.`]
: []
})
process.stdout.write(`${JSON.stringify({
file,
postscriptName: font.postscriptName,
numGlyphs: font.numGlyphs,
axes,
})}\n`)
if (problems.length > 0) {
process.stderr.write(`${file}\n${problems.map((problem) => ` ${problem}`).join('\n')}\n`)
process.exit(1)
}