@xingwangzhe/stalux 1.4.6 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.4.6",
3
+ "version": "1.5.1",
4
4
  "description": "A powerful, modern Astro blog theme \u2014 use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -91,7 +91,7 @@
91
91
  "pagefind": "^1.5.2",
92
92
  "photoswipe": "^5.4.4",
93
93
  "simple-icons": "^16.27.1",
94
- "subset-font": "^2.5.0",
94
+ "taetype": "0.1.3",
95
95
  "temml": "^0.13.3"
96
96
  },
97
97
  "devDependencies": {
@@ -12,7 +12,8 @@ import { resolve, join, dirname } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
 
14
14
  import type { AstroIntegrationLogger } from "astro";
15
- import subsetFontFn from "subset-font";
15
+ import * as taetype from "taetype";
16
+ import { register_font_raw, get_glyph_ids, subset_font_full } from "taetype";
16
17
 
17
18
  const FONT_INPUT = "src/assets/fonts/LXGWWenKai-Regular.ttf";
18
19
  const CONTENT_ROOT = "stalux";
@@ -126,17 +127,22 @@ async function generateSubsetOnDemand(
126
127
  const fontBuffer = readFileSync(fontPath);
127
128
  mkdirSync(outDir, { recursive: true });
128
129
 
129
- // Generate subset WOFF2
130
+ // Generate subset TTF using taetype
130
131
  const charsStr = [...charSet].sort().join("");
132
+ try { register_font_raw("stalux", fontBuffer); } catch { /* already registered */ }
133
+ const glyphs = get_glyph_ids(charsStr, "stalux", "normal", 400);
134
+ if (glyphs.length === 0) return null;
135
+ const subsetResult = subset_font_full("stalux", "normal", 400, 0, glyphs);
131
136
  const hash = [...charsStr].slice(0, 12).join("");
132
- const filename = `subset-${subsetId}-${hash}.woff2`;
137
+ const filename = `subset-${subsetId}-${hash}.ttf`;
133
138
  const outPath = join(outDir, filename);
134
139
 
135
140
  if (!existsSync(outPath)) {
136
141
  try {
137
- const data = await subsetFontFn(fontBuffer, charsStr, { targetFormat: "woff2" });
138
- writeFileSync(outPath, data);
139
- logger.info(` on-demand subset: ${(data.length / 1024).toFixed(1)} KB → ${filename}`);
142
+ writeFileSync(outPath, subsetResult.fontBytes);
143
+ logger.info(
144
+ ` on-demand subset: ${(subsetResult.fontBytes.length / 1024).toFixed(1)} KB → ${filename}`,
145
+ );
140
146
  } catch {
141
147
  return null;
142
148
  }
@@ -144,7 +150,7 @@ async function generateSubsetOnDemand(
144
150
 
145
151
  return `@font-face {
146
152
  font-family: "LXGW WenKai Subset";
147
- src: url("/fonts/${filename}") format("woff2");
153
+ src: url("/fonts/${filename}") format("truetype");
148
154
  font-display: swap;
149
155
  }`;
150
156
  }
@@ -7,10 +7,10 @@
7
7
  *
8
8
  * Output layout (under public/fonts/):
9
9
  * fonts/
10
- * common.woff2 — UI text subset (ASCII, nav, i18n)
11
- * post-{abbrlink}.woff2 — per-post subset
12
- * about.woff2 — about page subset
13
- * words.woff2 — combined words subset
10
+ * common.ttf — UI text subset (ASCII, nav, i18n)
11
+ * post-{abbrlink}.ttf — per-post subset
12
+ * about.ttf — about page subset
13
+ * words.ttf — combined words subset
14
14
  * manifest.json — route → subset file mapping
15
15
  * subset.css — global @font-face declarations
16
16
  */
@@ -21,8 +21,9 @@ import { resolve, basename, extname, join, dirname } from "node:path";
21
21
  import { fileURLToPath } from "node:url";
22
22
 
23
23
  import type { AstroIntegrationLogger } from "astro";
24
- // subset-font 是 WASM 模块,需要静态导入(Vite module runner 在构建钩子中不兼容动态 import)
25
- import subsetFontFn from "subset-font";
24
+ // taetype 是 Rust 原生字体引擎,比 subset-font (WASM) 快 2-3 倍
25
+ import * as taetype from "taetype";
26
+ import { register_font_raw, get_glyph_ids, subset_font_full } from "taetype";
26
27
 
27
28
  // ---------------------------------------------------------------------------
28
29
  // Configuration
@@ -263,6 +264,14 @@ export async function runFontSubsetting(
263
264
  }
264
265
  const fontBuffer = readFileSync(fontPath);
265
266
 
267
+ // Register font with taetype for fast Rust subsetting
268
+ try {
269
+ register_font_raw("stalux", fontBuffer);
270
+ } catch (e) {
271
+ logger.warn(`Font registration failed: ${e}, skipping subsetting`);
272
+ return;
273
+ }
274
+
266
275
  // Clean old generated font files to avoid stale cached references
267
276
  const outDir = resolve(projectRoot, FONT_OUT_DIR);
268
277
  if (existsSync(outDir)) {
@@ -298,26 +307,33 @@ export async function runFontSubsetting(
298
307
  });
299
308
  }
300
309
 
301
- // 5. Deduplicate and generate subsets
310
+ // 5. Deduplicate and generate subsets using taetype (Rust native, fast)
302
311
  mkdirSync(outDir, { recursive: true });
303
312
 
304
- // Dynamic import subset-font (ESM only)
305
- let subsetFont = subsetFontFn;
313
+ // Helper: subset using taetype
314
+ function doSubset(chars: string): Uint8Array {
315
+ const glyphs = get_glyph_ids(chars, "stalux", "normal", 400);
316
+ if (glyphs.length === 0) return new Uint8Array(0);
317
+ const result = subset_font_full("stalux", "normal", 400, 0, glyphs);
318
+ return result.fontBytes;
319
+ }
306
320
 
307
321
  // Generate common subset
308
- const commonFilename = `common-${commonHash}.woff2`;
322
+ const commonFilename = `common-${commonHash}.ttf`;
309
323
  const commonOutPath = join(outDir, commonFilename);
310
324
  if (!existsSync(commonOutPath)) {
311
- const data = await subsetFont(fontBuffer, commonChars, { targetFormat: "woff2" });
312
- writeFileSync(commonOutPath, data);
313
- logger.info(` common subset: ${(data.length / 1024).toFixed(1)} KB → ${commonFilename}`);
325
+ const data = doSubset(commonChars);
326
+ if (data.length > 0) {
327
+ writeFileSync(commonOutPath, data);
328
+ logger.info(` common subset: ${(data.length / 1024).toFixed(1)} KB → ${commonFilename}`);
329
+ }
314
330
  }
315
331
 
316
332
  // Generate common CSS
317
333
  const commonCSS = `/* Auto-generated common font subset */
318
334
  @font-face {
319
335
  font-family: "LXGW WenKai Subset";
320
- src: url("/fonts/${commonFilename}") format("woff2");
336
+ src: url("/fonts/${commonFilename}") format("truetype");
321
337
  font-display: swap;
322
338
  }
323
339
  `;
@@ -330,39 +346,38 @@ export async function runFontSubsetting(
330
346
  let subsetCount = 0;
331
347
  const cssGenerated = new Set<string>();
332
348
 
333
- if (!skipPerRoute) {
334
- for (const [hash, routes] of groups) {
335
- const filename = `subset-${hash}.woff2`;
336
- const outPath = join(outDir, filename);
337
- if (!existsSync(outPath)) {
338
- // Find the route chars for this hash
339
- const route = routeSets.find((r) => r.hash === hash);
340
- const chars = route?.chars ?? "";
341
- // Only subset characters NOT in common (already covered by common.css)
342
- const uniqueChars = chars
343
- .split("")
344
- .filter((ch) => !commonChars.includes(ch))
345
- .join("");
346
- if (uniqueChars.length > 0) {
347
- const data = await subsetFont(fontBuffer, uniqueChars, {
348
- targetFormat: "woff2",
349
- });
350
- writeFileSync(outPath, data);
349
+ if (!skipPerRoute) {
350
+ for (const [hash, routes] of groups) {
351
+ const filename = `subset-${hash}.ttf`;
352
+ const outPath = join(outDir, filename);
353
+ if (!existsSync(outPath)) {
354
+ const route = routeSets.find((r) => r.hash === hash);
355
+ const chars = route?.chars ?? "";
356
+ const uniqueChars = chars
357
+ .split("")
358
+ .filter((ch) => !commonChars.includes(ch))
359
+ .join("");
360
+ if (uniqueChars.length > 0) {
361
+ const glyphs = get_glyph_ids(uniqueChars, "stalux", "normal", 400);
362
+ if (glyphs.length > 0) {
363
+ const result = subset_font_full("stalux", "normal", 400, 0, glyphs);
364
+ writeFileSync(outPath, result.fontBytes);
351
365
  subsetCount++;
352
366
  logger.info(
353
- ` subset ${subsetCount}: ${(data.length / 1024).toFixed(1)} KB → ${filename}`,
367
+ ` subset ${subsetCount}: ${(result.fontBytes.length / 1024).toFixed(1)} KB → ${filename}`,
354
368
  );
355
369
  }
356
370
  }
371
+ }
357
372
 
358
- // Generate per-route CSS (one per unique hash, references all routes with this hash)
359
- if (!cssGenerated.has(hash)) {
360
- cssGenerated.add(hash);
361
- for (const route of routes) {
362
- const cssContent = `/* Auto-generated font subset for route: ${route} */
373
+ // Generate per-route CSS (one per unique hash, references all routes with this hash)
374
+ if (!cssGenerated.has(hash)) {
375
+ cssGenerated.add(hash);
376
+ for (const route of routes) {
377
+ const cssContent = `/* Auto-generated font subset for route: ${route} */
363
378
  @font-face {
364
379
  font-family: "LXGW WenKai Subset";
365
- src: url("/fonts/${filename}") format("woff2");
380
+ src: url("/fonts/${filename}") format("truetype");
366
381
  font-display: swap;
367
382
  }
368
383
  `;