mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 20:21:39 +03:00
82 lines
3.6 KiB
TypeScript
82 lines
3.6 KiB
TypeScript
#!/usr/bin/env bun
|
||
// Lists the monospaced font families installed on this Mac (the post table's isFixedPitch, read with
|
||
// fontkit) and writes their names to src/lib/local-mono-fonts.json, for Settings › Mono font's
|
||
// "Installed on this Mac" group. Only the names are kept; the fonts are never copied. fontkit
|
||
// installs into a temporary folder, so the app takes no dependency on it.
|
||
// Run from packages/console/app: bun script/local-mono-fonts.ts
|
||
import { mkdtemp, readdir, rm } from "fs/promises"
|
||
import { homedir, tmpdir } from "os"
|
||
import { dirname, join } from "path"
|
||
import { fileURLToPath } from "url"
|
||
|
||
const root = join(dirname(fileURLToPath(import.meta.url)), "..")
|
||
const tmp = await mkdtemp(join(tmpdir(), "fontkit-"))
|
||
await Bun.$`bun add fontkit@2.0.4`.cwd(tmp).quiet()
|
||
type Face = {
|
||
familyName?: string
|
||
name?: { records?: { preferredFamily?: Record<string, string> } }
|
||
post?: { isFixedPitch?: number }
|
||
glyphsForString?: (text: string) => ({ id: number; advanceWidth: number } | null)[]
|
||
}
|
||
const fontkit: { openSync: (path: string) => Face & { fonts?: Face[] } } = await import(
|
||
join(tmp, "node_modules/fontkit/dist/main.cjs")
|
||
)
|
||
// The families the site offers on their own, and Apple's hidden UI faces.
|
||
const skipped = ["IBM Plex Mono", "OpenTUI Mono", "Apple Color Emoji", "LastResort", "GB18030 Bitmap"]
|
||
const ascii = Array.from({ length: 95 }, (_, index) => String.fromCharCode(32 + index)).join("")
|
||
const folders = [join(homedir(), "Library/Fonts"), "/Library/Fonts", "/System/Library/Fonts"]
|
||
const files = (
|
||
await Promise.all(
|
||
folders.map((folder) =>
|
||
readdir(folder, { recursive: true })
|
||
.then((names) => names.map((name) => join(folder, name)))
|
||
.catch(() => []),
|
||
),
|
||
)
|
||
)
|
||
.flat()
|
||
.filter((file) => /\.(otf|ttf|ttc|otc|dfont)$/i.test(file))
|
||
const faces = files.flatMap((file) => {
|
||
const font = (() => {
|
||
try {
|
||
return fontkit.openSync(file)
|
||
} catch {
|
||
return undefined
|
||
}
|
||
})()
|
||
if (!font) return []
|
||
return (font.fonts ?? [font]).flatMap((face) => {
|
||
const legacy = face.familyName?.trim()
|
||
if (!legacy) return []
|
||
// The typographic family ("Geist Mono"), not the legacy one per weight ("Geist Mono Light").
|
||
const family = face.name?.records?.preferredFamily?.en?.trim() ?? legacy
|
||
return [{ family, legacy, mono: monospaced(face) }]
|
||
})
|
||
})
|
||
await rm(tmp, { recursive: true, force: true })
|
||
// A mono face in a family with proportional faces (Apercu Pro's Mono) goes by its legacy name, which
|
||
// CSS also matches, so the name never picks the proportional faces.
|
||
const mixed = new Set(faces.filter((face) => !face.mono).map((face) => face.family))
|
||
const list = [
|
||
...new Set(faces.filter((face) => face.mono).map((face) => (mixed.has(face.family) ? face.legacy : face.family))),
|
||
]
|
||
.filter((name) => !name.startsWith(".") && !skipped.includes(name))
|
||
.toSorted((a, b) => a.localeCompare(b))
|
||
await Bun.write(join(root, "src/lib/local-mono-fonts.json"), JSON.stringify(list, null, 2) + "\n")
|
||
console.log(`${list.length} monospaced families\n${list.join("\n")}`)
|
||
|
||
// Some monospaced fonts leave isFixedPitch unset (Monaco, Monaspace): every printable ASCII
|
||
// character then has the same advance. Fonts without Latin (Braille, STIX, Kailasa) map those
|
||
// characters to the missing glyph, so each needs a glyph of its own either way.
|
||
function monospaced(face: Face) {
|
||
const glyphs = (() => {
|
||
try {
|
||
return face.glyphsForString?.(ascii) ?? []
|
||
} catch {
|
||
return []
|
||
}
|
||
})()
|
||
if (glyphs.length !== ascii.length || glyphs.some((glyph) => !glyph || glyph.id === 0)) return false
|
||
return !!face.post?.isFixedPitch || glyphs.every((glyph) => glyph?.advanceWidth === glyphs[0]?.advanceWidth)
|
||
}
|