| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -90,7 +90,7 @@ export default defineFontProvider('google', async (providerOptions: GoogleProvid | |||
| 90 | 90 | const fontAxes = new Map(font.axes.map(axis => [axis.tag, axis])) | |
| 91 | 91 | const weightAxis = fontAxes.get('wght') | |
| 92 | 92 | ||
| 93 | - const weights = dedupeBy(prepareWeights({ | ||
| 93 | + const allWeights = dedupeBy(prepareWeights({ | ||
| 94 | 94 | inputWeights: options.weights, | |
| 95 | 95 | hasVariableWeights: !!weightAxis, | |
| 96 | 96 | weights: Object.keys(font.fonts), | |
@@ -104,6 +104,11 @@ export default defineFontProvider('google', async (providerOptions: GoogleProvid | |||
| 104 | 104 | return { weight: clamped, variable: clamped.includes('..') } | |
| 105 | 105 | }), v => v.weight) | |
| 106 | 106 | ||
| 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 | + | ||
| 107 | 112 | if (weights.length === 0 || styles.length === 0 || !hasRequestedSubset) | |
| 108 | 113 | return [] | |
| 109 | 114 | ||
@@ -119,29 +124,39 @@ export default defineFontProvider('google', async (providerOptions: GoogleProvid | |||
| 119 | 124 | } | |
| 120 | 125 | } | |
| 121 | 126 | ||
| 122 | - const resolvedAxes = [] | ||
| 123 | - let resolvedVariants: string[] = [] | ||
| 124 | 127 | const candidateAxes = [ | |
| 125 | 128 | 'wght', | |
| 126 | 129 | 'ital', | |
| 127 | 130 | ...Object.keys(resolvedVariableAxes), | |
| 128 | 131 | ].sort(googleFlavoredSorting) | |
| 129 | 132 | ||
| 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) | ||
| 141 | 149 | } | |
| 142 | - resolvedAxes.push(axis) | ||
| 150 | + return `${font.family}:${resolvedAxes.join(',')}@${resolvedVariants.join(';')}` | ||
| 143 | 151 | } | |
| 144 | 152 | ||
| 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 | + | ||
| 145 | 160 | let priority = 0 | |
| 146 | 161 | const resolvedFontFaceData: FontFaceData[] = [] | |
| 147 | 162 | ||
@@ -150,31 +165,33 @@ export default defineFontProvider('google', async (providerOptions: GoogleProvid | |||
| 150 | 165 | if (!userAgent) | |
| 151 | 166 | continue | |
| 152 | 167 | ||
| 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 | + } | ||
| 178 | 195 | } | |
| 179 | 196 | priority++ | |
| 180 | 197 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -288,6 +288,51 @@ describe('google', () => { | |||
| 288 | 288 | expect(fonts.length).toEqual(2) | |
| 289 | 289 | }) | |
| 290 | 290 | ||
| 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 | + | ||
| 291 | 336 | it('resolves to no fonts when the family does not publish the requested style', async () => { | |
| 292 | 337 | const { requests, restore } = mockCss2() | |
| 293 | 338 | const unifont = await createUnifont([providers.google()]) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments