Skip to content

Commit

Permalink
feat: Use TTFLoader in useFont for non-JSON files (#2095)
Browse files Browse the repository at this point in the history
* Fix useFont example in docs

* Remove unused imports from Text3D

* Use TTFLoader in useFont

* Update documentation for Text3D

* Add story for Text3D using TTFLoader

* ttf font

---------

Co-authored-by: Antoine BERNIER <antoine.bernier@gmail.com>
  • Loading branch information
mz8i and abernier authored Nov 16, 2024
1 parent d008e2b commit 61d3bc5
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 15 deletions.
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

0 comments on commit 61d3bc5

Please sign in to comment.