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:
22
index.html
22
index.html
@@ -6,9 +6,25 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||||
<meta name="theme-color" content="#fdf2f4" />
|
<meta name="theme-color" content="#fdf2f4" />
|
||||||
<title>Pawfectly Yours — Customize Your Shirt</title>
|
<title>Pawfectly Yours — Customize Your Shirt</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<!--
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
Fonts are served by the goods-editor module from the host's fonts/
|
||||||
<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">
|
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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,145 +1,28 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* Downloads TTF font files for the Google Fonts used in the editor and saves
|
* DEPRECATED — fonts now live with goods-editor-module.
|
||||||
* them under ./fonts/. server.js scans that directory at startup and calls
|
|
||||||
* registerFont() for each file.
|
|
||||||
*
|
*
|
||||||
* Why we do this:
|
* The host used to fetch TTFs into its own fonts/ directory and
|
||||||
* node-canvas does not pick up Google Fonts automatically. It only knows
|
* register them at server boot. After the v0.8 module reorg, font
|
||||||
* about fonts that are either (a) registered explicitly via registerFont()
|
* ownership moved into goods-editor-module:
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* Source:
|
* • The TTF files live under goods-editor-module/fonts/.
|
||||||
* We pull TTFs from the Fontsource jsDelivr CDN. URLs look like
|
* • Fetch them with `npm run fetch-fonts` inside goods-editor-module.
|
||||||
* https://cdn.jsdelivr.net/fontsource/fonts/<slug>@latest/latin-400-normal.ttf
|
* • mountEditorApi (called from the host's server.js) registers them
|
||||||
* Slugs are the lowercase, hyphenated family name. The script asks for two
|
* with node-canvas at boot and serves them to the browser via
|
||||||
* weights per family (regular + bold) and falls through gracefully if a
|
* /api/fonts/fonts.css.
|
||||||
* weight isn't published.
|
* • The host's index.html loads that stylesheet directly — no more
|
||||||
|
* Google Fonts CDN, no more host-side TTFs.
|
||||||
*
|
*
|
||||||
* Usage:
|
* This file is kept as a tombstone so anyone who looks for it
|
||||||
* npm run fetch-fonts
|
* understands what happened. Safe to `rm scripts/fetch-fonts.mjs`
|
||||||
*
|
* in the next housekeeping commit; nothing references it anymore
|
||||||
* Re-running is cheap: existing files are skipped unless --force is passed.
|
* (package.json's scripts block was already trimmed during the
|
||||||
|
* module-split work in May 2026).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mkdir, writeFile, access } from 'node:fs/promises';
|
console.error(
|
||||||
import { constants as fsConstants } from 'node:fs';
|
'[fetch-fonts] This script has moved to goods-editor-module.\n' +
|
||||||
import { join, dirname } from 'node:path';
|
' Run: (cd ../goods-editor-module && npm run fetch-fonts)'
|
||||||
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);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user