Skip to content

Commit

Permalink
msdfonts: base64 distributed msdf fonts
Browse files Browse the repository at this point in the history
  • Loading branch information
bbohlender committed Oct 3, 2024
1 parent 3f76da1 commit a1df879
Show file tree
Hide file tree
Showing 19 changed files with 6,618 additions and 17,016 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ jobs:
- name: Set publishing config
run: pnpm config set '//registry.npmjs.org/:_authToken' "${{ secrets.NPM_TOKEN }}"

- name: Deploy msdfonts Package
working-directory: ./packages/msdfonts
run: pnpm publish --access public --no-git-checks --tag ${{ steps.gitversion.outputs.preReleaseLabel == '' && 'latest' || steps.gitversion.outputs.preReleaseLabel }}

- name: Deploy Uikit Vanilla Package
working-directory: ./packages/uikit
run: pnpm publish --access public --no-git-checks --tag ${{ steps.gitversion.outputs.preReleaseLabel == '' && 'latest' || steps.gitversion.outputs.preReleaseLabel }}
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,60 +41,60 @@ jobs:

# Fonts
- name: Make Dist Dir
working-directory: ./packages/fonts
working-directory: ./packages/msdfonts/docker-volume
run: mkdir dist

# Inter
- name: Generate Inter Light
working-directory: ./packages/fonts
working-directory: ./packages/msdfonts/docker-volume
run: |
node download.js "Inter" 300
fontforge -lang=ff -c 'Open($1); SelectAll(); RemoveOverlap(); Generate($2)' font.ttf inter-light.ttf
pnpm msdf-bmfont -f json inter-light.ttf -i charset.txt -m 256,512 -o dist/inter-light -s 48
- name: Generate Inter Normal
working-directory: ./packages/fonts
working-directory: ./packages/msdfonts/docker-volume
run: |
node download.js "Inter" 400
fontforge -lang=ff -c 'Open($1); SelectAll(); RemoveOverlap(); Generate($2)' font.ttf inter-normal.ttf
pnpm msdf-bmfont -f json inter-normal.ttf -i charset.txt -m 256,512 -o dist/inter-normal -s 48
- name: Generate Inter Medium
working-directory: ./packages/fonts
working-directory: ./packages/msdfonts/docker-volume
run: |
node download.js "Inter" 500
fontforge -lang=ff -c 'Open($1); SelectAll(); RemoveOverlap(); Generate($2)' font.ttf inter-medium.ttf
pnpm msdf-bmfont -f json inter-medium.ttf -i charset.txt -m 256,512 -o dist/inter-medium -s 48
- name: Generate Inter Semi Bold
working-directory: ./packages/fonts
working-directory: ./packages/msdfonts/docker-volume
run: |
node download.js "Inter" 600
fontforge -lang=ff -c 'Open($1); SelectAll(); RemoveOverlap(); Generate($2)' font.ttf inter-semi-bold.ttf
pnpm msdf-bmfont -f json inter-semi-bold.ttf -i charset.txt -m 256,512 -o dist/inter-semi-bold -s 48
- name: Generate Inter Bold
working-directory: ./packages/fonts
working-directory: ./packages/msdfonts/docker-volume
run: |
node download.js "Inter" 700
fontforge -lang=ff -c 'Open($1); SelectAll(); RemoveOverlap(); Generate($2)' font.ttf inter-bold.ttf
pnpm msdf-bmfont -f json inter-bold.ttf -i charset.txt -m 256,512 -o dist/inter-bold -s 48
- name: Convert to Webp
working-directory: ./packages/fonts
working-directory: ./packages/msdfonts/docker-volume
run: pnpm sharp --lossless -i dist/*.png -o dist/ -f webp

- name: Replace file png files
working-directory: ./packages/fonts
working-directory: ./packages/msdfonts/docker-volume
run: |
sed -i 's/png/webp/g' dist/*.json
rm dist/*.png
- name: Copy font files
run: |
mkdir -p public/fonts
cp ./packages/fonts/dist/* ./public/fonts
cp ./packages/fonts/LICENSE public/fonts/LICENSE
cp ./packages/msdfonts/docker-volume/dist/* ./public/fonts
cp ./packages/msdfonts/LICENSE public/fonts/LICENSE
- name: Upload Artifact
uses: actions/upload-artifact@v4
Expand Down
6 changes: 0 additions & 6 deletions packages/fonts/package.json

This file was deleted.

File renamed without changes.
29 changes: 29 additions & 0 deletions packages/msdfonts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# msdfonts

base64 msdf fonts distributed as npm packages

# usage with R3/uikit

```jsx
import { inter } from '@pmndrs/msdfonts'

;<FontFamilyProvider inter={inter}>{...children}</FontFamilyProvider>
```

# How to build

## First Step

`cd docker-volume`
`docker build . -t msdfonts`
`docker run -v ./docker-volume:/data/:rw -e GOOGLE_FONTS_API_KEY='<insert-api-key>' msdfonts`

for users on ARM architecture (e.g. Apple M-chips)

`cd docker-volume`
`docker build . --platform linux/x86_64 -t msdfonts`
`docker run -v ./docker-volume:/data/:rw -e GOOGLE_FONTS_API_KEY='<insert-api-key>' --platform linux/x86_64 msdfonts`

## Final Step

Now delete the file in `src/index.ts` and copy the file `docker-volume/index.ts` into `src/index.ts`
6 changes: 6 additions & 0 deletions packages/msdfonts/docker-volume/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.json
!package.json
*.ttf
*.png
*.webp
*.ts
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const https = require('https') // or 'https' for https:// URLs
const http = require('http')
const fs = require('fs')
import https from 'https' // or 'https' for https:// URLs
import http from 'http'
import fs from 'fs'

const [, , fontFamily, variant] = process.argv

Expand Down
89 changes: 89 additions & 0 deletions packages/msdfonts/docker-volume/generate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import https from 'https'
import http from 'http'
import fs, { writeFileSync } from 'fs'
import { exec } from 'child_process'
import { dirname, resolve } from 'path'

async function generate(fontFamily, variant) {
const response = await fetch(
`https://www.googleapis.com/webfonts/v1/webfonts?family=${fontFamily}&key=${process.env.GOOGLE_FONTS_API_KEY}`,
)
const json = await response.json()
if (json.error != null) {
console.error('fetch font families', fontFamily, json.error)
return
}
const result = Object.entries(json.items[0].files).find(
([name]) => name.toLocaleLowerCase() === variant.toLocaleLowerCase(),
)
if (result == null) {
return false
}
await download(result[1], 'font.ttf')
await runCmd("fontforge -lang=ff -c 'Open($1); SelectAll(); RemoveOverlap(); Generate($2)' font.ttf fixed-font.ttf")
await runCmd(`npm run msdf`)
await runCmd(`npm run webp`)
return true
}

const variants = { light: '300', regular: '400', medium: '500', 'semi-bold': '600', bold: '700' }
const fontFamilies = ['Inter']

async function main() {
let result = ''
for (const fontFamily of fontFamilies) {
result += `export const ${fontFamily.toLowerCase()} = {`
for (const [fontWeightName, fontWeightValue] of Object.entries(variants)) {
if (!(await generate(fontFamily, fontWeightValue))) {
continue
}
result += `\t"${fontWeightName}": ${fontToJson('fixed-font.json')},`
}
result += `}\n\n`
}
writeFileSync('./index.ts', result)
}

main().catch(console.error)

function runCmd(cmd) {
return new Promise((resolve, reject) =>
exec(cmd, (error) => {
if (error == null) {
resolve()
return
}
reject(error)
}),
)
}

function download(url, to) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(to)
;(url.startsWith('https') ? https : http)
.get(url, (response) => {
response.pipe(file)
file.on('finish', () => {
file.close()
resolve()
})
})
.on('error', reject)
})
}

function fontToJson(jsonPath) {
const json = JSON.parse(fs.readFileSync(jsonPath))

for (let i = 0; i < json.pages.length; i++) {
const url = resolve(dirname(jsonPath), json.pages[i]).replace('.png', '.webp')
json.pages[i] = toUrl(fs.readFileSync(url), 'image/webp')
}

return JSON.stringify(json)
}

function toUrl(buf, mimeType) {
return `data:${mimeType};base64,${buf.toString('base64')}`
}
11 changes: 11 additions & 0 deletions packages/msdfonts/docker-volume/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type": "module",
"dependencies": {
"msdf-bmfont-xml": "^2.7.0",
"sharp-cli": "4.1"
},
"scripts": {
"msdf": "msdf-bmfont -f json fixed-font.ttf -i charset.txt -m 256,512 -o fixed-font -s 48",
"webp": "sharp --nearLossless -i fixed-font.png -o fixed-font.webp -f webp"
}
}
9 changes: 9 additions & 0 deletions packages/msdfonts/dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM ubuntu:24.10
RUN apt -y update
RUN apt -y install nodejs
RUN apt -y install npm
RUN apt -y install -y fontforge
WORKDIR /data/
CMD npm install && node generate.js


23 changes: 23 additions & 0 deletions packages/msdfonts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@pmndrs/msdfonts",
"version": "0.0.0",
"description": "base64 msdf fonts distributed as npm package",
"files": [
"dist"
],
"keywords": [
"fonts",
"uikit",
"icons",
"threejs",
"r3f",
"msdf"
],
"author": "Bela Bohlender",
"scripts": {
"build": "tsc"
},
"type": "module",
"main": "dist/index.js"
}

2 changes: 2 additions & 0 deletions packages/msdfonts/src/index.ts

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions packages/msdfonts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"declaration": true,
"skipLibCheck": true
},
"include": ["src"]
}
4 changes: 2 additions & 2 deletions packages/react/src/font.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
Font,
FontFamilies,
FontFamilyUrls,
FontFamilyWeightMap,
FontWeight,
GlyphLayoutProperties,
Initializers,
Expand All @@ -19,7 +19,7 @@ import { useContext, createContext, ReactNode, useCallback, useEffect, useMemo }
const FontFamiliesContext = createContext<FontFamilies>(null as any)

export function FontFamilyProvider<T extends string = never>(properties: {
[Key in T]: Key extends 'children' ? ReactNode : FontFamilyUrls
[Key in T]: Key extends 'children' ? ReactNode : FontFamilyWeightMap
}) {
let { children, ...fontFamilies } = properties as any
const existinFontFamilyUrls = useContext(FontFamiliesContext)
Expand Down
3 changes: 2 additions & 1 deletion packages/uikit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"inline-style-parser": "^0.2.3",
"node-html-parser": "^6.1.13",
"tw-to-css": "^0.0.12",
"yoga-layout": "^3.0.4"
"yoga-layout": "^3.0.4",
"@pmndrs/msdfonts": "workspace:^"
},
"devDependencies": {
"@types/node": "^20.11.0"
Expand Down
25 changes: 16 additions & 9 deletions packages/uikit/src/text/cache.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { TextureLoader, WebGLRenderer } from 'three'
import { Font, FontInfo } from './font.js'

const fontCache = new Map<string, Set<(font: Font) => void> | Font>()
const fontCache = new Map<string | FontInfo, Set<(font: Font) => void> | Font>()

const textureLoader = new TextureLoader()

export function loadCachedFont(url: string, renderer: WebGLRenderer, onLoad: (font: Font) => void): void {
let entry = fontCache.get(url)
export function loadCachedFont(
fontInfoOrUrl: string | FontInfo,
renderer: WebGLRenderer,
onLoad: (font: Font) => void,
): void {
let entry = fontCache.get(fontInfoOrUrl)
if (entry instanceof Set) {
entry.add(onLoad)
return
Expand All @@ -18,26 +22,29 @@ export function loadCachedFont(url: string, renderer: WebGLRenderer, onLoad: (fo

const set = new Set<(font: Font) => void>()
set.add(onLoad)
fontCache.set(url, set)
fontCache.set(fontInfoOrUrl, set)

loadFont(url, renderer)
loadFont(fontInfoOrUrl, renderer)
.then((font) => {
for (const fn of set) {
fn(font)
}
fontCache.set(url, font)
fontCache.set(fontInfoOrUrl, font)
})
.catch(console.error)
}

async function loadFont(url: string, renderer: WebGLRenderer): Promise<Font> {
const info: FontInfo = await (await fetch(url)).json()
async function loadFont(fontInfoOrUrl: string | FontInfo, renderer: WebGLRenderer): Promise<Font> {
const info: FontInfo = typeof fontInfoOrUrl === 'object' ? fontInfoOrUrl : await (await fetch(fontInfoOrUrl)).json()

if (info.pages.length !== 1) {
throw new Error('only supporting exactly 1 page')
}

const page = await textureLoader.loadAsync(new URL(info.pages[0], new URL(url, window.location.href)).href)
const page = await textureLoader.loadAsync(
new URL(info.pages[0], typeof fontInfoOrUrl === 'string' ? new URL(fontInfoOrUrl, window.location.href) : undefined)
.href,
)

page.anisotropy = renderer.capabilities.getMaxAnisotropy()
page.flipY = false
Expand Down
Loading

0 comments on commit a1df879

Please sign in to comment.