FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(google): request variable ranges and static weights separately · unjs/unifont@553ac22 · GitHub

/ unifont Public

Commit 553ac22

Browse files
committed
fix(google): request variable ranges and static weights separately
1 parent 6589bfb commit 553ac22

2 files changed

Lines changed: 102 additions & 40 deletions

File tree

‎src/providers/google.ts‎

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export default defineFontProvider('google', async (providerOptions: GoogleProvid
9090
const fontAxes = new Map(font.axes.map(axis => [axis.tag, axis]))
9191
const weightAxis = fontAxes.get('wght')
9292

93-
const weights = dedupeBy(prepareWeights({
93+
const allWeights = dedupeBy(prepareWeights({
9494
inputWeights: options.weights,
9595
hasVariableWeights: !!weightAxis,
9696
weights: Object.keys(font.fonts),
@@ -104,6 +104,11 @@ export default defineFontProvider('google', async (providerOptions: GoogleProvid
104104
return { weight: clamped, variable: clamped.includes('..') }
105105
}), v => v.weight)
106106

107+
// A variable range already delivers every weight it spans, so requesting those weights as
108+
// separate static files as well would ship the same glyphs twice.
109+
const ranges = allWeights.filter(v => v.variable).map(v => v.weight.split('..').map(Number) as [number, number])
110+
const weights = allWeights.filter(v => v.variable || !ranges.some(([min, max]) => Number(v.weight) >= min && Number(v.weight) <= max))
111+
107112
if (weights.length === 0 || styles.length === 0 || !hasRequestedSubset)
108113
return []
109114

@@ -119,29 +124,39 @@ export default defineFontProvider('google', async (providerOptions: GoogleProvid
119124
}
120125
}
121126

122-
const resolvedAxes = []
123-
let resolvedVariants: string[] = []
124127
const candidateAxes = [
125128
'wght',
126129
'ital',
127130
...Object.keys(resolvedVariableAxes),
128131
].sort(googleFlavoredSorting)
129132

130-
for (const axis of candidateAxes) {
131-
const axisValue = ({
132-
wght: weights.map(v => v.weight),
133-
ital: styles,
134-
})[axis] ?? resolvedVariableAxes[axis]!
135-
136-
if (resolvedVariants.length === 0) {
137-
resolvedVariants = axisValue
138-
}
139-
else {
140-
resolvedVariants = resolvedVariants.flatMap(v => Array.from(axisValue, o => [v, o].join(','))).sort()
133+
function buildRequest(weightValues: string[]) {
134+
const resolvedAxes: string[] = []
135+
let resolvedVariants: string[] = []
136+
for (const axis of candidateAxes) {
137+
const axisValue = ({
138+
wght: weightValues,
139+
ital: styles,
140+
})[axis] ?? resolvedVariableAxes[axis]!
141+
142+
if (resolvedVariants.length === 0) {
143+
resolvedVariants = axisValue
144+
}
145+
else {
146+
resolvedVariants = resolvedVariants.flatMap(v => Array.from(axisValue, o => [v, o].join(','))).sort()
147+
}
148+
resolvedAxes.push(axis)
141149
}
142-
resolvedAxes.push(axis)
150+
return `${font.family}:${resolvedAxes.join(',')}@${resolvedVariants.join(';')}`
143151
}
144152

153+
// `css2` rejects a request that mixes an axis range with discrete values on the same axis,
154+
// so ranges and static weights are requested separately and merged.
155+
const requests = [
156+
weights.filter(v => v.variable),
157+
weights.filter(v => !v.variable),
158+
].filter(group => group.length > 0).map(group => buildRequest(group.map(v => v.weight)))
159+
145160
let priority = 0
146161
const resolvedFontFaceData: FontFaceData[] = []
147162

@@ -150,31 +165,33 @@ export default defineFontProvider('google', async (providerOptions: GoogleProvid
150165
if (!userAgent)
151166
continue
152167

153-
let url = `https://fonts.googleapis.com/css2?family=${font.family}:${resolvedAxes.join(',')}@${resolvedVariants.join(';')}`
154-
if (glyphs) {
155-
url += `&text=${encodeURIComponent(glyphs)}`
156-
}
157-
const rawCss = await ctx.fetch(url, {
158-
headers: {
159-
'user-agent': userAgent,
160-
},
161-
}).then(res => res.text())
162-
const groups = splitCssIntoSubsets(rawCss).filter(group => group.subset ? options.subsets.includes(group.subset) : true)
163-
for (const group of groups) {
164-
const data = extractFontFaceData(group.css)
165-
data.map((f) => {
166-
// avoid accidental pinning to a single width
167-
if (!resolvedVariableAxes.wdth && f.stretch && !f.stretch.includes(' ')) {
168-
delete f.stretch
169-
}
170-
f.meta ??= {}
171-
f.meta.priority = priority
172-
if (group.subset) {
173-
f.meta.subset = group.subset
174-
}
175-
return f
176-
})
177-
resolvedFontFaceData.push(...data)
168+
for (const request of requests) {
169+
let url = `https://fonts.googleapis.com/css2?family=${request}`
170+
if (glyphs) {
171+
url += `&text=${encodeURIComponent(glyphs)}`
172+
}
173+
const rawCss = await ctx.fetch(url, {
174+
headers: {
175+
'user-agent': userAgent,
176+
},
177+
}).then(res => res.text())
178+
const groups = splitCssIntoSubsets(rawCss).filter(group => group.subset ? options.subsets.includes(group.subset) : true)
179+
for (const group of groups) {
180+
const data = extractFontFaceData(group.css)
181+
data.map((f) => {
182+
// avoid accidental pinning to a single width
183+
if (!resolvedVariableAxes.wdth && f.stretch && !f.stretch.includes(' ')) {
184+
delete f.stretch
185+
}
186+
f.meta ??= {}
187+
f.meta.priority = priority
188+
if (group.subset) {
189+
f.meta.subset = group.subset
190+
}
191+
return f
192+
})
193+
resolvedFontFaceData.push(...data)
194+
}
178195
}
179196
priority++
180197
}

‎test/providers/google.test.ts‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,51 @@ describe('google', () => {
288288
expect(fonts.length).toEqual(2)
289289
})
290290

291+
it('resolves the weights reported by getFontProperties', async () => {
292+
const unifont = await createUnifont([providers.google()])
293+
const properties = await unifont.getFontProperties('Newsreader')
294+
const error = vi.spyOn(console, 'error').mockImplementation(() => {})
295+
296+
const { fonts } = await unifont.resolveFont('Newsreader', {
297+
weights: properties!.weights,
298+
styles: properties!.styles,
299+
subsets: properties!.subsets,
300+
})
301+
302+
expect(fonts.length).toBeGreaterThan(0)
303+
expect(error).not.toHaveBeenCalled()
304+
error.mockRestore()
305+
})
306+
307+
it('prefers a variable range over the static weights it covers', async () => {
308+
const { requests, restore } = mockCss2()
309+
const unifont = await createUnifont([providers.google()])
310+
await unifont.resolveFont('Newsreader', {
311+
formats: ['woff2'],
312+
styles: ['normal'],
313+
weights: ['400', '600', '200 800'],
314+
})
315+
316+
expect(requests).toHaveBeenCalledTimes(1)
317+
expect(requests).toHaveBeenCalledWith('https://fonts.googleapis.com/css2?family=Newsreader:ital,wght@0,200..800')
318+
restore()
319+
})
320+
321+
it('requests static weights outside a variable range separately', async () => {
322+
const { requests, restore } = mockCss2()
323+
const unifont = await createUnifont([providers.google()])
324+
await unifont.resolveFont('Newsreader', {
325+
formats: ['woff2'],
326+
styles: ['normal'],
327+
weights: ['200 300', '800'],
328+
})
329+
330+
expect(requests).toHaveBeenCalledTimes(2)
331+
expect(requests).toHaveBeenCalledWith('https://fonts.googleapis.com/css2?family=Newsreader:ital,wght@0,200..300')
332+
expect(requests).toHaveBeenCalledWith('https://fonts.googleapis.com/css2?family=Newsreader:ital,wght@0,800')
333+
restore()
334+
})
335+
291336
it('resolves to no fonts when the family does not publish the requested style', async () => {
292337
const { requests, restore } = mockCss2()
293338
const unifont = await createUnifont([providers.google()])

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL