Compare commits
2
Commits
main
...
spielwiese
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5812a9f0ab | ||
|
|
4742f724f1 |
@@ -0,0 +1,6 @@
|
||||
# Pristine Montserrat originals, re-downloadable by tools/rescale-aspect.py.
|
||||
tools/_originals/
|
||||
|
||||
# Python bytecode
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
This is my very first HTML and CSS project.
|
||||
Please help me to learn.
|
||||
Explain concepts and functionalities in detail.
|
||||
Binary file not shown.
Binary file not shown.
+5
-8
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
Reference in New Issue
Block a user