Skip to content

Commit 5df0ebe

Browse files
authored
fix(variable): handle new numbered subsets on variable as well (#146)
1 parent f6e2882 commit 5df0ebe

File tree

3 files changed

+149
-2
lines changed

3 files changed

+149
-2
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "google-font-metadata",
33
"description": "A metadata generator for Google Fonts.",
4-
"version": "6.0.5",
4+
"version": "6.0.6",
55
"author": "Ayuhito <[email protected]>",
66
"main": "./dist/index.js",
77
"module": "./dist/index.mjs",

src/variable-parser.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,32 @@ export const parseCSS = (cssTuple: string[][], defSubset?: string) => {
220220
}
221221

222222
if (rule.type === '@font-face') {
223+
// For each @font-face rule, we need to determine the actual subset
224+
// This could come from a comment (old format) or URL pattern (new format)
225+
let actualSubset = subset;
226+
227+
// First, look for any URL to extract subset from filename if needed.
228+
for (const subrule of rule.children) {
229+
if (typeof subrule !== 'string' && subrule.props === 'src') {
230+
if (typeof subrule.children === 'string') {
231+
const typeMatch = /(url)\((.+?)\)/g;
232+
const match: string[][] = [
233+
...subrule.children.matchAll(typeMatch),
234+
];
235+
if (match.length > 0) {
236+
const path: string = match[0][2];
237+
actualSubset = extractSubsetFromUrl(
238+
path,
239+
subset,
240+
defSubset ?? 'latin',
241+
);
242+
break;
243+
}
244+
}
245+
}
246+
}
247+
248+
// Then process all properties with the correct subset
223249
for (const subrule of rule.children) {
224250
// Type guard to ensure there are children in font-face rules
225251
if (typeof subrule === 'string')
@@ -246,7 +272,7 @@ export const parseCSS = (cssTuple: string[][], defSubset?: string) => {
246272
const path: string = match[0][2];
247273

248274
if (type === 'url')
249-
fontVariants[fontType][fontStyle][subset] = path;
275+
fontVariants[fontType][fontStyle][actualSubset] = path;
250276
}
251277
}
252278
}
@@ -297,3 +323,24 @@ export const parseVariable = async (noValidate: boolean) => {
297323
} variable font datapoints have been generated.`,
298324
);
299325
};
326+
327+
/**
328+
* Extract subset from URL filename for numbered subsets.
329+
* Falls back to current subset if no numbered pattern is found.
330+
*/
331+
const extractSubsetFromUrl = (
332+
url: string,
333+
currentSubset: string,
334+
defSubset: string,
335+
): string => {
336+
// If current subset is not the default, it was set by a comment, so use it
337+
if (currentSubset !== defSubset) return currentSubset;
338+
339+
// Extract numbered subset from filename pattern like .123.woff2
340+
const match = url.match(/\.(\d+)\.(woff2?|ttf|otf)$/);
341+
if (match) {
342+
return `[${match[1]}]`;
343+
}
344+
345+
return currentSubset;
346+
};

tests/variable-parser.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
addAndMergeAxesRange,
1010
fetchAllCSS,
1111
generateCSSLinks,
12+
parseCSS,
1213
parseVariable,
1314
sortAxes,
1415
} from '../src/variable-parser';
@@ -233,6 +234,105 @@ describe('Variable Parser', () => {
233234
});
234235
});
235236

237+
describe('Parse CSS with numbered subsets', () => {
238+
it('Handles numbered subsets without comments (new format)', () => {
239+
// Mock CSS data that mimics the new Google Fonts format without comments
240+
// but with numbered subsets in the URL filenames
241+
const cssWithoutComments: string[][] = [
242+
[
243+
'wght.normal',
244+
`@font-face {
245+
font-family: 'Noto Sans JP Variable';
246+
font-style: normal;
247+
font-weight: 100 900;
248+
src: url(https://fonts.gstatic.com/s/notosansjp/v55/variable-font.0.woff2) format('woff2');
249+
}
250+
@font-face {
251+
font-family: 'Noto Sans JP Variable';
252+
font-style: normal;
253+
font-weight: 100 900;
254+
src: url(https://fonts.gstatic.com/s/notosansjp/v55/variable-font.1.woff2) format('woff2');
255+
}
256+
@font-face {
257+
font-family: 'Noto Sans JP Variable';
258+
font-style: normal;
259+
font-weight: 100 900;
260+
src: url(https://fonts.gstatic.com/s/notosansjp/v55/variable-font.119.woff2) format('woff2');
261+
}`,
262+
],
263+
];
264+
265+
const result = parseCSS(cssWithoutComments);
266+
267+
// Should extract numbered subsets from URLs: [0], [1], and [119]
268+
expect(result.wght.normal['[0]']).toBe(
269+
'https://fonts.gstatic.com/s/notosansjp/v55/variable-font.0.woff2',
270+
);
271+
expect(result.wght.normal['[1]']).toBe(
272+
'https://fonts.gstatic.com/s/notosansjp/v55/variable-font.1.woff2',
273+
);
274+
expect(result.wght.normal['[119]']).toBe(
275+
'https://fonts.gstatic.com/s/notosansjp/v55/variable-font.119.woff2',
276+
);
277+
});
278+
279+
it('Handles numbered subsets with comments (old format)', () => {
280+
// Mock CSS data with comments (old format)
281+
const cssWithComments: string[][] = [
282+
[
283+
'wght.normal',
284+
`/* [0] */
285+
@font-face {
286+
font-family: 'Noto Sans JP Variable';
287+
font-style: normal;
288+
font-weight: 100 900;
289+
src: url(https://fonts.gstatic.com/s/notosansjp/v55/variable-font.0.woff2) format('woff2');
290+
}
291+
/* [1] */
292+
@font-face {
293+
font-family: 'Noto Sans JP Variable';
294+
font-style: normal;
295+
font-weight: 100 900;
296+
src: url(https://fonts.gstatic.com/s/notosansjp/v55/variable-font.1.woff2) format('woff2');
297+
}`,
298+
],
299+
];
300+
301+
const result = parseCSS(cssWithComments);
302+
303+
// Should use comment-based subset names [0] and [1]
304+
expect(result.wght.normal['[0]']).toBe(
305+
'https://fonts.gstatic.com/s/notosansjp/v55/variable-font.0.woff2',
306+
);
307+
expect(result.wght.normal['[1]']).toBe(
308+
'https://fonts.gstatic.com/s/notosansjp/v55/variable-font.1.woff2',
309+
);
310+
});
311+
312+
it('Falls back to default subset for non-numbered URLs', () => {
313+
// Mock CSS data with regular URLs (no numbered pattern)
314+
const cssRegular: string[][] = [
315+
[
316+
'wght.normal',
317+
`/* latin */
318+
@font-face {
319+
font-family: 'Roboto Flex';
320+
font-style: normal;
321+
font-weight: 100 1000;
322+
src: url(https://fonts.gstatic.com/s/robotoflex/v30/regular.woff2) format('woff2');
323+
}`,
324+
],
325+
];
326+
327+
const result = parseCSS(cssRegular);
328+
329+
// Should use comment-based subset name 'latin'
330+
expect(result.wght.normal.latin).toBe(
331+
'https://fonts.gstatic.com/s/robotoflex/v30/regular.woff2',
332+
);
333+
});
334+
});
335+
236336
describe('Full parse and order', () => {
237337
it('Parses successfully', async () => {
238338
await parseVariable(false);

0 commit comments

Comments
 (0)