v0.8: self-hosted fonts replace the Google Fonts CDN link

index.html now loads /api/fonts/fonts.css, synthesized at request time
by goods-editor's mountEditorApi from the host's fonts/ directory
(populated via fetch-fonts in goods-editor-module — the host-local
fetch script shrinks to a pointer). Shipped synchronously in <head>
on purpose: the module's runtime injector races first paint and the
editor's text measurement gives wrong answers against fallback
metrics until real fonts load.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
khalid@traclabs.com
2026-09-01 23:29:04 -05:00
parent 6599a5e68f
commit dfa1f90a16
2 changed files with 40 additions and 141 deletions

View File

@@ -6,9 +6,25 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#fdf2f4" />
<title>Pawfectly Yours — Customize Your Shirt</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700&family=Bebas+Neue&family=Caveat:wght@400;600;700&family=DM+Sans:wght@400;500;700&family=Fira+Sans:wght@400;500;600;700&family=Lato:wght@400;700&family=Lora:wght@400;500;600;700&family=Merriweather:wght@400;700&family=Montserrat:wght@400;600;700&family=Nunito:wght@400;500;600;700;800&family=Open+Sans:wght@400;600;700&family=Oswald:wght@400;500;700&family=Pacifico&family=Playfair+Display:wght@400;600;700&family=Poppins:wght@400;500;600;700&family=Raleway:wght@400;600;700&family=Roboto:wght@400;500;700&family=Roboto+Condensed:wght@400;700&family=Roboto+Slab:wght@400;700&family=Rubik:wght@400;500;600;700&family=Source+Sans+3:wght@400;600;700&family=Space+Mono:wght@400;700&family=Ubuntu:wght@400;500;700&family=Work+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<!--
Fonts are served by the goods-editor module from the host's fonts/
directory (populated by `npm run fetch-fonts` in goods-editor-module).
mountEditorApi synthesizes the @font-face rules at request time
based on whatever .ttf/.otf/.woff2/.woff files are present, so
adding a font is a "drop file + restart" workflow with no code
change here. See goods-editor-module/src/server/fontRegistry.js
for the generator.
We deliberately ship this synchronously in <head> rather than
letting the module inject a <link> at mount time: the module's
runtime injector races first paint, and the editor's font-
dependent text measurement (`measureTextWidth`) gives wrong
answers against fallback metrics until the fonts load. The
host pays a tiny extra TTFB to make first paint correct.
<ApparelDesigner loadFonts={false}> in App.jsx / CustomizeRoute
tells the module not to also inject its own runtime tag.
-->
<link rel="stylesheet" href="/api/fonts/fonts.css">
</head>
<body>
<div id="root"></div>

View File

