Skip to content

Commit 5a50580

Browse files
miga-heygenclaude
andcommitted
refactor: extract helpers to reduce complexity and duplication
Split extractGoogleFontsText into addCaseClosure, addFullwidthVariants, and addFullSizeKanaVariants. Extract subsetTextFor test helper to eliminate repeated URL→text boilerplate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 92d9af2 commit 5a50580

2 files changed

Lines changed: 78 additions & 61 deletions

File tree

packages/producer/src/services/deterministicFonts-textSubset.test.ts

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,52 +15,54 @@ async function requestedGoogleFontUrl(html: string): Promise<URL> {
1515
return new URL(requestedUrl);
1616
}
1717

18+
async function subsetTextFor(html: string): Promise<string> {
19+
const url = await requestedGoogleFontUrl(html);
20+
return url.searchParams.get("text") ?? "";
21+
}
22+
1823
describe("Google Fonts text subsetting", () => {
1924
it("sends the composition character set to the CSS API", async () => {
20-
const url = await requestedGoogleFontUrl(
25+
const text = await subsetTextFor(
2126
`<!doctype html><html><head><style>
2227
h1 { font-family: "Noto Performance Test", sans-serif; }
2328
</style></head><body><h1>旅行ランキング</h1></body></html>`,
2429
);
2530

26-
const text = url.searchParams.get("text") ?? "";
2731
for (const character of new Set("旅行ランキング")) {
2832
expect(text).toContain(character);
2933
}
3034
});
3135

3236
it("includes decoded HTML entities from visible composition text", async () => {
33-
const url = await requestedGoogleFontUrl(
37+
const text = await subsetTextFor(
3438
`<!doctype html><html><head><style>
3539
h1 { font-family: "Noto Performance Test", sans-serif; }
3640
</style></head><body><h1>&#x65C5;&#34892;</h1></body></html>`,
3741
);
3842

39-
expect(url.searchParams.get("text")).toContain("旅行");
43+
expect(text).toContain("旅行");
4044
});
4145

4246
it("includes case variants for transformed supplemental alias weights", async () => {
43-
const url = await requestedGoogleFontUrl(
47+
const text = await subsetTextFor(
4448
`<!doctype html><html><head><style>
4549
h1 { font-family: "Inter", sans-serif; font-weight: 800; text-transform: uppercase; }
4650
</style></head><body><h1>Your Kidney Transplant:<br/>What Happens Next</h1></body></html>`,
4751
);
4852

49-
const text = url.searchParams.get("text") ?? "";
5053
expect(encodeURIComponent(text).length).toBeLessThan(700);
5154
for (const character of new Set("YOUR KIDNEY TRANSPLANT:WHAT HAPPENS NEXT")) {
5255
expect(text).toContain(character);
5356
}
5457
});
5558

5659
it("covers capitalized words through the same case closure", async () => {
57-
const url = await requestedGoogleFontUrl(
60+
const text = await subsetTextFor(
5861
`<!doctype html><html><head><style>
5962
h1 { font-family: "Inter", sans-serif; font-weight: 800; text-transform: capitalize; }
6063
</style></head><body><h1>hello world</h1></body></html>`,
6164
);
6265

63-
const text = url.searchParams.get("text") ?? "";
6466
expect(text).toContain("H");
6567
expect(text).toContain("W");
6668
});
@@ -83,115 +85,107 @@ describe("Google Fonts text subsetting", () => {
8385
});
8486

8587
it("includes Turkish İ and ı when lang=tr is present", async () => {
86-
const url = await requestedGoogleFontUrl(
88+
const text = await subsetTextFor(
8789
`<!doctype html><html lang="tr"><head><style>
8890
h1 { font-family: "Inter", sans-serif; text-transform: uppercase; }
8991
</style></head><body><h1>istanbul</h1></body></html>`,
9092
);
9193

92-
const text = url.searchParams.get("text") ?? "";
9394
expect(text).toContain("İ");
9495
expect(text).toContain("ı");
9596
});
9697

9798
it("does not include Turkish İ/ı without a Turkish lang attribute", async () => {
98-
const url = await requestedGoogleFontUrl(
99+
const text = await subsetTextFor(
99100
`<!doctype html><html lang="en"><head><style>
100101
h1 { font-family: "Inter", sans-serif; text-transform: uppercase; }
101102
</style></head><body><h1>istanbul</h1></body></html>`,
102103
);
103104

104-
const text = url.searchParams.get("text") ?? "";
105105
expect(text).not.toContain("İ");
106106
expect(text).not.toContain("ı");
107107
});
108108

109109
it("includes Azeri locale variants when lang=az is present", async () => {
110-
const url = await requestedGoogleFontUrl(
110+
const text = await subsetTextFor(
111111
`<!doctype html><html lang="az"><head><style>
112112
p { font-family: "Inter", sans-serif; }
113113
</style></head><body><p>iyi</p></body></html>`,
114114
);
115115

116-
const text = url.searchParams.get("text") ?? "";
117116
expect(text).toContain("İ");
118117
expect(text).toContain("ı");
119118
});
120119

121120
it("maps ASCII to fullwidth equivalents when full-width appears in the source", async () => {
122-
const url = await requestedGoogleFontUrl(
121+
const text = await subsetTextFor(
123122
`<!doctype html><html><head><style>
124123
p { font-family: "Noto Performance Test", sans-serif; text-transform: full-width; }
125124
</style></head><body><p>ABC</p></body></html>`,
126125
);
127126

128-
const text = url.searchParams.get("text") ?? "";
129127
expect(text).toContain("A");
130128
expect(text).toContain("B");
131129
expect(text).toContain("C");
132130
});
133131

134132
it("does not add fullwidth variants without full-width in the source", async () => {
135-
const url = await requestedGoogleFontUrl(
133+
const text = await subsetTextFor(
136134
`<!doctype html><html><head><style>
137135
p { font-family: "Noto Performance Test", sans-serif; }
138136
</style></head><body><p>ABC</p></body></html>`,
139137
);
140138

141-
const text = url.searchParams.get("text") ?? "";
142139
expect(text).not.toContain("A");
143140
});
144141

145142
it("maps small kana to full-size equivalents when full-size-kana appears in the source", async () => {
146-
const url = await requestedGoogleFontUrl(
143+
const text = await subsetTextFor(
147144
`<!doctype html><html><head><style>
148145
p { font-family: "Noto Performance Test", sans-serif; text-transform: full-size-kana; }
149146
</style></head><body><p>ぁっょ</p></body></html>`,
150147
);
151148

152-
const text = url.searchParams.get("text") ?? "";
153149
expect(text).toContain("あ");
154150
expect(text).toContain("つ");
155151
expect(text).toContain("よ");
156152
});
157153

158154
it("maps small katakana to full-size when full-size-kana appears in the source", async () => {
159-
const url = await requestedGoogleFontUrl(
155+
const text = await subsetTextFor(
160156
`<!doctype html><html><head><style>
161157
p { font-family: "Noto Performance Test", sans-serif; text-transform: full-size-kana; }
162158
</style></head><body><p>ァヵ</p></body></html>`,
163159
);
164160

165-
const text = url.searchParams.get("text") ?? "";
166161
expect(text).toContain("ア");
167162
expect(text).toContain("カ");
168163
});
169164

170165
it("stays within the URL budget for a realistic mixed-script composition with all transforms", async () => {
171-
const latin = "The Quick Brown Fox Jumps Over The Lazy Dog — Your Kidney Transplant: What Happens Next";
166+
const latin =
167+
"The Quick Brown Fox Jumps Over The Lazy Dog — Your Kidney Transplant: What Happens Next";
172168
const cjk = "旅行ランキング東京大阪京都名古屋福岡";
173169
const kana = "ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶ";
174170

175-
const url = await requestedGoogleFontUrl(
171+
const text = await subsetTextFor(
176172
`<!doctype html><html lang="tr"><head><style>
177173
h1 { font-family: "Noto Performance Test", sans-serif; text-transform: full-width; }
178174
p { font-family: "Noto Performance Test", sans-serif; text-transform: full-size-kana; }
179175
</style></head><body><h1>${latin}</h1><p>${cjk}${kana}</p></body></html>`,
180176
);
181177

182-
const text = url.searchParams.get("text") ?? "";
183178
expect(text.length).toBeGreaterThan(0);
184179
expect(encodeURIComponent(text).length).toBeLessThanOrEqual(1700);
185180
});
186181

187182
it("collects lang from nested elements, not just the root", async () => {
188-
const url = await requestedGoogleFontUrl(
183+
const text = await subsetTextFor(
189184
`<!doctype html><html lang="en"><head><style>
190185
p { font-family: "Inter", sans-serif; }
191186
</style></head><body><p>hello</p><p lang="tr">istanbul</p></body></html>`,
192187
);
193188

194-
const text = url.searchParams.get("text") ?? "";
195189
expect(text).toContain("İ");
196190
expect(text).toContain("ı");
197191
});

packages/producer/src/services/deterministicFonts.ts

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,14 +1199,33 @@ export interface InjectDeterministicFontFacesOptions {
11991199
const GOOGLE_FONTS_TEXT_MAX_ENCODED_LENGTH = 1_700;
12001200

12011201
const SMALL_TO_FULL_KANA: ReadonlyMap<string, string> = new Map([
1202-
["ぁ", "あ"], ["ぃ", "い"], ["ぅ", "う"], ["ぇ", "え"], ["ぉ", "お"],
1203-
["っ", "つ"], ["ゃ", "や"], ["ゅ", "ゆ"], ["ょ", "よ"], ["ゎ", "わ"],
1204-
["ァ", "ア"], ["ィ", "イ"], ["ゥ", "ウ"], ["ェ", "エ"], ["ォ", "オ"],
1205-
["ッ", "ツ"], ["ャ", "ヤ"], ["ュ", "ユ"], ["ョ", "ヨ"], ["ヮ", "ワ"],
1206-
["ヵ", "カ"], ["ヶ", "ケ"],
1202+
["ぁ", "あ"],
1203+
["ぃ", "い"],
1204+
["ぅ", "う"],
1205+
["ぇ", "え"],
1206+
["ぉ", "お"],
1207+
["っ", "つ"],
1208+
["ゃ", "や"],
1209+
["ゅ", "ゆ"],
1210+
["ょ", "よ"],
1211+
["ゎ", "わ"],
1212+
["ァ", "ア"],
1213+
["ィ", "イ"],
1214+
["ゥ", "ウ"],
1215+
["ェ", "エ"],
1216+
["ォ", "オ"],
1217+
["ッ", "ツ"],
1218+
["ャ", "ヤ"],
1219+
["ュ", "ユ"],
1220+
["ョ", "ヨ"],
1221+
["ヮ", "ワ"],
1222+
["ヵ", "カ"],
1223+
["ヶ", "ケ"],
12071224
]);
12081225

1209-
function collectLangAttributes(document: { querySelectorAll(selector: string): Iterable<{ getAttribute(name: string): string | null }> }): Set<string> {
1226+
function collectLangAttributes(document: {
1227+
querySelectorAll(selector: string): Iterable<{ getAttribute(name: string): string | null }>;
1228+
}): Set<string> {
12101229
const locales = new Set<string>();
12111230
for (const element of document.querySelectorAll("[lang]")) {
12121231
const lang = element.getAttribute("lang");
@@ -1217,42 +1236,46 @@ function collectLangAttributes(document: { querySelectorAll(selector: string): I
12171236
return locales;
12181237
}
12191238

1239+
function addCaseClosure(out: Set<string>, character: string, locales: ReadonlySet<string>): void {
1240+
out.add(character);
1241+
for (const variant of `${character.toUpperCase()}${character.toLowerCase()}`) {
1242+
out.add(variant);
1243+
}
1244+
for (const locale of locales) {
1245+
for (const variant of `${character.toLocaleUpperCase(locale)}${character.toLocaleLowerCase(locale)}`) {
1246+
out.add(variant);
1247+
}
1248+
}
1249+
}
1250+
1251+
function addFullwidthVariants(chars: Set<string>): void {
1252+
for (const character of [...chars]) {
1253+
const code = character.codePointAt(0) ?? 0;
1254+
if (code >= 0x0021 && code <= 0x007e) {
1255+
chars.add(String.fromCodePoint(code + 0xfee0));
1256+
}
1257+
}
1258+
}
1259+
1260+
function addFullSizeKanaVariants(chars: Set<string>): void {
1261+
for (const character of [...chars]) {
1262+
const full = SMALL_TO_FULL_KANA.get(character);
1263+
if (full) chars.add(full);
1264+
}
1265+
}
1266+
12201267
function extractGoogleFontsText(html: string): string | undefined {
12211268
const { document } = parseHTML(html);
12221269
const decodedBodyText = document.body?.textContent ?? "";
12231270
const locales = collectLangAttributes(document);
1224-
const hasFullWidth = html.includes("full-width");
1225-
const hasFullSizeKana = html.includes("full-size-kana");
12261271

1227-
const characters = [...Array.from(html), ...Array.from(decodedBodyText)];
12281272
const uniqueCharacters = new Set<string>();
1229-
for (const character of characters) {
1230-
uniqueCharacters.add(character);
1231-
for (const variant of `${character.toUpperCase()}${character.toLowerCase()}`) {
1232-
uniqueCharacters.add(variant);
1233-
}
1234-
for (const locale of locales) {
1235-
for (const variant of `${character.toLocaleUpperCase(locale)}${character.toLocaleLowerCase(locale)}`) {
1236-
uniqueCharacters.add(variant);
1237-
}
1238-
}
1239-
}
1240-
1241-
if (hasFullWidth) {
1242-
for (const character of [...uniqueCharacters]) {
1243-
const code = character.codePointAt(0) ?? 0;
1244-
if (code >= 0x0021 && code <= 0x007e) {
1245-
uniqueCharacters.add(String.fromCodePoint(code + 0xfee0));
1246-
}
1247-
}
1273+
for (const character of [...Array.from(html), ...Array.from(decodedBodyText)]) {
1274+
addCaseClosure(uniqueCharacters, character, locales);
12481275
}
12491276

1250-
if (hasFullSizeKana) {
1251-
for (const character of [...uniqueCharacters]) {
1252-
const full = SMALL_TO_FULL_KANA.get(character);
1253-
if (full) uniqueCharacters.add(full);
1254-
}
1255-
}
1277+
if (html.includes("full-width")) addFullwidthVariants(uniqueCharacters);
1278+
if (html.includes("full-size-kana")) addFullSizeKanaVariants(uniqueCharacters);
12561279

12571280
const fontText = [...uniqueCharacters].join("");
12581281
return encodeURIComponent(fontText).length <= GOOGLE_FONTS_TEXT_MAX_ENCODED_LENGTH

0 commit comments

Comments
 (0)