From 995f2eb7202789a83671878209c65d240082ade7 Mon Sep 17 00:00:00 2001 From: Jason Johnston Date: Mon, 22 Jun 2020 16:37:38 -0600 Subject: [PATCH] feat(troika-three-text): promote standalone text to a new `troika-three-text` package Closes #47. This creates a new package, `troika-three-text`, which contains everything that used to go into the `textmesh-standalone.js` build target from `troika-3d-text`, and doesn't have any dependencies on the Troika framework packages. Users can now just install and import directly from the `troika-three-text` package. Also renamed `TextMesh` to just `Text`. This is a breaking change for users that were using the secondary standalone dist file, but non-breaking otherwise. --- packages/troika-3d-text/README.md | 237 ++--------------- packages/troika-3d-text/package.json | 13 +- .../troika-3d-text/rollup.build-entries.js | 11 - .../src/facade/SelectionManagerFacade.js | 2 +- .../troika-3d-text/src/facade/Text3DFacade.js | 4 +- .../src/index-textmesh-standalone.js | 11 - packages/troika-3d-text/src/index.js | 16 +- packages/troika-three-text/README.md | 244 ++++++++++++++++++ .../build-typr.js | 3 + .../find-google-font-url.js | 0 .../libs/opentype.factory.js | 0 .../libs/typr.factory.js | 0 .../libs/woff2otf.factory.js | 0 packages/troika-three-text/package.json | 29 +++ .../rollup.config.build-opentype.js | 0 .../screenshot1.png | Bin .../screenshot2.png | Bin .../screenshot3.png | Bin .../screenshot4.png | Bin .../src}/GlyphsGeometry.js | 0 .../src/Text.js} | 12 +- .../src/TextBuilder.js | 21 +- .../src}/TextDerivedMaterial.js | 0 packages/troika-three-text/src/index.js | 7 + .../src/selectionUtils.js | 0 .../src/woff2otf.js | 0 .../src/worker}/FontParser_OpenType.js | 2 +- .../src/worker}/FontParser_Typr.js | 4 +- .../src/worker}/FontProcessor.js | 0 .../src/worker}/GlyphSegmentsQuadtree.js | 0 .../src/worker}/SDFGenerator.js | 0 31 files changed, 332 insertions(+), 284 deletions(-) delete mode 100644 packages/troika-3d-text/rollup.build-entries.js delete mode 100644 packages/troika-3d-text/src/index-textmesh-standalone.js create mode 100644 packages/troika-three-text/README.md rename packages/{troika-3d-text => troika-three-text}/build-typr.js (88%) rename packages/{troika-3d-text => troika-three-text}/find-google-font-url.js (100%) rename packages/{troika-3d-text => troika-three-text}/libs/opentype.factory.js (100%) rename packages/{troika-3d-text => troika-three-text}/libs/typr.factory.js (100%) rename packages/{troika-3d-text => troika-three-text}/libs/woff2otf.factory.js (100%) create mode 100644 packages/troika-three-text/package.json rename packages/{troika-3d-text => troika-three-text}/rollup.config.build-opentype.js (100%) rename packages/{troika-3d-text => troika-three-text}/screenshot1.png (100%) rename packages/{troika-3d-text => troika-three-text}/screenshot2.png (100%) rename packages/{troika-3d-text => troika-three-text}/screenshot3.png (100%) rename packages/{troika-3d-text => troika-three-text}/screenshot4.png (100%) rename packages/{troika-3d-text/src/three => troika-three-text/src}/GlyphsGeometry.js (100%) rename packages/{troika-3d-text/src/three/TextMesh.js => troika-three-text/src/Text.js} (98%) rename packages/{troika-3d-text => troika-three-text}/src/TextBuilder.js (93%) rename packages/{troika-3d-text/src/three => troika-three-text/src}/TextDerivedMaterial.js (100%) create mode 100644 packages/troika-three-text/src/index.js rename packages/{troika-3d-text => troika-three-text}/src/selectionUtils.js (100%) rename packages/{troika-3d-text => troika-three-text}/src/woff2otf.js (100%) rename packages/{troika-3d-text/src => troika-three-text/src/worker}/FontParser_OpenType.js (97%) rename packages/{troika-3d-text/src => troika-three-text/src/worker}/FontParser_Typr.js (97%) rename packages/{troika-3d-text/src => troika-three-text/src/worker}/FontProcessor.js (100%) rename packages/{troika-3d-text/src => troika-three-text/src/worker}/GlyphSegmentsQuadtree.js (100%) rename packages/{troika-3d-text/src => troika-three-text/src/worker}/SDFGenerator.js (100%) diff --git a/packages/troika-3d-text/README.md b/packages/troika-3d-text/README.md index 3ca6db38..18db472d 100644 --- a/packages/troika-3d-text/README.md +++ b/packages/troika-3d-text/README.md @@ -1,25 +1,22 @@ # `troika-3d-text` -This package provides high quality text rendering in 3D scenes, using signed distance fields (SDF) and antialiasing using standard derivatives. +> **PLEASE NOTE:** The standalone `TextMesh` for Three.js, which used to be accessible from this package in a custom `dist/textmesh-standalone.umd.js` file, has been promoted to its own package, [troika-three-text](../troika-three-text). You can now just import `Text` from that package directly: +> +> ```js +> import { Text } from 'troika-three-text' +> +> let myText = new Text() +> ``` -Rather than relying on pre-generated SDF textures, this parses font files (.ttf, .otf, .woff) directly using [Typr.js](https://github.com/photopea/Typr.js), and generates the SDF atlas for glyphs on-the-fly as they are used. It also handles proper kerning and ligature glyph substitution. All font parsing, SDF generation, and glyph layout is performed in a web worker to prevent frame drops. +------ + +This package provides high quality text rendering in the Troika scene management framework, using signed distance fields (SDF) and antialiasing using standard derivatives. It is based on [troika-three-text](../troika-three-text). -Once the SDFs are generated, it assembles a geometry that positions all the glyphs, and _patches_ any Three.js Material with the proper shader code for rendering the SDFs. This means you can still benefit from all the features of Three.js's built-in materials like lighting, physically-based rendering, shadows, and fog. ## Demo Here's [an online demo](https://troika-examples.netlify.com/#text). -## Screenshots - -![Text Rendering](./screenshot1.png) - -![Zoomed-in](./screenshot2.png) - -![Font with ligatures](./screenshot3.png) - -![Text with a texture](./screenshot4.png) - ## Installation Get it from [NPM](https://www.npmjs.com/package/troika-3d-text): @@ -32,15 +29,13 @@ You will also need to install a compatible version of [Three.js](https://threejs ## Usage -### Using within the Troika 3D scene management framework - -This library is built first and foremost to work within Troika 3D scenes, via the `Text3DFacade` class. +Import the `Text3DFacade` class: ```js -import {Text3DFacade} from 'troika-3d-text' +import { Text3DFacade } from 'troika-3d-text' ``` -...then within your scene descriptor: +...then use it within your scene descriptor to configure it: ```js { @@ -54,208 +49,6 @@ import {Text3DFacade} from 'troika-3d-text' } ```` -### Using standalone with Three.js - -The `Text3DFacade` mentioned above is just a wrapper around a Three.js `TextMesh` object, which you can also use directly outside of the Troika framework. A special build file is provided in the distribution that includes only the necessary dependencies: - -```js -import {TextMesh} from 'troika-3d-text/dist/textmesh-standalone.esm.js' -```` - -You can then use the `TextMesh` class like any other Three.js mesh: - -```js -const textMesh = new TextMesh() -myScene.add(textMesh) - -// set properties to configure: -textMesh.text = 'Hello world!' -textMesh.fontSize = 0.2 -textMesh.position.z = -2 -textMesh.color = 0x9966FF - -// be sure to call sync() after all properties are set to update the rendering: -textMesh.sync() -``` - -When you're done with the `TextMesh` instance, be sure to call `dispose` on it to prevent a memory leak: - -```js -myScene.remove(textMesh) -textMesh.dispose() -``` - -### Using in other frameworks - -Some examples of using the standalone TextMesh within other frameworks: - -* [With react-three-fiber](https://codesandbox.io/embed/troika-3d-text-via-react-three-fiber-ntfx2?fontsize=14) -* [As an A-Frame component](https://github.com/lojjic/aframe-troika-text) - -## Supported properties - -Both `Text3DFacade` and `TextMesh` support the following properties for controlling the text rendering: - -#### `text` - -The string of text to be rendered. Newlines and repeating whitespace characters are honored. - -Default: _none_ - -#### `anchor` - -This property is deprecated as of version 0.24.0; use `anchorX` and `anchorY` instead. - -#### `anchorX` - -Defines the horizontal position in the text block that should line up with the local origin. Can be specified as a numeric `x` position in local units, a string percentage of the total text block width e.g. `'25%'`, or one of the following keyword strings: `'left'`, `'center'`, or `'right'`. - -Default: `0` - -#### `anchorY` - -Defines the vertical position in the text block that should line up with the local origin. Can be specified as a numeric `y` position in local units (note: down is negative y), a string percentage of the total text block height e.g. `'25%'`, or one of the following keyword strings: `'top'`, `'top-baseline'`, `'middle'`, `'bottom-baseline'`, or `'bottom'`. - -Default: `0` - -#### `clipRect` - -If specified, defines the `[minX, minY, maxX, maxY]` of a rectangle outside of which all pixels will be discarded. This can be used for example to clip overflowing text when `whiteSpace='nowrap'`. - -Default: _none_ - -#### `color` - -This is a shortcut for setting the `color` of the text's `material`. You can use this if you don't want to specify a whole custom `material` and just want to change its color. - -Use the `material` property if you want to control aspects of the material other than its color. - -Default: _none_ - uses the color of the `material` - -#### `depthOffset` - -This is a shortcut for setting the material's [`polygonOffset` and related properties](https://threejs.org/docs/#api/en/materials/Material.polygonOffset), which can be useful in preventing z-fighting when this text is laid on top of another plane in the scene. Positive numbers are further from the camera, negatives closer. - -Be aware that while this can help with z-fighting, it does not affect the rendering order; if the text renders before the content behind it, you may see antialiasing pixels that appear too dark or light. You may need to also change the text mesh's `renderOrder`, or set its `z` position a fraction closer to the camera, to ensure the text renders after background objects. - -Default: `0` - -#### `font` - -The URL of a custom font file to be used. Supported font formats are: -* .ttf -* .otf -* .woff (.woff2 is _not_ supported) - -Default: The *Roboto* font loaded from Google Fonts CDN - -#### `fontSize` - -The em-height at which to render the font, in local world units. - -Default: `0.1` - -#### `glyphGeometryDetail` - -The number of vertical/horizontal segments that make up each glyph's rectangular plane. This can be increased to provide more geometrical detail for custom vertex shader effects, for example. - -Default: `1` - -#### `letterSpacing` - -Sets a uniform adjustment to spacing between letters after kerning is applied, in local world units. Positive numbers increase spacing and negative numbers decrease it. - -Default: `0` - -#### `lineHeight` - -Sets the height of each line of text. Can either be `'normal'` which chooses a reasonable height based on the chosen font's ascender/descender metrics, or a number that is interpreted as a multiple of the `fontSize`. - -Default: `'normal'` - -#### `material` - -Defines a Three.js Material _instance_ to be used as a base when rendering the text. This material will be automatically replaced with a new material derived from it, that adds shader code to decrease the alpha for each fragment (pixel) outside the text glyphs, with antialiasing. - -By default it will derive from a simple white `MeshBasicMaterial, but you can use any of the other mesh materials to gain other features like lighting, texture maps, etc. - -Also see the `color` shortcut property. - -Default: a `MeshBasicMaterial` instance - -#### `maxWidth` - -The maximum width of the text block, above which text may start wrapping according to the `whiteSpace` and `overflowWrap` properties. - -Default: `Infinity`, meaning text will never wrap - -#### `overflowWrap` - -Defines how text wraps if the `whiteSpace` property is `'normal'`. Can be either `'normal'` to break at whitespace characters, or `'break-word'` to allow breaking within words. - -Default: `'normal'` - -#### `sdfGlyphSize` - -Allows overriding the default size of each glyph's SDF (signed distance field) used when rendering this text instance. This must be a power-of-two number. Larger sizes can improve the quality of glyph rendering by increasing the sharpness of corners and preventing loss of very thin lines, at the expense of increased memory footprint and longer SDF generation time. - -Default: `64` - -#### `textAlign` - -The horizontal alignment of each line of text within the overall text bounding box. Can be one of `'left'`, `'right'`, `'center'`, or `'justify'`. - -Default: `'left'` - -#### `whiteSpace` - -Defines whether text should wrap when a line reaches the `maxWidth`. Can be either `'normal'`, to allow wrapping according to the `overflowWrap` property, or `'nowrap'` to prevent wrapping. - -Note that `'normal'` in this context _does_ honor newline characters to manually break lines, making it behave more like `'pre-wrap'` does in CSS. - -Default: `'normal'` - - -## Preloading - -To avoid long pauses when first displaying a piece of text in your scene, you can preload fonts and optionally pre-generate the SDF textures for particular glyphs up front: - -```js -import {preloadFont} from 'troika-3d-text' - -myApp.showLoadingScreen() - -preloadFont( - 'path/to/myfontfile.woff', - 'abcdefghijklmnopqrstuvwxyz', - () => { - myApp.showScene() - } -) -``` - -The arguments are: - -- `font` - The URL of the font file to preload. If `null` is passed, this will preload the default font. - -- `charSequences` - A string or array of string character sequences for which to pre-generate glyph SDF textures. Note that this _will_ honor ligature substitution, so you may need to specify ligature sequences in addition to their individual characters to get all possible glyphs, e.g. `["t", "h", "th"]` to get the "t" and "h" glyphs plus the "th" glyph. - -- `callback` - A function that will be called when the preloading is complete. - - -## Carets and Selection Ranges - -In addition to rendering text, it is possible to access positioning information for caret placement and selection ranges. To access that info, use the `getCaretAtPoint` and `getSelectionRects` utility functions. Both of these functions take a `textRenderInfo` object as input, which you can get from the `TextMesh` object either in the `sync()` callback or from its `textRenderInfo` property after sync has completed. - -#### `getCaretAtPoint(textRenderInfo, x, y)` - -This returns the caret position nearest to a given x/y position in the local text plane. This is useful for placing an editing caret based on a click or ther raycasted event. The return value is an object with the following properties: - -- `x` - x position of the caret -- `y` - y position of the caret's bottom -- `height` - height of the caret, based on the current fontSize and lineHeight -- `charIndex` - the index in the original input string of this caret's target character. The caret will be for the position _before_ that character. For the final caret position, this will be equal to the string length. For ligature glyphs, this will be for the first character in the ligature sequence. - -#### `getSelectionRects(textRenderInfo, start, end)` +### Supported properties -This returns a list of rectangles covering all the characters within a given character range. This is useful for highlighting a selection range. The return value is an array of objects, each with `{left, top, right, bottom}` properties in the local text plane. +`Text3DFacade` supports all properties supported by the `Text` mesh from [troika-three-text](../troika-3d-text); see the documentation there for details. diff --git a/packages/troika-3d-text/package.json b/packages/troika-3d-text/package.json index 76826d6b..dd89f883 100644 --- a/packages/troika-3d-text/package.json +++ b/packages/troika-3d-text/package.json @@ -1,7 +1,7 @@ { "name": "troika-3d-text", "version": "0.28.1", - "description": "Troika 3D Text", + "description": "SDF text for the Troika 3D scene management framework", "author": "Jason Johnston ", "repository": { "type": "git", @@ -16,15 +16,6 @@ "module:src": "src/index.js", "dependencies": { "troika-3d": "^0.28.1", - "troika-three-utils": "^0.28.1", - "troika-worker-utils": "^0.28.0" - }, - "devDependencies": { - "node-fetch": "^2.6.0", - "opentype.js": "^1.0.1" - }, - "scripts": { - "build-opentype": "rollup -c rollup.config.build-opentype.js", - "build-typr": "node build-typr.js" + "troika-three-text": "^0.28.1" } } diff --git a/packages/troika-3d-text/rollup.build-entries.js b/packages/troika-3d-text/rollup.build-entries.js deleted file mode 100644 index 6dc1d2a7..00000000 --- a/packages/troika-3d-text/rollup.build-entries.js +++ /dev/null @@ -1,11 +0,0 @@ -// Define custom Rollup build entry points for this package... - -module.exports = { - // Default package: - "src/index.js": "troika-3d-text", - - // Secondary entry point for just the TextMesh class with its much smaller - // dependency tree; this can then be used directly by Three.js projects without - // pulling in unneeded Troika framework code: - "src/index-textmesh-standalone.js": "textmesh-standalone" -} \ No newline at end of file diff --git a/packages/troika-3d-text/src/facade/SelectionManagerFacade.js b/packages/troika-3d-text/src/facade/SelectionManagerFacade.js index 1dab9276..6f72e978 100644 --- a/packages/troika-3d-text/src/facade/SelectionManagerFacade.js +++ b/packages/troika-3d-text/src/facade/SelectionManagerFacade.js @@ -1,6 +1,6 @@ import { ListFacade } from 'troika-3d' import { Matrix4, Plane, Vector3 } from 'three' -import { getCaretAtPoint, getSelectionRects } from '../selectionUtils.js' +import { getCaretAtPoint, getSelectionRects } from 'troika-three-text' import SelectionRangeRect from './SelectionRangeRect.js' const THICKNESS = 0.25 //rect depth as percentage of height diff --git a/packages/troika-3d-text/src/facade/Text3DFacade.js b/packages/troika-3d-text/src/facade/Text3DFacade.js index ddc3652a..594eefdc 100644 --- a/packages/troika-3d-text/src/facade/Text3DFacade.js +++ b/packages/troika-3d-text/src/facade/Text3DFacade.js @@ -1,5 +1,5 @@ import { Object3DFacade } from 'troika-3d' -import { TextMesh } from '../three/TextMesh.js' +import { Text } from 'troika-three-text' import SelectionManagerFacade from './SelectionManagerFacade.js' // Properties that will simply be forwarded to the TextMesh: @@ -33,7 +33,7 @@ const TEXT_MESH_PROPS = [ */ class Text3DFacade extends Object3DFacade { constructor(parent) { - const mesh = new TextMesh() + const mesh = new Text() mesh.geometry.boundingSphere.version = 0 super(parent, mesh) diff --git a/packages/troika-3d-text/src/index-textmesh-standalone.js b/packages/troika-3d-text/src/index-textmesh-standalone.js deleted file mode 100644 index 41228c8b..00000000 --- a/packages/troika-3d-text/src/index-textmesh-standalone.js +++ /dev/null @@ -1,11 +0,0 @@ -// Exports for standalone TextMesh-only build: - -export { - configureTextBuilder, - fontProcessorWorkerModule, - preloadFont -} from './TextBuilder.js' - -export {TextMesh} from './three/TextMesh.js' -export {GlyphsGeometry} from './three/GlyphsGeometry.js' -export {getCaretAtPoint, getSelectionRects} from './selectionUtils.js' diff --git a/packages/troika-3d-text/src/index.js b/packages/troika-3d-text/src/index.js index 4690aa71..f9484bc6 100644 --- a/packages/troika-3d-text/src/index.js +++ b/packages/troika-3d-text/src/index.js @@ -1,13 +1,13 @@ -// 3D text exports - +// Proxy exports from troika-three-text for convenience: export { configureTextBuilder, fontProcessorWorkerModule, - preloadFont -} from './TextBuilder.js' - -export {TextMesh} from './three/TextMesh.js' -export {GlyphsGeometry} from './three/GlyphsGeometry.js' -export {getCaretAtPoint, getSelectionRects} from './selectionUtils.js' + preloadFont, + Text as TextMesh, + GlyphsGeometry, + getCaretAtPoint, + getSelectionRects +} from 'troika-three-text' +// Troika framework specific exports: export {default as Text3DFacade} from './facade/Text3DFacade.js' diff --git a/packages/troika-three-text/README.md b/packages/troika-three-text/README.md new file mode 100644 index 00000000..172720b5 --- /dev/null +++ b/packages/troika-three-text/README.md @@ -0,0 +1,244 @@ +# `troika-three-text` + +This package provides high quality text rendering in [Three.js](https://threejs.org) scenes, using signed distance fields (SDF) and antialiasing using standard derivatives. + +Rather than relying on pre-generated SDF textures, this parses font files (.ttf, .otf, .woff) directly using [Typr.js](https://github.com/photopea/Typr.js), and generates the SDF atlas for glyphs on-the-fly as they are used. It also handles proper kerning and ligature glyph substitution. All font parsing, SDF generation, and glyph layout is performed in a web worker to prevent frame drops. + +Once the SDFs are generated, it assembles a geometry that positions all the glyphs, and _patches_ any Three.js Material with the proper shader code for rendering the SDFs. This means you can still benefit from all the features of Three.js's built-in materials like lighting, physically-based rendering, shadows, and fog. + +## Demos + +* [With the Troika scene management framework](https://troika-examples.netlify.com/#text) +* [With react-three-fiber](https://codesandbox.io/embed/troika-3d-text-via-react-three-fiber-ntfx2?fontsize=14) + +## With Other Frameworks + +* [In the `drei` utilities for react-three-fiber](https://github.com/react-spring/drei#%EF%B8%8F-text-) +* [As an A-Frame component](https://github.com/lojjic/aframe-troika-text) + + +## Screenshots + +![Text Rendering](./screenshot1.png) + +![Zoomed-in](./screenshot2.png) + +![Font with ligatures](./screenshot3.png) + +![Text with a texture](./screenshot4.png) + +## Installation + +Get it from [NPM](https://www.npmjs.com/package/troika-three-text): + +```sh +npm install troika-three-text +``` + +You will also need to install a compatible version of [Three.js](https://threejs.org); see the notes in the [Troika 3D Readme](../troika-3d/README.md#installation) for details. + +## Usage + +```js +import {Text} from 'troika-three-text' +```` + +You can then use the `Text` class like any other Three.js mesh: + +```js +// Create: +const myText = new Text() +myScene.add(myText) + +// Set properties to configure: +myText.text = 'Hello world!' +myText.fontSize = 0.2 +myText.position.z = -2 +myText.color = 0x9966FF + +// Update the rendering: +myText.sync() +``` + +It's a good idea to call the `.sync()` method after changing any properties that would affect the text's layout. If you don't, it will be called automatically on the next render frame, but calling it yourself can get the result sooner. + +When you're done with the `Text` instance, be sure to call `dispose` on it to prevent a memory leak: + +```js +myScene.remove(myText) +myText.dispose() +``` + +## Supported properties + +Instances of `Text` support the following configuration properties: + +#### `text` + +The string of text to be rendered. Newlines and repeating whitespace characters are honored. + +Default: _none_ + +#### `anchor` + +This property is deprecated as of version 0.24.0; use `anchorX` and `anchorY` instead. + +#### `anchorX` + +Defines the horizontal position in the text block that should line up with the local origin. Can be specified as a numeric `x` position in local units, a string percentage of the total text block width e.g. `'25%'`, or one of the following keyword strings: `'left'`, `'center'`, or `'right'`. + +Default: `0` + +#### `anchorY` + +Defines the vertical position in the text block that should line up with the local origin. Can be specified as a numeric `y` position in local units (note: down is negative y), a string percentage of the total text block height e.g. `'25%'`, or one of the following keyword strings: `'top'`, `'top-baseline'`, `'middle'`, `'bottom-baseline'`, or `'bottom'`. + +Default: `0` + +#### `clipRect` + +If specified, defines the `[minX, minY, maxX, maxY]` of a rectangle outside of which all pixels will be discarded. This can be used for example to clip overflowing text when `whiteSpace='nowrap'`. + +Default: _none_ + +#### `color` + +This is a shortcut for setting the `color` of the text's `material`. You can use this if you don't want to specify a whole custom `material` and just want to change its color. + +Use the `material` property if you want to control aspects of the material other than its color. + +Default: _none_ - uses the color of the `material` + +#### `depthOffset` + +This is a shortcut for setting the material's [`polygonOffset` and related properties](https://threejs.org/docs/#api/en/materials/Material.polygonOffset), which can be useful in preventing z-fighting when this text is laid on top of another plane in the scene. Positive numbers are further from the camera, negatives closer. + +Be aware that while this can help with z-fighting, it does not affect the rendering order; if the text renders before the content behind it, you may see antialiasing pixels that appear too dark or light. You may need to also change the text mesh's `renderOrder`, or set its `z` position a fraction closer to the camera, to ensure the text renders after background objects. + +Default: `0` + +#### `font` + +The URL of a custom font file to be used. Supported font formats are: +* .ttf +* .otf +* .woff (.woff2 is _not_ supported) + +Default: The *Roboto* font loaded from Google Fonts CDN + +#### `fontSize` + +The em-height at which to render the font, in local world units. + +Default: `0.1` + +#### `glyphGeometryDetail` + +The number of vertical/horizontal segments that make up each glyph's rectangular plane. This can be increased to provide more geometrical detail for custom vertex shader effects, for example. + +Default: `1` + +#### `letterSpacing` + +Sets a uniform adjustment to spacing between letters after kerning is applied, in local world units. Positive numbers increase spacing and negative numbers decrease it. + +Default: `0` + +#### `lineHeight` + +Sets the height of each line of text. Can either be `'normal'` which chooses a reasonable height based on the chosen font's ascender/descender metrics, or a number that is interpreted as a multiple of the `fontSize`. + +Default: `'normal'` + +#### `material` + +Defines a Three.js Material _instance_ to be used as a base when rendering the text. This material will be automatically replaced with a new material derived from it, that adds shader code to decrease the alpha for each fragment (pixel) outside the text glyphs, with antialiasing. + +By default it will derive from a simple white `MeshBasicMaterial, but you can use any of the other mesh materials to gain other features like lighting, texture maps, etc. + +Also see the `color` shortcut property. + +Default: a `MeshBasicMaterial` instance + +#### `maxWidth` + +The maximum width of the text block, above which text may start wrapping according to the `whiteSpace` and `overflowWrap` properties. + +Default: `Infinity`, meaning text will never wrap + +#### `overflowWrap` + +Defines how text wraps if the `whiteSpace` property is `'normal'`. Can be either `'normal'` to break at whitespace characters, or `'break-word'` to allow breaking within words. + +Default: `'normal'` + +#### `sdfGlyphSize` + +Allows overriding the default size of each glyph's SDF (signed distance field) used when rendering this text instance. This must be a power-of-two number. Larger sizes can improve the quality of glyph rendering by increasing the sharpness of corners and preventing loss of very thin lines, at the expense of increased memory footprint and longer SDF generation time. + +Default: `64` + +#### `textAlign` + +The horizontal alignment of each line of text within the overall text bounding box. Can be one of `'left'`, `'right'`, `'center'`, or `'justify'`. + +Default: `'left'` + +#### `whiteSpace` + +Defines whether text should wrap when a line reaches the `maxWidth`. Can be either `'normal'`, to allow wrapping according to the `overflowWrap` property, or `'nowrap'` to prevent wrapping. + +Note that `'normal'` in this context _does_ honor newline characters to manually break lines, making it behave more like `'pre-wrap'` does in CSS. + +Default: `'normal'` + + +## Preloading + +To avoid long pauses when first displaying a piece of text in your scene, you can preload fonts and optionally pre-generate the SDF textures for particular glyphs up front: + +```js +import {preloadFont} from 'troika-three-text' + +myApp.showLoadingScreen() + +preloadFont( + { + font: 'path/to/myfontfile.woff', + characters: 'abcdefghijklmnopqrstuvwxyz' + }, + () => { + myApp.showScene() + } +) +``` + +The arguments are: + +- `options` + + - `options.font` - The URL of the font file to preload. If `null` is passed, this will preload the default font. + + - `options.characters` - A string or array of string character sequences for which to pre-generate glyph SDF textures. Note that this _will_ honor ligature substitution, so you may need to specify ligature sequences in addition to their individual characters to get all possible glyphs, e.g. `["t", "h", "th"]` to get the "t" and "h" glyphs plus the "th" glyph. + + - `options.sdfGlyphSize` - The size at which to prerender the SDFs for the `characters` glyphs. See the `sdfGlyphSize` config property on `Text` for details about SDF sizes. If not specified, will use the default SDF size. + +- `callback` - A function that will be called when the preloading is complete. + + +## Carets and Selection Ranges + +In addition to rendering text, it is possible to access positioning information for caret placement and selection ranges. To access that info, use the `getCaretAtPoint` and `getSelectionRects` utility functions. Both of these functions take a `textRenderInfo` object as input, which you can get from the `Text` object either in the `sync()` callback or from its `textRenderInfo` property after sync has completed. + +#### `getCaretAtPoint(textRenderInfo, x, y)` + +This returns the caret position nearest to a given x/y position in the local text plane. This is useful for placing an editing caret based on a click or ther raycasted event. The return value is an object with the following properties: + +- `x` - x position of the caret +- `y` - y position of the caret's bottom +- `height` - height of the caret, based on the current fontSize and lineHeight +- `charIndex` - the index in the original input string of this caret's target character. The caret will be for the position _before_ that character. For the final caret position, this will be equal to the string length. For ligature glyphs, this will be for the first character in the ligature sequence. + +#### `getSelectionRects(textRenderInfo, start, end)` + +This returns a list of rectangles covering all the characters within a given character range. This is useful for highlighting a selection range. The return value is an array of objects, each with `{left, top, right, bottom}` properties in the local text plane. diff --git a/packages/troika-3d-text/build-typr.js b/packages/troika-three-text/build-typr.js similarity index 88% rename from packages/troika-3d-text/build-typr.js rename to packages/troika-three-text/build-typr.js index bc4706d2..2bff1f73 100644 --- a/packages/troika-3d-text/build-typr.js +++ b/packages/troika-three-text/build-typr.js @@ -16,6 +16,9 @@ async function fetchText(url) { } async function buildTypr() { + // NOTE: probably want to lock this to the last version of Typr before they stripped out + // built-in support for some things like ligatures in favor of a much larger Harfbuzz wasm + // plugin. We may need to fork Typr going forward if we need to merge future fixes. const typr = await fetchText('https://raw.githubusercontent.com/photopea/Typr.js/gh-pages/src/Typr.js') let typrU = await fetchText('https://raw.githubusercontent.com/photopea/Typr.js/gh-pages/src/Typr.U.js') diff --git a/packages/troika-3d-text/find-google-font-url.js b/packages/troika-three-text/find-google-font-url.js similarity index 100% rename from packages/troika-3d-text/find-google-font-url.js rename to packages/troika-three-text/find-google-font-url.js diff --git a/packages/troika-3d-text/libs/opentype.factory.js b/packages/troika-three-text/libs/opentype.factory.js similarity index 100% rename from packages/troika-3d-text/libs/opentype.factory.js rename to packages/troika-three-text/libs/opentype.factory.js diff --git a/packages/troika-3d-text/libs/typr.factory.js b/packages/troika-three-text/libs/typr.factory.js similarity index 100% rename from packages/troika-3d-text/libs/typr.factory.js rename to packages/troika-three-text/libs/typr.factory.js diff --git a/packages/troika-3d-text/libs/woff2otf.factory.js b/packages/troika-three-text/libs/woff2otf.factory.js similarity index 100% rename from packages/troika-3d-text/libs/woff2otf.factory.js rename to packages/troika-three-text/libs/woff2otf.factory.js diff --git a/packages/troika-three-text/package.json b/packages/troika-three-text/package.json new file mode 100644 index 00000000..2d7966ad --- /dev/null +++ b/packages/troika-three-text/package.json @@ -0,0 +1,29 @@ +{ + "name": "troika-three-text", + "version": "0.28.1", + "description": "SDF-based text rendering for Three.js", + "author": "Jason Johnston ", + "repository": { + "type": "git", + "url": "https://github.com/protectwise/troika.git", + "directory": "packages/troika-three-text" + }, + "license": "MIT", + "main": "dist/troika-three-text.umd.js", + "browser": "dist/troika-three-text.umd.js", + "jsnext:main": "dist/troika-three-text.esm.js", + "module": "dist/troika-three-text.esm.js", + "module:src": "src/index.js", + "dependencies": { + "troika-three-utils": "^0.28.1", + "troika-worker-utils": "^0.28.0" + }, + "devDependencies": { + "node-fetch": "^2.6.0", + "opentype.js": "^1.0.1" + }, + "scripts": { + "build-opentype": "rollup -c rollup.config.build-opentype.js", + "build-typr": "node build-typr.js" + } +} diff --git a/packages/troika-3d-text/rollup.config.build-opentype.js b/packages/troika-three-text/rollup.config.build-opentype.js similarity index 100% rename from packages/troika-3d-text/rollup.config.build-opentype.js rename to packages/troika-three-text/rollup.config.build-opentype.js diff --git a/packages/troika-3d-text/screenshot1.png b/packages/troika-three-text/screenshot1.png similarity index 100% rename from packages/troika-3d-text/screenshot1.png rename to packages/troika-three-text/screenshot1.png diff --git a/packages/troika-3d-text/screenshot2.png b/packages/troika-three-text/screenshot2.png similarity index 100% rename from packages/troika-3d-text/screenshot2.png rename to packages/troika-three-text/screenshot2.png diff --git a/packages/troika-3d-text/screenshot3.png b/packages/troika-three-text/screenshot3.png similarity index 100% rename from packages/troika-3d-text/screenshot3.png rename to packages/troika-three-text/screenshot3.png diff --git a/packages/troika-3d-text/screenshot4.png b/packages/troika-three-text/screenshot4.png similarity index 100% rename from packages/troika-3d-text/screenshot4.png rename to packages/troika-three-text/screenshot4.png diff --git a/packages/troika-3d-text/src/three/GlyphsGeometry.js b/packages/troika-three-text/src/GlyphsGeometry.js similarity index 100% rename from packages/troika-3d-text/src/three/GlyphsGeometry.js rename to packages/troika-three-text/src/GlyphsGeometry.js diff --git a/packages/troika-3d-text/src/three/TextMesh.js b/packages/troika-three-text/src/Text.js similarity index 98% rename from packages/troika-3d-text/src/three/TextMesh.js rename to packages/troika-three-text/src/Text.js index 127ab770..2b53f3c1 100644 --- a/packages/troika-3d-text/src/three/TextMesh.js +++ b/packages/troika-three-text/src/Text.js @@ -8,7 +8,7 @@ import { } from 'three' import { GlyphsGeometry } from './GlyphsGeometry.js' import { createTextDerivedMaterial } from './TextDerivedMaterial.js' -import { getTextRenderInfo } from '../TextBuilder.js' +import { getTextRenderInfo } from './TextBuilder.js' @@ -61,12 +61,12 @@ const COPYABLE_PROPS = SYNCABLE_PROPS.concat( /** - * @class TextMesh + * @class Text * * A ThreeJS Mesh that renders a string of text on a plane in 3D space using signed distance * fields (SDF). */ -class TextMesh extends Mesh { +class Text extends Mesh { constructor() { const geometry = new GlyphsGeometry() super(geometry, null) @@ -483,7 +483,7 @@ class TextMesh extends Mesh { // Create setters for properties that affect text layout: SYNCABLE_PROPS.forEach(prop => { const privateKey = '_private_' + prop - Object.defineProperty(TextMesh.prototype, prop, { + Object.defineProperty(Text.prototype, prop, { get() { return this[privateKey] }, @@ -499,7 +499,7 @@ SYNCABLE_PROPS.forEach(prop => { // Deprecation handler for `anchor` array: let deprMsgShown = false -Object.defineProperty(TextMesh.prototype, 'anchor', { +Object.defineProperty(Text.prototype, 'anchor', { get() { return this._deprecated_anchor }, @@ -521,7 +521,7 @@ Object.defineProperty(TextMesh.prototype, 'anchor', { -export {TextMesh} +export {Text} diff --git a/packages/troika-3d-text/src/TextBuilder.js b/packages/troika-three-text/src/TextBuilder.js similarity index 93% rename from packages/troika-3d-text/src/TextBuilder.js rename to packages/troika-three-text/src/TextBuilder.js index e385d962..232679d5 100644 --- a/packages/troika-3d-text/src/TextBuilder.js +++ b/packages/troika-three-text/src/TextBuilder.js @@ -1,11 +1,11 @@ import { Color, DataTexture, LinearFilter, LuminanceFormat } from 'three' import { defineWorkerModule, ThenableWorkerModule } from 'troika-worker-utils' -import { createSDFGenerator } from './SDFGenerator.js' -import { createFontProcessor } from './FontProcessor.js' -import { createGlyphSegmentsQuadtree } from './GlyphSegmentsQuadtree' +import { createSDFGenerator } from './worker/SDFGenerator.js' +import { createFontProcessor } from './worker/FontProcessor.js' +import { createGlyphSegmentsQuadtree } from './worker/GlyphSegmentsQuadtree.js' // Choose parser impl: -import fontParser from './FontParser_Typr.js' +import fontParser from './worker/FontParser_Typr.js' //import fontParser from './FontParser_OpenType.js' @@ -217,17 +217,20 @@ function getTextRenderInfo(args, callback) { * This can be useful to avoid long pauses when first showing text in a scene, by preloading the * needed fonts and glyphs up front along with other assets. * - * @param {string} font - URL of the font file to preload. If not given, the default font will + * @param {object} options + * @param {string} options.font - URL of the font file to preload. If not given, the default font will * be loaded. - * @param {string|string[]} charSequences - One or more character sequences for which to pre- + * @param {string|string[]} options.characters - One or more character sequences for which to pre- * generate glyph SDFs. Note that this will honor ligature substitution, so you may need * to specify ligature sequences in addition to their individual characters to get all * possible glyphs, e.g. `["t", "h", "th"]` to get the "t" and "h" glyphs plus the "th" ligature. + * @param {number} options.sdfGlyphSize - The size at which to prerender the SDF textures for the + * specified `characters`. * @param {function} callback - A function that will be called when the preloading is complete. */ -function preloadFont(font, charSequences, callback) { - let text = Array.isArray(charSequences) ? charSequences.join('\n') : '' + charSequences - getTextRenderInfo({ font, text }, callback) +function preloadFont({font, characters, sdfGlyphSize}, callback) { + let text = Array.isArray(characters) ? characters.join('\n') : '' + characters + getTextRenderInfo({ font, sdfGlyphSize, text }, callback) } diff --git a/packages/troika-3d-text/src/three/TextDerivedMaterial.js b/packages/troika-three-text/src/TextDerivedMaterial.js similarity index 100% rename from packages/troika-3d-text/src/three/TextDerivedMaterial.js rename to packages/troika-three-text/src/TextDerivedMaterial.js diff --git a/packages/troika-three-text/src/index.js b/packages/troika-three-text/src/index.js new file mode 100644 index 00000000..7b8054ae --- /dev/null +++ b/packages/troika-three-text/src/index.js @@ -0,0 +1,7 @@ +// Exports for troika-three-text package: + +export { configureTextBuilder, fontProcessorWorkerModule, preloadFont } from './TextBuilder.js' +export { Text } from './Text.js' +export { GlyphsGeometry } from './GlyphsGeometry.js' +export { createTextDerivedMaterial } from './TextDerivedMaterial.js' +export { getCaretAtPoint, getSelectionRects } from './selectionUtils.js' diff --git a/packages/troika-3d-text/src/selectionUtils.js b/packages/troika-three-text/src/selectionUtils.js similarity index 100% rename from packages/troika-3d-text/src/selectionUtils.js rename to packages/troika-three-text/src/selectionUtils.js diff --git a/packages/troika-3d-text/src/woff2otf.js b/packages/troika-three-text/src/woff2otf.js similarity index 100% rename from packages/troika-3d-text/src/woff2otf.js rename to packages/troika-three-text/src/woff2otf.js diff --git a/packages/troika-3d-text/src/FontParser_OpenType.js b/packages/troika-three-text/src/worker/FontParser_OpenType.js similarity index 97% rename from packages/troika-3d-text/src/FontParser_OpenType.js rename to packages/troika-three-text/src/worker/FontParser_OpenType.js index 7feeb544..0a41c303 100644 --- a/packages/troika-3d-text/src/FontParser_OpenType.js +++ b/packages/troika-three-text/src/worker/FontParser_OpenType.js @@ -3,7 +3,7 @@ * Also adds support for WOFF files (not WOFF2). */ -import opentypeFactory from '../libs/opentype.factory.js' +import opentypeFactory from '../../libs/opentype.factory.js' import { defineWorkerModule } from 'troika-worker-utils' function parserFactory(opentype) { diff --git a/packages/troika-3d-text/src/FontParser_Typr.js b/packages/troika-three-text/src/worker/FontParser_Typr.js similarity index 97% rename from packages/troika-3d-text/src/FontParser_Typr.js rename to packages/troika-three-text/src/worker/FontParser_Typr.js index 4afa1740..cff31f65 100644 --- a/packages/troika-3d-text/src/FontParser_Typr.js +++ b/packages/troika-three-text/src/worker/FontParser_Typr.js @@ -3,8 +3,8 @@ * Also adds support for WOFF files (not WOFF2). */ -import typrFactory from '../libs/typr.factory.js' -import woff2otfFactory from '../libs/woff2otf.factory.js' +import typrFactory from '../../libs/typr.factory.js' +import woff2otfFactory from '../../libs/woff2otf.factory.js' import {defineWorkerModule} from 'troika-worker-utils' function parserFactory(Typr, woff2otf) { diff --git a/packages/troika-3d-text/src/FontProcessor.js b/packages/troika-three-text/src/worker/FontProcessor.js similarity index 100% rename from packages/troika-3d-text/src/FontProcessor.js rename to packages/troika-three-text/src/worker/FontProcessor.js diff --git a/packages/troika-3d-text/src/GlyphSegmentsQuadtree.js b/packages/troika-three-text/src/worker/GlyphSegmentsQuadtree.js similarity index 100% rename from packages/troika-3d-text/src/GlyphSegmentsQuadtree.js rename to packages/troika-three-text/src/worker/GlyphSegmentsQuadtree.js diff --git a/packages/troika-3d-text/src/SDFGenerator.js b/packages/troika-three-text/src/worker/SDFGenerator.js similarity index 100% rename from packages/troika-3d-text/src/SDFGenerator.js rename to packages/troika-three-text/src/worker/SDFGenerator.js