Skip to content

Commit a560a3a

Browse files
committed
feat: 基于 fonts-index.ts 更新可嵌入字体索引
构建脚本优先读取 fonts-index.ts,合并 FONT_FAMILY_ALIASES 与 EXTRA_SEARCH_ALIASES,同步 Font Awesome 5/6 等新增字体与简写命中。 fix: 背景形状渲染时去除文字高亮避免误带 highlight
1 parent 2d40fef commit a560a3a

5 files changed

Lines changed: 142 additions & 19 deletions

File tree

src/bin/build-embed-font-index.ts

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
/**
2-
* Build embeddable font search index from ../../fonts/fonts-index.json
2+
* Build embeddable font search index from ../../fonts/fonts-index.ts
3+
* (falls back to ../../fonts/fonts-index.json)
34
*
45
* Usage: npx tsx src/bin/build-embed-font-index.ts
56
*/
67
import { existsSync, readFileSync, writeFileSync } from 'fs';
78
import path from 'path';
9+
import { pathToFileURL } from 'url';
810

911
interface FontIndexEntry {
1012
familyName: string;
1113
fullName: string;
1214
postscriptName: string;
1315
subfamilyName: string;
1416
ttfPath: string;
17+
eotPath?: string;
1518
searchKeys: string[];
1619
}
1720

@@ -21,28 +24,90 @@ interface FontIndexFile {
2124
fonts: FontIndexEntry[];
2225
}
2326

27+
interface FontIndexModule {
28+
FONT_INDEX?: FontIndexFile;
29+
FONT_FAMILY_ALIASES?: Readonly<Record<string, string>>;
30+
EXTRA_SEARCH_ALIASES?: Readonly<Record<string, string>>;
31+
}
32+
2433
function toSearchKey(name: string): string {
2534
return name
2635
.normalize('NFKC')
2736
.toLowerCase()
2837
.replace(/[^a-z0-9\u4e00-\u9fff]/g, '');
2938
}
3039

