Skip to content

Commit

Permalink
fix: subsets (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
ricardogobbosouza authored Mar 14, 2024
1 parent 14689de commit a72573b
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 381 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
"dev": "vitest",
"lint": "eslint --ext .ts,.js .",
"prepack": "unbuild",
"release": "pnpm test && pnpm build && changelogen --release && git push --follow-tags && pnpm publish",
"release": "pnpm test && pnpm build && changelogen --release --push && pnpm publish",
"test": "pnpm lint && vitest run --coverage"
},
"dependencies": {
"deepmerge": "^4.3.1",
"hookable": "^5.5.3",
"ofetch": "^1.3.3",
"ufo": "^1.3.2"
"ufo": "^1.4.0"
},
"devDependencies": {
"@nuxtjs/eslint-config-typescript": "latest",
Expand All @@ -44,5 +44,5 @@
"unbuild": "latest",
"vitest": "latest"
},
"packageManager": "pnpm@8.11.0"
}
"packageManager": "pnpm@8.15.4"
}
12 changes: 6 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

192 changes: 63 additions & 129 deletions src/construct-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { GOOGLE_FONTS_DOMAIN, isValidDisplay, parseFamilyName, parseStyle } from
import type { GoogleFonts, Families } from './types'

export function constructURL ({ families, display, subsets, text }: GoogleFonts = {}): string | false {
const subset = (Array.isArray(subsets) ? subsets : [subsets]).filter(Boolean)
const prefix = subset.length > 0 ? 'css' : 'css2'
const family = convertFamiliesToArray(families ?? {}, prefix.endsWith('2'))
const _subsets = (Array.isArray(subsets) ? subsets : [subsets]).filter(Boolean)
const family = convertFamiliesToArray(families ?? {})

if (family.length < 1) {
return false
Expand All @@ -19,150 +18,85 @@ export function constructURL ({ families, display, subsets, text }: GoogleFonts
query.display = display
}

if (subset.length > 0) {
query.subset = subset.join(',')
if (_subsets.length > 0) {
query.subset = _subsets.join(',')
}

if (text) {
query.text = text
}

return withHttps(withQuery(resolveURL(GOOGLE_FONTS_DOMAIN, prefix), query))
return withHttps(withQuery(resolveURL(GOOGLE_FONTS_DOMAIN, 'css2'), query))
}

function convertFamiliesToArray (families: Families, v2 = true): string[] {
function convertFamiliesToArray (families: Families): string[] {
const result: string[] = []

// v1
if (!v2) {
Object.entries(families).forEach(([name, values]) => {
if (!name) {
return
}

name = parseFamilyName(name)

if ((Array.isArray(values) && values.length > 0) || (values === true || values === 400)) {
result.push(name)
return
}

if (values === 700) {
result.push(`${name}:bold`)
return
}

if (Object.keys(values).length > 0) {
const styles: string[] = []

Object
.entries(values)
.sort(([styleA], [styleB]) => styleA.localeCompare(styleB))
.forEach(([style, weight]) => {
const styleParsed = parseStyle(style)

if (styleParsed === 'ital' && (weight === 700 || (Array.isArray(weight) && weight.includes(700)))) {
styles.push('bolditalic')

if (Array.isArray(weight) && weight.includes(400)) {
styles.push(styleParsed)
}
} else if (styleParsed === 'wght' && (weight === 700 || (Array.isArray(weight) && weight.includes(700)))) {
styles.push('bold')

if (Array.isArray(weight) && weight.includes(400)) {
styles.push(styleParsed)
Object.entries(families).forEach(([name, values]) => {
if (!name) {
return
}

name = parseFamilyName(name)

if (typeof values === 'string' && String(values).includes('..')) {
result.push(`${name}:wght@${values}`)
return
}

if (Array.isArray(values) && values.length > 0) {
result.push(`${name}:wght@${values.join(';')}`)
return
}

if (Object.keys(values).length > 0) {
const styles: string[] = []
const weights: string[] = []
let forceWght = false

Object
.entries(values)
.sort(([styleA], [styleB]) => styleA.localeCompare(styleB))
.forEach(([style, weight]) => {
const styleParsed = parseStyle(style)
styles.push(styleParsed)

const weightList = Array.isArray(weight) ? weight : [weight]
weightList.forEach((value: string | number) => {
if (Object.keys(values).length === 1 && styleParsed === 'wght') {
weights.push(String(value))
} else {
const index = styleParsed === 'wght' ? 0 : 1

if (
(value.toString() === 'true' || value === 1 || value === 400) &&
Object.entries(values).length === 1 && weightList.length === 1
) {
weights.push(`${index}`)
} else if (value) {
forceWght = true
weights.push(`${index},${value}`)
}
} else if (weight !== false) {
styles.push(styleParsed)
}
})
})

const stylesSortered = styles
.sort(([styleA], [styleB]) => styleA.localeCompare(styleB))
.reverse()
.join(',')

if (stylesSortered === 'wght') {
result.push(name)
return
}

result.push(`${name}:${stylesSortered}`)
}
})

return result.length ? [result.join('|')] : result
}

// v2
if (v2) {
Object.entries(families).forEach(([name, values]) => {
if (!name) {
return
if (!styles.includes('wght') && forceWght) {
styles.push('wght')
}

name = parseFamilyName(name)

if (typeof values === 'string' && String(values).includes('..')) {
result.push(`${name}:wght@${values}`)
return
}
const weightsSortered = weights
.sort(([weightA], [weightB]) => weightA.localeCompare(weightB))
.join(';')

if (Array.isArray(values) && values.length > 0) {
result.push(`${name}:wght@${values.join(';')}`)
return
}
result.push(`${name}:${styles.join(',')}@${weightsSortered}`)
return
}

if (Object.keys(values).length > 0) {
const styles: string[] = []
const weights: string[] = []
let forceWght = false

Object
.entries(values)
.sort(([styleA], [styleB]) => styleA.localeCompare(styleB))
.forEach(([style, weight]) => {
const styleParsed = parseStyle(style)
styles.push(styleParsed)

const weightList = Array.isArray(weight) ? weight : [weight]
weightList.forEach((value: string | number) => {
if (Object.keys(values).length === 1 && styleParsed === 'wght') {
weights.push(String(value))
} else {
const index = styleParsed === 'wght' ? 0 : 1

if (
(value.toString() === 'true' || value === 1 || value === 400) &&
Object.entries(values).length === 1 && weightList.length === 1
) {
weights.push(`${index}`)
} else if (value) {
forceWght = true
weights.push(`${index},${value}`)
}
}
})
})

if (!styles.includes('wght') && forceWght) {
styles.push('wght')
}

const weightsSortered = weights
.sort(([weightA], [weightB]) => weightA.localeCompare(weightB))
.join(';')

result.push(`${name}:${styles.join(',')}@${weightsSortered}`)
return
}

if (values) {
result.push(name)
}
})
}
if (values) {
result.push(name)
}
})

return result
}
32 changes: 24 additions & 8 deletions src/downloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { extname, posix, resolve, dirname } from 'node:path'
import { ofetch } from 'ofetch'
import { Hookable } from 'hookable'
import { isValidURL } from './is-valid-url'
import { FontSubset } from './types'

export interface FontInputOutput {
inputFont: string
Expand Down Expand Up @@ -83,10 +84,13 @@ export class Downloader extends Hookable<DownloaderHooks> {

await this.callHook('download:start')

const { searchParams } = new URL(this.url)
const subsets = searchParams.get('subset') ? searchParams.get('subset')?.split(',') as FontSubset[] : undefined

// download css content
await this.callHook('download-css:before', this.url)
const cssContent = await ofetch(this.url, { headers })
const fontsFromCss = parseFontsFromCss(cssContent, fontsPath)
const _css = await ofetch(this.url, { headers })
const { fonts: fontsFromCss, css: cssContent } = parseFontsFromCss(_css, fontsPath, subsets)
await this.callHook('download-css:done', this.url, cssContent, fontsFromCss)

// download fonts from css
Expand Down Expand Up @@ -165,7 +169,11 @@ export class Downloader extends Hookable<DownloaderHooks> {
}
}

function parseFontsFromCss (content: string, fontsPath: string): FontInputOutput[] {
function parseFontsFromCss (content: string, fontsPath: string, subsets?: FontSubset[]): {
fonts: FontInputOutput[]
css: string
} {
const css: string[] = []
const fonts: FontInputOutput[] = []
const re = {
face: /\s*(?:\/\*\s*(.*?)\s*\*\/)?[^@]*?@font-face\s*{(?:[^}]*?)}\s*/gi,
Expand All @@ -178,23 +186,28 @@ function parseFontsFromCss (content: string, fontsPath: string): FontInputOutput
let match1

while ((match1 = re.face.exec(content)) !== null) {
const [fontface, comment] = match1
const [fontface, subset] = match1
const familyRegExpArray = re.family.exec(fontface)
const family = familyRegExpArray ? familyRegExpArray[1] : ''
const weightRegExpArray = re.weight.exec(fontface)
const weight = weightRegExpArray ? weightRegExpArray[1] : ''

if (subsets && subsets.length && !subsets.includes(subset as FontSubset)) {
continue
}

css.push(fontface)

let match2
while ((match2 = re.url.exec(fontface)) !== null) {
const [forReplace, url] = match2
const ext = extname(url).replace(/^\./, '') || 'woff2'

const newFilename = formatFontFileName('{family}-{weight}-{i}.{ext}', {
comment: comment || '',
family: family.replace(/\s+/g, '_'),
weight: weight.replace(/\s+/g, '_') || '',
ext,
i: String(i++)
i: String(i++),
ext
}).replace(/\.$/, '')

fonts.push({
Expand All @@ -206,7 +219,10 @@ function parseFontsFromCss (content: string, fontsPath: string): FontInputOutput
}
}

return fonts
return {
css: css.join('\n'),
fonts
}
}

function formatFontFileName (template: string, values: { [s: string]: string } | ArrayLike<string>): string {
Expand Down
3 changes: 1 addition & 2 deletions src/parse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { createURL } from 'ufo'
import { isValidURL } from './is-valid-url'
import { isValidDisplay, parseFamilyName, parseStyle } from './utils'
import type { GoogleFonts, Families, FontDisplay, FontSubset } from './types'
Expand All @@ -10,7 +9,7 @@ export function parse (url: string): GoogleFonts {
return result
}

const { searchParams, pathname } = createURL(url)
const { searchParams, pathname } = new URL(url)

if (!searchParams.has('family')) {
return result
Expand Down
Loading

0 comments on commit a72573b

Please sign in to comment.