@xingwangzhe/stalux 1.5.4 → 1.6.0

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.5.4",
4
- "description": "A powerful, modern Astro blog theme \u2014 use as template or install as plugin",
3
+ "version": "1.6.0",
4
+ "description": "A powerful, modern Astro blog theme — use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
7
7
  "astro-integration",
@@ -83,15 +83,15 @@
83
83
  "@xingwangzhe/satteri-photoswipe": "^0.2.0",
84
84
  "@xingwangzhe/tags-cloud": "^1.2.4",
85
85
  "animejs": "^4.5.0",
86
- "astro": "^7.1.3",
87
86
  "astro-expressive-code": "^0.44.1",
88
87
  "astro-seo": "^1.1.0",
89
88
  "astro-typewriter": "^1.2.0",
90
89
  "badge-maker": "^6.0.0",
91
90
  "pagefind": "^1.5.2",
92
91
  "photoswipe": "^5.4.4",
92
+ "satteri": "^0.9.5",
93
93
  "simple-icons": "^16.27.1",
94
- "taetype": "0.1.3",
94
+ "subset-font": "^2.5.0",
95
95
  "temml": "^0.13.3"
96
96
  },
97
97
  "devDependencies": {
package/src/index.ts CHANGED
@@ -8,10 +8,10 @@
8
8
  * 基于 Astro 7.1.3 Integration API(astro:config:setup / injectRoute / injectScript)
9
9
  */
10
10
 
11
+ import { existsSync, readdirSync } from "node:fs";
11
12
  import path from "node:path";
12
13
  import { resolve } from "node:path";
13
14
  import { fileURLToPath } from "node:url";
14
- import { existsSync, readdirSync } from "node:fs";
15
15
 
16
16
  import type { AstroIntegration } from "astro";
17
17
  // pagefind 是 ESM-only 包,需要在模块顶层导入
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dev mode on-demand font subsetting middleware.
3
3
  *
4
- * Intercepts /fonts/subset-*.css requests, generates the WOFF2 subset
4
+ * Intercepts /fonts/subset-*.css requests, generates the TTF subset
5
5
  * for the requested route on-the-fly, and serves it.
6
6
  *
7
7
  * Handles ALL HTML routes: posts, about, words, home, archives, tags, etc.
@@ -12,7 +12,7 @@ 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 { register_font_raw, get_glyph_ids, subset_font_full } from "taetype";
15
+ import subsetFont from "subset-font";
16
16
 
17
17
  const FONT_INPUT = "src/assets/fonts/LXGWWenKai-Regular.ttf";
18
18
  const CONTENT_ROOT = "stalux";
@@ -98,14 +98,6 @@ async function generateSubsetOnDemand(
98
98
  for (const ch of chars) charSet.add(ch);
99
99
  if (charSet.size === 0) return null;
100
100
 
101
- // Deduplicate against common subset
102
- const outDir = resolve(projectRoot, FONT_OUT_DIR);
103
-
104
- // We can't precisely know which chars common covers without re-extracting.
105
- // But we can read the i18n + config source for a rough estimate.
106
- // For now, generate the full subset - the WOFF2 will be small either way.
107
- // (Most chars are already in common, so the delta is tiny.)
108
-
109
101
  // Locate font file
110
102
  const fontPaths = [
111
103
  resolve(projectRoot, FONT_INPUT),
@@ -122,23 +114,23 @@ async function generateSubsetOnDemand(
122
114
  if (!fontPath) return null;
123
115
 
124
116
  const fontBuffer = readFileSync(fontPath);
117
+ const outDir = resolve(projectRoot, FONT_OUT_DIR);
125
118
  mkdirSync(outDir, { recursive: true });
126
119
 
127
- // Generate subset TTF using taetype
120
+ // Generate subset TTF using subset-font (WASM)
128
121
  const charsStr = [...charSet].sort().join("");
129
- try { register_font_raw("stalux", fontBuffer); } catch { /* already registered */ }
130
- const glyphs = get_glyph_ids(charsStr, "stalux", "normal", 400);
131
- if (glyphs.length === 0) return null;
132
- const subsetResult = subset_font_full("stalux", "normal", 400, 0, glyphs);
133
- const hash = [...charsStr].slice(0, 12).join("");
122
+ const hash = hashStr(charsStr);
134
123
  const filename = `subset-${subsetId}-${hash}.ttf`;
135
124
  const outPath = join(outDir, filename);
136
125
 
137
126
  if (!existsSync(outPath)) {
138
127
  try {
139
- writeFileSync(outPath, subsetResult.fontBytes);
128
+ const result = await subsetFont(fontBuffer, charsStr, {
129
+ targetFormat: "sfnt",
130
+ });
131
+ writeFileSync(outPath, result);
140
132
  logger.info(
141
- ` on-demand subset: ${(subsetResult.fontBytes.length / 1024).toFixed(1)} KB → ${filename}`,
133
+ ` on-demand subset: ${(result.length / 1024).toFixed(1)} KB → ${filename}`,
142
134
  );
143
135
  } catch {
144
136
  return null;
@@ -152,6 +144,17 @@ async function generateSubsetOnDemand(
152
144
  }`;
153
145
  }
154
146
 
147
+ /** Simple hash function for strings */
148
+ function hashStr(s: string): string {
149
+ let hash = 0;
150
+ for (let i = 0; i < s.length && i < 100; i++) {
151
+ const ch = s.charCodeAt(i);
152
+ hash = (hash << 5) - hash + ch;
153
+ hash |= 0;
154
+ }
155
+ return Math.abs(hash).toString(36).slice(0, 8);
156
+ }
157
+
155
158
  /**
156
159
  * Create Vite middleware for on-demand font subsetting in dev mode.
157
160
  */
@@ -2,26 +2,32 @@
2
2
  * Per-route font subsetting engine.
3
3
  *
4
4
  * Scans each content file individually, extracts its unique character set,
5
- * and generates a minimal WOFF2 subset per route. Also generates a "common"
5
+ * and generates a minimal TTF subset per route. Also generates a "common"
6
6
  * subset for UI text (navs, i18n, layout).
7
7
  *
8
8
  * Output layout (under public/fonts/):
9
9
  * fonts/
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
- * manifest.json — route → subset file mapping
15
- * subset.css — global @font-face declarations
10
+ * common-{hash}.ttf — UI text subset (ASCII, nav, i18n)
11
+ * subset-{hash}.ttf — per-route subset (shared by routes with same chars)
12
+ * common.css — @font-face for common subset
13
+ * subset-{route}.css — per-route @font-face
14
+ * manifest.json — route → subset file mapping
16
15
  */
17
16
 
18
17
  import { createHash } from "node:crypto";
19
- import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from "node:fs";
18
+ import {
19
+ existsSync,
20
+ mkdirSync,
21
+ readFileSync,
22
+ writeFileSync,
23
+ readdirSync,
24
+ unlinkSync,
25
+ } from "node:fs";
20
26
  import { resolve, basename, extname, join, dirname } from "node:path";
21
27
  import { fileURLToPath } from "node:url";
22
28
 
23
29
  import type { AstroIntegrationLogger } from "astro";
24
- import { register_font_raw, get_glyph_ids, subset_font_full } from "taetype";
30
+ import subsetFont from "subset-font";
25
31
 
26
32
  // ---------------------------------------------------------------------------
27
33
  // Configuration
@@ -277,20 +283,16 @@ export async function runFontSubsetting(
277
283
  }
278
284
  const fontBuffer = readFileSync(fontPath);
279
285
 
280
- // Register font with taetype for fast Rust subsetting
281
- try {
282
- register_font_raw("stalux", fontBuffer);
283
- } catch (e) {
284
- logger.warn(`Font registration failed: ${e}, skipping subsetting`);
285
- return;
286
- }
287
-
288
286
  // Clean old generated font files to avoid stale cached references
289
287
  const outDir = resolve(projectRoot, FONT_OUT_DIR);
290
288
  if (existsSync(outDir)) {
291
289
  for (const f of readdirSync(outDir)) {
292
290
  if (f.startsWith("common-") || f.startsWith("subset-")) {
293
- try { unlinkSync(join(outDir, f)); } catch { /* skip */ }
291
+ try {
292
+ unlinkSync(join(outDir, f));
293
+ } catch {
294
+ /* skip */
295
+ }
294
296
  }
295
297
  }
296
298
  }
@@ -320,25 +322,32 @@ export async function runFontSubsetting(
320
322
  });
321
323
  }
322
324
 
323
- // 5. Deduplicate and generate subsets using taetype (Rust native, fast)
325
+ // 5. Deduplicate and generate subsets using subset-font (WASM)
324
326
  mkdirSync(outDir, { recursive: true });
325
327
 
326
- // Helper: subset using taetype
327
- function doSubset(chars: string): Uint8Array {
328
- const glyphs = get_glyph_ids(chars, "stalux", "normal", 400);
329
- if (glyphs.length === 0) return new Uint8Array(0);
330
- const result = subset_font_full("stalux", "normal", 400, 0, glyphs);
331
- return result.fontBytes;
328
+ // Helper: subset using subset-font (async WASM)
329
+ async function doSubset(chars: string): Promise<Uint8Array> {
330
+ if (!chars) return new Uint8Array(0);
331
+ try {
332
+ const result = await subsetFont(fontBuffer, chars, {
333
+ targetFormat: "sfnt", // "sfnt" = TTF output
334
+ });
335
+ return result;
336
+ } catch {
337
+ return new Uint8Array(0);
338
+ }
332
339
  }
333
340
 
334
341
  // Generate common subset
335
342
  const commonFilename = `common-${commonHash}.ttf`;
336
343
  const commonOutPath = join(outDir, commonFilename);
337
344
  if (!existsSync(commonOutPath)) {
338
- const data = doSubset(commonChars);
345
+ const data = await doSubset(commonChars);
339
346
  if (data.length > 0) {
340
347
  writeFileSync(commonOutPath, data);
341
- logger.info(` common subset: ${(data.length / 1024).toFixed(1)} KB → ${commonFilename}`);
348
+ logger.info(
349
+ ` common subset: ${(data.length / 1024).toFixed(1)} KB → ${commonFilename}`,
350
+ );
342
351
  }
343
352
  }
344
353
 
@@ -370,13 +379,12 @@ export async function runFontSubsetting(
370
379
  .filter((ch) => !commonChars.includes(ch))
371
380
  .join("");
372
381
  if (uniqueChars.length > 0) {
373
- const glyphs = get_glyph_ids(uniqueChars, "stalux", "normal", 400);
374
- if (glyphs.length > 0) {
375
- const result = subset_font_full("stalux", "normal", 400, 0, glyphs);
376
- writeFileSync(outPath, result.fontBytes);
382
+ const data = await doSubset(uniqueChars);
383
+ if (data.length > 0) {
384
+ writeFileSync(outPath, data);
377
385
  subsetCount++;
378
386
  logger.info(
379
- ` subset ${subsetCount}: ${(result.fontBytes.length / 1024).toFixed(1)} KB → ${filename}`,
387
+ ` subset ${subsetCount}: ${(data.length / 1024).toFixed(1)} KB → ${filename}`,
380
388
  );
381
389
  }
382
390
  }