@@ -1,145 +1,28 @@
#!/usr/bin/env node
/**
* Downloads TTF font files for the Google Fonts used in the editor and saves
* them under ./fonts/. server.js scans that directory at startup and calls
* registerFont() for each file.
* DEPRECATED — fonts now live with goods-editor-module.
*
* Why we do this:
* node-canvas does not pick up Google Fonts automatically. It only knows
* about fonts that are either (a) registered explicitly via registerFont()
* or (b) installed system-wide and discoverable via fontconfig. Without
* this script, the server falls back to whatever Cairo can find for
* "Roboto", "Bebas Neue", etc., which is usually nothing — exports get
* substituted to a generic sans-serif and look nothing like the preview.
* The host used to fetch TTFs into its own fonts/ directory and
* register them at server boot. After the v0.8 module reorg, font
* ownership moved into goods-editor-module:
*
* Source:
* We pull TTFs from the Fontsource jsDelivr CDN. URLs look like
* https://cdn.jsdelivr.net/fontsource/fonts/<slug>@latest/latin-400-normal.ttf
* Slugs are the lowercase, hyphenated family name. The script asks for two
* weights per family (regular + bold) and falls through gracefully if a
* weight isn't published.
* • The TTF files live under goods-editor-module/fonts/.
* • Fetch them with `npm run fetch-fonts` inside goods-editor-module.
* • mountEditorApi (called from the host's server.js) registers them
* with node-canvas at boot and serves them to the browser via
* /api/fonts/fonts.css.
* • The host's index.html loads that stylesheet directly — no more
* Google Fonts CDN, no more host-side TTFs.
*
* Usage:
* npm run fetch-fonts
*
* Re-running is cheap: existing files are skipped unless --force is passed.
* This file is kept as a tombstone so anyone who looks for it
* understands what happened. Safe to `rm scripts/fetch-fonts.mjs`
* in the next housekeeping commit; nothing references it anymore
* (package.json's scripts block was already trimmed during the
* module-split work in May 2026).
*/
import { mkdir, writeFile, access } from 'node:fs/promises';
import { constants as fsConstants } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');
const FONTS_DIR = join(ROOT, 'fonts');
const FORCE = process.argv.includes('--force');
// Families used by the editor (FONTS in src/constants/fonts.js + the two
// CSS-level defaults). Keep this list in sync with that file.
const FAMILIES = [
'Roboto',
'Open Sans',
'Lato',
'Montserrat',
'Oswald',
'Raleway',
'Poppins',
'Roboto Condensed',
'Source Sans 3',
'Roboto Slab',
'Merriweather',
'Ubuntu',
'Playfair Display',
'Nunito',
'Rubik',
'Work Sans',
'Lora',
'Fira Sans',
'Barlow',
'Bebas Neue',
'DM Sans',
'Space Mono',
// Display + handwritten faces used by the Pawfectly Yours theme. Pacifico is
// the brand logo face; Caveat is a friendly script fallback exposed in the
// text-tab font picker so users can match the brand voice in their designs.
'Pacifico',
'Caveat',
];
const WEIGHTS = [
{ weight: 400, suffix: 'Regular' },
{ weight: 700, suffix: 'Bold' },
];
const familyToSlug = (family) => family.toLowerCase().replace(/\s+/g, '-');
// Filename convention used by server.js's registration scanner:
// "Family_Name-Variant.ttf" → family = "Family Name", variant = "Variant".
// Underscore-vs-space is so the file system stays portable.
const familyToFileBase = (family) => family.replace(/\s+/g, '_');
async function fileExists(path) {
try {
await access(path, fsConstants.R_OK);
return true;
} catch {
return false;
}
}
async function fetchOne(family, weight, suffix) {
const slug = familyToSlug(family);
const url = `https://cdn.jsdelivr.net/fontsource/fonts/${slug}@latest/latin-${weight}-normal.ttf`;
const dest = join(FONTS_DIR, `${familyToFileBase(family)}-${suffix}.ttf`);
if (!FORCE && await fileExists(dest)) {
return { family, weight, status: 'skipped', dest };
}
const res = await fetch(url);
if (!res.ok) {
return { family, weight, status: 'missing', dest, code: res.status };
}
const buf = Buffer.from(await res.arrayBuffer());
await writeFile(dest, buf);
return { family, weight, status: 'fetched', dest, bytes: buf.length };
}
async function main() {
await mkdir(FONTS_DIR, { recursive: true });
let fetched = 0, skipped = 0, missing = 0;
for (const family of FAMILIES) {
for (const { weight, suffix } of WEIGHTS) {
try {
const r = await fetchOne(family, weight, suffix);
if (r.status === 'fetched') {
fetched++;
console.log(`✓ ${family} ${weight} → ${r.dest} (${(r.bytes / 1024).toFixed(0)} KB)`);
} else if (r.status === 'skipped') {
skipped++;
console.log(`· ${family} ${weight} (already present)`);
} else {
missing++;
console.log(`! ${family} ${weight} HTTP ${r.code} — variant not published`);
}
} catch (err) {
missing++;
console.log(`! ${family} ${weight} ${err.message}`);
}
}
}
console.log(`\nDone. Fetched ${fetched}, skipped ${skipped}, missing ${missing}.`);
if (missing > 0) {
console.log('Missing variants are not fatal — the server will fall back to other registered weights.');
}
}
main().catch((err) => {
console.error('Font fetch failed:', err);
console.error(
'[fetch-fonts] This script has moved to goods-editor-module.\n' +
' Run: (cd ../goods-editor-module && npm run fetch-fonts)'
);
process.exit(1);
});