Skip to content

Commit

Permalink
feat(google): allow configuring variable axes (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
qwerzl authored Oct 17, 2024
1 parent 88c62c2 commit 59bce04
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 3 deletions.
50 changes: 47 additions & 3 deletions src/providers/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ import { extractFontFaceData } from '../css/parse'
import { $fetch } from '../fetch'
import { defineFontProvider } from '../utils'

export default defineFontProvider('google', async (_options, ctx) => {
type VariableAxis = 'opsz' | 'slnt' | 'wdth' | (string & {})

interface ProviderOption {
experimental?: {
/**
* Experimental: Setting variable axis configuration on a per-font basis.
*/
variableAxis?: {
[key: string]: Partial<Record<VariableAxis, ([string, string] | string)[] >>
}
}
}

export default defineFontProvider<ProviderOption>('google', async (_options = {}, ctx) => {
const googleFonts = await ctx.storage.getItem('google:meta.json', () => $fetch<{ familyMetadataList: FontIndexMeta[] }>('https://fonts.google.com/metadata/fonts', { responseType: 'json' }).then(r => r.familyMetadataList))

const styleMap = {
Expand Down Expand Up @@ -34,7 +47,23 @@ export default defineFontProvider('google', async (_options, ctx) => {
if (weights.length === 0 || styles.length === 0)
return []

const resolvedVariants = weights.flatMap(w => [...styles].map(s => `${s},${w}`)).sort()
const resolvedAxes = []
let resolvedVariants: string[] = []

for (const axis of ['wght', 'ital', ...Object.keys(_options?.experimental?.variableAxis?.[family] ?? {})].sort(googleFlavoredSorting)) {
const axisValue = ({
wght: weights,
ital: styles,
})[axis] ?? _options!.experimental!.variableAxis![family]![axis]!.map(v => Array.isArray(v) ? `${v[0]}..${v[1]}` : v)

if (resolvedVariants.length === 0) {
resolvedVariants = axisValue
}
else {
resolvedVariants = resolvedVariants.flatMap(v => [...axisValue].map(o => [v, o].join(','))).sort()
}
resolvedAxes.push(axis)
}

let css = ''

Expand All @@ -43,7 +72,7 @@ export default defineFontProvider('google', async (_options, ctx) => {
baseURL: 'https://fonts.googleapis.com',
headers: { 'user-agent': userAgents[extension as keyof typeof userAgents] },
query: {
family: `${family}:` + `ital,wght@${resolvedVariants.join(';')}`,
family: `${family}:${resolvedAxes.join(',')}@${resolvedVariants.join(';')}`,
},
})
}
Expand All @@ -64,6 +93,8 @@ export default defineFontProvider('google', async (_options, ctx) => {
}
})

/** internal */

interface FontIndexMeta {
family: string
subsets: string[]
Expand All @@ -80,3 +111,16 @@ interface FontIndexMeta {
defaultValue: number
}>
}

// Google wants lowercase letters to be in front of uppercase letters.
function googleFlavoredSorting(a: string, b: string) {
const isALowercase = a.charAt(0) === a.charAt(0).toLowerCase()
const isBLowercase = b.charAt(0) === b.charAt(0).toLowerCase()

if (isALowercase !== isBLowercase) {
return Number(isBLowercase) - Number(isALowercase)
}
else {
return a.localeCompare(b)
}
}
29 changes: 29 additions & 0 deletions test/providers/google.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,33 @@ describe('google', () => {
expect(resolvedStyles).toMatchObject(styles)
expect(resolvedWeights).toMatchObject(weights)
})

it('supports variable axes', async () => {
const unifont = await createUnifont([providers.google({
experimental: {
variableAxis: {
Recursive: {
slnt: [['-15', '0']],
CASL: [['0', '1']],
CRSV: ['1'],
MONO: [['0', '1']],
},
},
},
})])

const { fonts } = await unifont.resolveFont('Recursive')

const resolvedStyles = pickUniqueBy(fonts, fnt => fnt.style)
const resolvedWeights = pickUniqueBy(fonts, fnt => String(fnt.weight))

const styles = ['oblique 0deg 15deg', 'normal'] as ResolveFontOptions['styles']

// Variable wght and separate weights from 300 to 1000
const weights = ['300,1000', ...([...Array.from({ length: 7 }).keys()].map(i => String(i * 100 + 300)))]

expect(fonts).toHaveLength(11)
expect(resolvedStyles).toMatchObject(styles)
expect(resolvedWeights).toMatchObject(weights)
})
})

0 comments on commit 59bce04

Please sign in to comment.