Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use TTFLoader in useFont for non-JSON files #2095

Merged
merged 6 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .storybook/public/fonts/lemon-round.ttf
Binary file not shown.
14 changes: 12 additions & 2 deletions .storybook/stories/Text3D.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export default {
</Setup>
),
],
args: {
bevelEnabled: true,
bevelSize: 0.05,
},
} satisfies Meta<typeof Text3D>

type Story = StoryObj<typeof Text3D>
Expand All @@ -36,9 +40,15 @@ function Text3DScene(props: React.ComponentProps<typeof Text3D>) {
export const Text3DSt = {
args: {
font: '/fonts/helvetiker_regular.typeface.json',
bevelEnabled: true,
bevelSize: 0.05,
},
render: (args) => <Text3DScene {...args} />,
name: 'Default',
} satisfies Story

export const Text3DTtfSt = {
args: {
font: '/fonts/lemon-round.ttf',
},
render: (args) => <Text3DScene {...args} />,
name: 'TTF',
} satisfies Story
2 changes: 2 additions & 0 deletions docs/abstractions/text3d.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Render 3D text using ThreeJS's `TextGeometry`.

Text3D will suspend while loading the font data. Text3D requires fonts in JSON format generated through [typeface.json](http://gero3.github.io/facetype.js), either as a path to a JSON file or a JSON object. If you face display issues try checking "Reverse font direction" in the typeface tool.

Alternatively, the path can point to a font file of a type supported by [opentype.js](https://github.com/opentypejs/opentype.js) (for example OTF or TTF), in which case the conversion to the JSON format will be done automatically at load time.

```jsx
<Text3D font={fontUrl} {...textOptions}>
Hello world!
Expand Down
12 changes: 10 additions & 2 deletions docs/loaders/use-font.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@ title: useFont
sourcecode: src/core/useFont.tsx
---

Uses THREE.FontLoader to load a font and returns a `THREE.Font` object. It also accepts a JSON object as a parameter. You can use this to preload or share a font across multiple components.
Uses `THREE.FontLoader` to load a font and returns a `THREE.Font` object. It also accepts a JSON object as a parameter. You can use this to preload or share a font across multiple components.

```jsx
const font = useFont('/fonts/helvetiker_regular.typeface.json')
return <Text3D font={font} />
return <Text3D font={font.data} />
```

In order to preload you do this:

```jsx
useFont.preload('/fonts/helvetiker_regular.typeface.json')
```

If the response from the URL is not a JSON, `THREE.TTFLoader` is used to try parsing the response as a standard font file.
However, keep in mind that the on-the-fly conversion to the JSON format will impact the loading time.

```jsx
const font = useFont('/fonts/helvetiker_regular.ttf')
return <Text3D font={font.data} />
```
7 changes: 3 additions & 4 deletions src/core/Text3D.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import * as React from 'react'
import * as THREE from 'three'
import { extend, MeshProps, Node } from '@react-three/fiber'
import { useMemo } from 'react'
import { suspend } from 'suspend-react'
import { mergeVertices, TextGeometry, TextGeometryParameters, FontLoader } from 'three-stdlib'
import { useFont, FontData } from './useFont'
import { mergeVertices, TextGeometry, TextGeometryParameters } from 'three-stdlib'
import { useFont } from './useFont'
import { ForwardRefComponent } from '../helpers/ts-utils'

declare global {
Expand All @@ -16,7 +15,7 @@ declare global {
}

type Text3DProps = {
font: FontData | string
font: Parameters<typeof useFont>[0]
bevelSegments?: number
smooth?: number
} & Omit<TextGeometryParameters, 'font'> &
Expand Down
33 changes: 26 additions & 7 deletions src/core/useFont.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FontLoader } from 'three-stdlib'
import { FontLoader, TTFLoader } from 'three-stdlib'
import { suspend, preload, clear } from 'suspend-react'

export type Glyph = {
Expand All @@ -22,10 +22,7 @@ export type FontData = {
type FontInput = string | FontData

let fontLoader: FontLoader | null = null

async function loadFontData(font: FontInput): Promise<FontData> {
return typeof font === 'string' ? await (await fetch(font)).json() : font
}
let ttfLoader: TTFLoader | null = null

function parseFontData(fontData: FontData) {
if (!fontLoader) {
Expand All @@ -34,9 +31,31 @@ function parseFontData(fontData: FontData) {
return fontLoader.parse(fontData)
}

function parseTtfArrayBuffer(ttfData: ArrayBuffer) {
if (!ttfLoader) {
ttfLoader = new TTFLoader()
}
return ttfLoader.parse(ttfData) as FontData
}

async function loadFontData(font: FontInput) {
if (typeof font === 'string') {
const res = await fetch(font)

if (res.headers.get('Content-Type')?.includes('application/json')) {
return (await res.json()) as FontData
} else {
const arrayBuffer = await res.arrayBuffer()
return parseTtfArrayBuffer(arrayBuffer)
}
} else {
return font
}
}

async function loader(font: FontInput) {
const data = await loadFontData(font)
return parseFontData(data)
const fontData = await loadFontData(font)
return parseFontData(fontData)
}

export function useFont(font: FontInput) {
Expand Down