fonts and colors

This commit is contained in:
2026-09-27 15:13:00 +02:00
parent 6004595d60
commit 4742f724f1
6 changed files with 201 additions and 11 deletions
+6
View File
@@ -0,0 +1,6 @@
# Pristine Montserrat originals, re-downloadable by tools/rescale-aspect.py.
tools/_originals/
# Python bytecode
__pycache__/
*.pyc
Binary file not shown.
Binary file not shown.
+5 -8
View File
@@ -6,13 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Raum und Linie</title>
<meta name="description" content="Raum für Deine Ideen." />
<link rel="preload" href="fonts/montserrat-latin-400-aspect.woff2" as="font"
type="font/woff2" crossorigin />
<link rel="stylesheet" href="style/base.css" />
<style>
body {
background-color: var(--color-background);
font-family: var(--font-body)
}
</style>
</head>
<body>
@@ -21,9 +18,9 @@
</header>
<main>
<p>Hier entsteht eine neue Seite.</p>
<p>🚧 Hier entsteht eine neue Seite.</p>
<p>
Der schnellste Weg zu mir:
📬 Der schnellste Weg zu mir:
<a href="mailto:mail@raum-und-linie.de">mail@raum-und-linie.de</a>.
</p>
</main>
+46 -3
View File
@@ -1,5 +1,48 @@
:root{
--color-background: #faf9f7;
--font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
@font-face {
font-family: "Montserrat";
font-style: normal;
font-weight: 400; /* 400 = Regular */
font-display: swap; /* show the fallback first, swap in later */
src: url("../fonts/montserrat-latin-400-aspect.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Montserrat";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("../fonts/montserrat-latin-700-aspect.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
:root{
--color-canvas: #faf9f7;
--color-banner-background: #435245;
--color-banner-text: orange;
--font-body: "Montserrat", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
body {
background-color: var(--color-canvas);
font-family: var(--font-body);
margin: 0px;
}
header {
color: var(--color-banner-text);
background-color: var(--color-banner-background);
padding-left: 32px;
display: flex;
justify-content: center;
}
main {
max-width: 42rem;
margin-inline: auto;
padding-inline: 1rem;
}
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""Generate the aspect-adjusted Montserrat webfonts used by the site.
The design calls for the typeface to be 110% as tall and 85% as wide as
the original Montserrat. CSS cannot do this: `font-stretch` only *selects*
a width variant from a family, it never scales, and Montserrat has no
width axis (its variable build only has `wght`). So the adjustment is baked
into the font files themselves.
Scaling a font's aspect ratio means changing the relationship between the
glyph *outlines* and the *advance widths*. If you scale the shapes but not
the advances, the browser still reserves the original width and every line
looks loosely spaced and wraps too early. So this script scales, in order:
1. every glyph outline coordinate (x * 0.85, y * 1.10)
2. every composite glyph component offset (e.g. the accent on "a-umlaut")
3. every advance width and side bearing (x * 0.85) <- affects layout
4. every vertical metric (ascent, descent, capHeight, xHeight) (y * 1.10)
Non-uniform scaling invalidates TrueType hinting, so the hinting programs
and the pixel-metric tables that reference the old coordinates are dropped.
Usage:
pip install "fonttools[woff]" # or: python3 -m venv .venv && ...
python3 tools/rescale-aspect.py
Pristine originals are cached in tools/_originals/ (git-ignored) and
downloaded from a PINNED Fontsource version, so re-running always yields
byte-identical output. The cache is disposable: delete it any time.
"""
import sys
import urllib.request
from pathlib import Path
try:
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.ttProgram import Program
except ImportError:
sys.exit('fontTools is missing. Install it with: pip install "fonttools[woff]"')
# ---- design parameters ----------------------------------------------------
SCALE_X = 0.85 # width -> 85%
SCALE_Y = 1.10 # height -> 110%
# Only the weights the site actually uses. Each unused weight is a file
# nobody downloads, so there is no reason to ship it.
WEIGHTS = (400, 700)
# Pinned so the output never silently changes under you.
FONTSOURCE_VERSION = "5.3.0"
CDN = f"https://cdn.jsdelivr.net/fontsource/fonts/montserrat@{FONTSOURCE_VERSION}"
ROOT = Path(__file__).resolve().parent.parent
CACHE = ROOT / "tools" / "_originals"
OUT = ROOT / "site" / "fonts"
# Tables whose contents are tied to the old coordinate space.
STALE_TABLES = ("hdmx", "LTSH", "VDMX", "gasp")
def fetch_original(weight: int) -> Path:
"""Download the pristine woff2 into the cache if it isn't there yet."""
CACHE.mkdir(parents=True, exist_ok=True)
dest = CACHE / f"montserrat-latin-{weight}-normal.woff2"
if not dest.exists():
url = f"{CDN}/latin-{weight}-normal.woff2"
print(f" downloading {url}")
with urllib.request.urlopen(url) as response:
dest.write_bytes(response.read())
return dest
def transform(src: Path, dst: Path) -> None:
font = TTFont(src)
glyf = font["glyf"]
order = font.getGlyphOrder()
# 1 + 2. outlines and composite offsets
for name in order:
g = glyf[name]
if g.isComposite():
# Every level's offset is scaled; nesting composes correctly
# because (0.85*a + 0.85*b) == 0.85 * (a + b).
for comp in g.components:
comp.x = round(comp.x * SCALE_X)
comp.y = round(comp.y * SCALE_Y)
elif g.numberOfContours > 0:
# Affine-scaling every point of a quadratic outline keeps it a
# valid quadratic, so no curve conversion is needed.
coords = g.coordinates
for i in range(len(coords)):
x, y = coords[i]
coords[i] = (x * SCALE_X, y * SCALE_Y)
g.program = Program() # hinting no longer matches the coordinates
# 3. horizontal metrics -- this is the part that makes layout reflow
hmtx = font["hmtx"]
for name in order:
advance, lsb = hmtx[name]
hmtx[name] = (round(advance * SCALE_X), round(lsb * SCALE_X))
# 4. vertical metrics, so the line box grows to match the taller glyphs
hhea, os2 = font["hhea"], font["OS/2"]
for table, attr in ((hhea, "ascent"), (hhea, "descent"), (hhea, "lineGap"),
(os2, "sTypoAscender"), (os2, "sTypoDescender"),
(os2, "sTypoLineGap"), (os2, "usWinAscent"),
(os2, "usWinDescent"),
(os2, "sCapHeight"), (os2, "sxHeight")):
value = getattr(table, attr, None)
if value is not None:
setattr(table, attr, round(value * SCALE_Y))
for table in STALE_TABLES:
if table in font:
del font[table]
font.recalcBBoxes = True
font.save(dst)
font.close()
def report(path: Path) -> None:
f = TTFont(path)
cmap = f.getBestCmap()
r = f["glyf"][cmap[ord("R")]]
umlaut = f["glyf"][cmap[ord("ä")]]
print(f" {path.name} ({path.stat().st_size:,} bytes)")
print(f" 'R' advance={f['hmtx']['R'][0]:4d} capHeight={f['OS/2'].sCapHeight}"
f" xHeight={f['OS/2'].sxHeight}")
print(f" umlauts still composite: {umlaut.isComposite()}"
f" glyphs: {f['maxp'].numGlyphs}")
f.close()
if __name__ == "__main__":
print(f"Montserrat {FONTSOURCE_VERSION} -> height {SCALE_Y:.0%}, width {SCALE_X:.0%}\n")
OUT.mkdir(parents=True, exist_ok=True)
for weight in WEIGHTS:
original = fetch_original(weight)
target = OUT / f"montserrat-latin-{weight}-aspect.woff2"
transform(original, target)
report(target)
print("\nDone. Point your @font-face src: at the *-aspect.woff2 files.")