31-
function main(): void {
40+
async function loadFontIndex(
41+
fontsDir: string
42+
): Promise<{
43+
data: FontIndexFile;
44+
familyAliases: Record<string, string>;
45+
extraSearchAliases: Record<string, string>;
46+
sourceLabel: string;
47+
}> {
48+
const tsPath = path.join(fontsDir, 'fonts-index.ts');
49+
const jsonPath = path.join(fontsDir, 'fonts-index.json');
50+
51+
if (existsSync(tsPath)) {
52+
const mod = (await import(pathToFileURL(tsPath).href)) as FontIndexModule;
53+
if (!mod.FONT_INDEX) {
54+
throw new Error(`${tsPath} must export FONT_INDEX`);
55+
}
56+
return {
57+
data: mod.FONT_INDEX,
58+
familyAliases: { ...(mod.FONT_FAMILY_ALIASES ?? {}) },
59+
extraSearchAliases: { ...(mod.EXTRA_SEARCH_ALIASES ?? {}) },
60+
sourceLabel: '../../fonts/fonts-index.ts',
61+
};
62+
}
63+
64+
if (!existsSync(jsonPath)) {
65+
throw new Error(`Missing font index: ${tsPath} or ${jsonPath}`);
66+
}
67+
68+
const raw = readFileSync(jsonPath, 'utf8');
69+
return {
70+
data: JSON.parse(raw) as FontIndexFile,
71+
familyAliases: {},
72+
extraSearchAliases: {},
73+
sourceLabel: '../../fonts/fonts-index.json',
74+
};
75+
}
76+
77+
function addAliasMappings(
78+
searchIndex: Record<string, string>,
79+
familyAliases: Record<string, string>,
80+
extraSearchAliases: Record<string, string>
81+
): void {
82+
for (const [alias, familyName] of Object.entries(familyAliases)) {
83+
const keys = [toSearchKey(alias), alias.trim().toLowerCase()];
84+
for (const key of keys) {
85+
if (!key || key.length < 2) continue;
86+
if (!searchIndex[key]) searchIndex[key] = familyName;
87+
}
88+
}
89+
90+
for (const [aliasKey, familyName] of Object.entries(extraSearchAliases)) {
91+
const key = toSearchKey(aliasKey);
92+
if (!key || key.length < 2) continue;
93+
if (!searchIndex[key]) searchIndex[key] = familyName;
94+
}
95+
}
96+
97+
async function main(): Promise<void> {
3298
const repoRoot = path.resolve(__dirname, '../../../..');
33-
const indexPath = path.join(repoRoot, 'fonts', 'fonts-index.json');
99+
const fontsDir = path.join(repoRoot, 'fonts');
34100
const outPath = path.resolve(__dirname, '../utils/embedFonts.index.ts');
35101

36-
if (!existsSync(indexPath)) {
102+
if (!existsSync(path.join(fontsDir, 'fonts-index.ts')) && !existsSync(path.join(fontsDir, 'fonts-index.json'))) {
37103
if (existsSync(outPath)) {
38-
console.log(`Skip: ${indexPath} not found, keeping existing ${outPath}`);
104+
console.log(`Skip: fonts index not found in ${fontsDir}, keeping existing ${outPath}`);
39105
return;
40106
}
41-
throw new Error(`Missing font index: ${indexPath}`);
107+
throw new Error(`Missing font index in ${fontsDir}`);
42108
}
43109

44-
const raw = readFileSync(indexPath, 'utf8');
45-
const data = JSON.parse(raw) as FontIndexFile;
110+
const { data, familyAliases, extraSearchAliases, sourceLabel } = await loadFontIndex(fontsDir);
46111

47112
const searchIndex: Record<string, string> = {};
48113
const families = new Set<string>();
@@ -62,21 +127,25 @@ function main(): void {
62127
}
63128
}
64129

130+
addAliasMappings(searchIndex, familyAliases, extraSearchAliases);
131+
65132
const sortedKeys = Object.keys(searchIndex).sort();
66133
const lines = sortedKeys.map(
67134
(key) => ` ${JSON.stringify(key)}: ${JSON.stringify(searchIndex[key])},`
68135
);
69136

70137
const content = `/**
71138
* Auto-generated by src/bin/build-embed-font-index.ts
72-
* Source: ../../fonts/fonts-index.json
139+
* Source: ${sourceLabel}
73140
* Do not edit manually.
74141
*/
75142
76143
export const EMBED_FONT_INDEX_META = {
77144
fontCount: ${data.count},
78145
familyCount: ${families.size},
79146
searchKeyCount: ${sortedKeys.length},
147+
familyAliasCount: ${Object.keys(familyAliases).length},
148+
extraSearchAliasCount: ${Object.keys(extraSearchAliases).length},
80149
timestamp: ${JSON.stringify(data.timestamp)},
81150
} as const;
82151
@@ -87,8 +156,13 @@ ${lines.join('\n')}
87156

88157
writeFileSync(outPath, content, 'utf8');
89158
console.log(
90-
`Wrote ${outPath} (${families.size} families, ${sortedKeys.length} search keys)`
159+
`Wrote ${outPath} (${families.size} families, ${sortedKeys.length} search keys, ` +
160+
`${Object.keys(familyAliases).length} family aliases, ` +
161+
`${Object.keys(extraSearchAliases).length} extra search aliases)`
91162
);
92163
}
93164

94-
main();
165+
main().catch((error) => {
166+
console.error(error);
167+
process.exit(1);
168+
});

src/cli/utils/output.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ export function printFontEmbedNotice(
234234
if (matched.length === 0 && unmatched.length === 0) return;
235235

236236
console.error(
237-
`Cloud embed font library (${indexMeta.familyCount} families from fonts-index.json):`
237+
`Cloud embed font library (${indexMeta.familyCount} families from fonts-index.ts):`
238238
);
239239
for (const item of matched) {
240240
const note =

src/conversion-report.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface ConversionElementStats {
2121
export interface ConversionFontStats {
2222
families: string[];
2323
variants: UsedFontDescriptor[];
24-
/** Probe result against the cloud embed font library (../../fonts/fonts-index.json) */
24+
/** Probe result against the cloud embed font library (../../fonts/fonts-index.ts) */
2525
embed?: FontEmbedProbeResult;
2626
}
2727

src/converter.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,23 @@ import {
5454
type PlaceholderMediaType,
5555
} from './utils/placeholder-assets';
5656

57+
/**
58+
* Background is rendered as a separate shape — strip a:highlight from host and per-run options
59+
* (buildScriptSplitTextPieces / rich-text runs may still carry highlight from getTextOptions).
60+
*/
61+
function stripTextHighlights(textOptions: Record<string, unknown>, textContent: unknown): unknown {
62+
delete textOptions.highlight;
63+
if (!Array.isArray(textContent)) return textContent;
64+
return textContent.map((run) => {
65+
if (!run || typeof run !== 'object') return run;
66+
const piece = run as { text?: string; options?: Record<string, unknown> };
67+
if (!piece.options || piece.options.highlight === undefined) return run;
68+
const nextOpts = { ...piece.options };
69+
delete nextOpts.highlight;
70+
return { ...piece, options: nextOpts };
71+
});
72+
}
73+
5774
export class ElementConverter {
5875
private registry: StyleEnhancementRegistry;
5976
private fontResolver?: (d: UsedFontDescriptor) => string;
@@ -897,7 +914,7 @@ export class ElementConverter {
897914
delete textBoxOptions.transparency;
898915
delete textBoxOptions.line;
899916
delete textBoxOptions.shadow;
900-
delete textBoxOptions.highlight;
917+
textContent = stripTextHighlights(textBoxOptions, textContent);
901918

902919
return [
903920
bgShape,
@@ -919,7 +936,7 @@ export class ElementConverter {
919936
fit: 'none',
920937
wrap: false,
921938
};
922-
delete pillOptions.highlight;
939+
textContent = stripTextHighlights(pillOptions, textContent);
923940
if (fillOptions.fill) pillOptions.fill = fillOptions.fill;
924941
if (fillOptions.transparency !== undefined) {
925942
pillOptions.transparency = fillOptions.transparency;
@@ -973,7 +990,7 @@ export class ElementConverter {
973990
delete textBoxOptions.rectRadius;
974991
delete textBoxOptions.shadow;
975992
// Background is the roundRect shape — glyph highlight duplicates fill and breaks sizing
976-
delete textBoxOptions.highlight;
993+
textContent = stripTextHighlights(textBoxOptions, textContent);
977994

978995
// Return both elements: shape first (background), then text (foreground)
979996
const bgShape: any = {

src/utils/embedFonts.index.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
/**
22
* Auto-generated by src/bin/build-embed-font-index.ts
3-
* Source: ../../fonts/fonts-index.json
3+
* Source: ../../fonts/fonts-index.ts
44
* Do not edit manually.
55
*/
66

77
export const EMBED_FONT_INDEX_META = {
88
fontCount: 11298,
9-
familyCount: 7214,
10-
searchKeyCount: 20620,
9+
familyCount: 7221,
10+
searchKeyCount: 20650,
11+
familyAliasCount: 10,
12+
extraSearchAliasCount: 10,
1113
timestamp: "2026-07-08T06:54:22.903Z",
1214
} as const;
1315

@@ -5602,6 +5604,36 @@ export const EMBED_FONT_SEARCH_INDEX: Readonly<Record<string, string>> = {
56025604
"fondamento": "Fondamento",
56035605
"fondamentoitalic": "Fondamento",
56045606
"fondamentoregular": "Fondamento",
5607+
"font awesome 5 brands": "Font Awesome 5 Brands Regular",
5608+
"font awesome 5 free": "Font Awesome 5 Free Solid",
5609+
"font awesome 5 free regular": "Font Awesome 5 Free Regular",
5610+
"font awesome 5 free solid": "Font Awesome 5 Free Solid",
5611+
"font awesome 6 brands": "Font Awesome 6 Brands Regular",
5612+
"font awesome 6 free": "Font Awesome 6 Free Solid",
5613+
"font awesome 6 free regular": "Font Awesome 6 Free Regular",
5614+
"font awesome 6 free solid": "Font Awesome 6 Free Solid",
5615+
"font awesome v4 compatibility": "Font Awesome v4 Compatibility Regular",
5616+
"fontawesome": "Font Awesome v4 Compatibility Regular",
5617+
"fontawesome5brands": "Font Awesome 5 Brands Regular",
5618+
"fontawesome5brandsregular": "Font Awesome 5 Brands Regular",
5619+
"fontawesome5brandsregularregular": "Font Awesome 5 Brands Regular",
5620+
"fontawesome5free": "Font Awesome 5 Free Solid",
5621+
"fontawesome5freeregular": "Font Awesome 5 Free Regular",
5622+
"fontawesome5freeregularregular": "Font Awesome 5 Free Regular",
5623+
"fontawesome5freesolid": "Font Awesome 5 Free Solid",
5624+
"fontawesome5freesolidsolid": "Font Awesome 5 Free Solid",
5625+
"fontawesome6brands": "Font Awesome 6 Brands Regular",
5626+
"fontawesome6brandsregular": "Font Awesome 6 Brands Regular",
5627+
"fontawesome6brandsregularregular": "Font Awesome 6 Brands Regular",
5628+
"fontawesome6free": "Font Awesome 6 Free Solid",
5629+
"fontawesome6freeregular": "Font Awesome 6 Free Regular",
5630+
"fontawesome6freeregularregular": "Font Awesome 6 Free Regular",
5631+
"fontawesome6freesolid": "Font Awesome 6 Free Solid",
5632+
"fontawesome6freesolidsolid": "Font Awesome 6 Free Solid",
5633+
"fontawesomev4": "Font Awesome v4 Compatibility Regular",
5634+
"fontawesomev4compatibility": "Font Awesome v4 Compatibility Regular",
5635+
"fontawesomev4compatibilityregular": "Font Awesome v4 Compatibility Regular",
5636+
"fontawesomev4compatibilityregularregular": "Font Awesome v4 Compatibility Regular",
56055637
"fontdinerswanky": "Fontdiner Swanky",
56065638
"fontdinerswankyregular": "Fontdiner Swanky",
56075639
"forum": "Forum",

0 commit comments

Comments
 (0)