@codexo/exojs 0.6.7 → 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -0
- package/README.md +0 -36
- package/dist/esm/index.d.ts +0 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/rendering/index.d.ts +3 -0
- package/dist/esm/rendering/text/DynamicGlyphAtlas.d.ts +33 -0
- package/dist/esm/rendering/text/DynamicGlyphAtlas.js +140 -0
- package/dist/esm/rendering/text/DynamicGlyphAtlas.js.map +1 -0
- package/dist/esm/rendering/text/Text.d.ts +21 -17
- package/dist/esm/rendering/text/Text.js +93 -93
- package/dist/esm/rendering/text/Text.js.map +1 -1
- package/dist/esm/rendering/text/TextLayout.d.ts +13 -0
- package/dist/esm/rendering/text/TextLayout.js +63 -0
- package/dist/esm/rendering/text/TextLayout.js.map +1 -0
- package/dist/esm/rendering/text/TextStyle.d.ts +26 -3
- package/dist/esm/rendering/text/TextStyle.js +45 -0
- package/dist/esm/rendering/text/TextStyle.js.map +1 -1
- package/dist/esm/rendering/text/atlas-singleton.d.ts +7 -0
- package/dist/esm/rendering/text/atlas-singleton.js +17 -0
- package/dist/esm/rendering/text/atlas-singleton.js.map +1 -0
- package/dist/esm/rendering/text/types.d.ts +45 -0
- package/dist/esm/rendering/utils.js +1 -14
- package/dist/esm/rendering/utils.js.map +1 -1
- package/dist/exo.esm.js +344 -573
- package/dist/exo.esm.js.map +1 -1
- package/package.json +1 -9
- package/dist/esm/physics/RapierPhysicsWorld.d.ts +0 -136
- package/dist/esm/physics/RapierPhysicsWorld.js +0 -475
- package/dist/esm/physics/RapierPhysicsWorld.js.map +0 -1
- package/dist/esm/physics/index.d.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,125 @@ All notable changes to ExoJS are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.6.9] - 2026-05-02
|
|
8
|
+
|
|
9
|
+
> **Heads-up — breaking change despite the patch number.** `Text`'s
|
|
10
|
+
> internal architecture changed completely: glyph-quad meshing
|
|
11
|
+
> against a runtime atlas instead of canvas2d-rasterize-as-Sprite.
|
|
12
|
+
> The user-facing API for `text.text`, `text.style`, and standard
|
|
13
|
+
> Drawable transforms (`position`, `rotation`, `scale`, etc.) is
|
|
14
|
+
> unchanged, but `text.canvas`, `text.setCanvas`, `text.textureFrame`,
|
|
15
|
+
> `text.getWordWrappedText`, and the `Text instanceof Sprite` check
|
|
16
|
+
> are gone. Text is now `Text extends Container`, not Sprite.
|
|
17
|
+
|
|
18
|
+
GPU font glyphs (Pixi-style runtime cache). Replaces the prior
|
|
19
|
+
canvas-rasterize-the-whole-string-as-Sprite path with: rasterize
|
|
20
|
+
each glyph once into a shared atlas Texture, build a single Mesh
|
|
21
|
+
per Text whose quads sample the atlas. All Texts in the page share
|
|
22
|
+
one atlas — memory-efficient at scale, single drawcall per Text.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`DynamicGlyphAtlas`** — public class. Constructor takes
|
|
27
|
+
`width = 1024, height = 1024`. Has `getGlyph(char, family, size,
|
|
28
|
+
weight, style) → GlyphInfo` (cached or rasterizes), `clear()` to
|
|
29
|
+
reset, and `texture` for binding to a Mesh. Internal shelf
|
|
30
|
+
bin-packing; throws on atlas-full (LRU eviction is V2).
|
|
31
|
+
- **`layoutText(text, style, atlas)`** — pure function. Returns
|
|
32
|
+
`readonly GlyphPlacement[]` with one quad per visible glyph.
|
|
33
|
+
Handles `\n` line breaks and `align: 'left' | 'center' | 'right'`
|
|
34
|
+
alignment per `style.align`. Empty text returns `[]`.
|
|
35
|
+
- **Types: `GlyphInfo`, `GlyphPlacement`, `GlyphKey`,
|
|
36
|
+
`TextAlignment`** — all exported for users who want to compose
|
|
37
|
+
their own atlas / layout pipelines.
|
|
38
|
+
- **TextStyle gets `fillColor: Color`** (defaults to white, used
|
|
39
|
+
via mesh.tint after glyph rasterization), **`fontStyle: 'normal'
|
|
40
|
+
| 'italic'`**, and **`lineHeight: number`** (multiplied by
|
|
41
|
+
fontSize for line spacing, defaults to 1.2). `align` field is
|
|
42
|
+
now strongly typed as `TextAlignment`.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **`Text` extends `Container`** (was `Sprite`). It internally
|
|
47
|
+
manages a single `Mesh` child whose vertices/uvs/indices are
|
|
48
|
+
rebuilt on every `text` / `style` setter call. Empty string =
|
|
49
|
+
no internal mesh (no children).
|
|
50
|
+
- **Glyphs always rasterize white**; `style.fillColor` becomes
|
|
51
|
+
`mesh.tint`. Changing fillColor is cheap (mesh-tint update only,
|
|
52
|
+
no atlas re-rasterization).
|
|
53
|
+
|
|
54
|
+
### Removed
|
|
55
|
+
|
|
56
|
+
- `Text.canvas` getter / setter, `Text.setCanvas(...)`,
|
|
57
|
+
`Text.textureFrame`, `Text.updateTexture(...)`,
|
|
58
|
+
`Text.getWordWrappedText(...)` — the old canvas2d path is gone.
|
|
59
|
+
Word-wrapping is V2; for now use `\n` for explicit line breaks.
|
|
60
|
+
|
|
61
|
+
### Notes
|
|
62
|
+
|
|
63
|
+
- Atlas is a process-wide singleton via `getDefaultGlyphAtlas()`
|
|
64
|
+
(internal helper, not a public function). All `Text` instances
|
|
65
|
+
share one atlas. Tests can reset it via `atlas.clear()`.
|
|
66
|
+
- The atlas uses `OffscreenCanvas` when available, falls back to
|
|
67
|
+
`document.createElement('canvas')` (works in jsdom / older
|
|
68
|
+
browsers).
|
|
69
|
+
- First-render of a never-seen glyph costs one canvas2d round-trip
|
|
70
|
+
+ texture re-upload. Cached glyphs are zero-cost on subsequent
|
|
71
|
+
renders.
|
|
72
|
+
- Per-character animation, MSDF rendering, word-wrap, BiDi, and
|
|
73
|
+
text outlines / drop-shadows are all V2.
|
|
74
|
+
|
|
75
|
+
## [0.6.8] - 2026-05-02
|
|
76
|
+
|
|
77
|
+
> **Heads-up — breaking change despite the patch number.** Removes
|
|
78
|
+
> the optional Rapier physics integration in its entirety. Pre-1.0
|
|
79
|
+
> SemVer permits breaking changes within the 0.x.y line; we kept
|
|
80
|
+
> the minor digit unchanged because the integration was opt-in and
|
|
81
|
+
> usage outside the engine is presumed minimal.
|
|
82
|
+
|
|
83
|
+
### Removed
|
|
84
|
+
|
|
85
|
+
- **`createRapierPhysicsWorld` factory and the `RapierPhysicsWorld`
|
|
86
|
+
/ `RapierPhysicsBinding` classes.** Plus the entire associated
|
|
87
|
+
type surface (`PhysicsBodyOptions`, `PhysicsBodyType`,
|
|
88
|
+
`PhysicsBoxShape`, `PhysicsCircleShape`, `PhysicsColliderShape`,
|
|
89
|
+
`PhysicsCollisionFilter`, `PhysicsSyncMode`, `RapierModuleLoader`,
|
|
90
|
+
`RapierPhysicsDebugDrawOptions`, `RapierPhysicsEvent`,
|
|
91
|
+
`RapierPhysicsWorldOptions`).
|
|
92
|
+
- **`@dimforge/rapier2d-compat` peerDependency.** Removed from
|
|
93
|
+
`package.json` along with the `peerDependenciesMeta` entry that
|
|
94
|
+
marked it optional.
|
|
95
|
+
- **README's "Optional Rapier Physics" section** and the
|
|
96
|
+
feature-list bullets that mentioned it.
|
|
97
|
+
- **`src/physics/`** and **`test/physics/`** directories deleted.
|
|
98
|
+
|
|
99
|
+
### Migration
|
|
100
|
+
|
|
101
|
+
Apps that depended on `createRapierPhysicsWorld` need to integrate
|
|
102
|
+
Rapier (or any other physics library) directly in their own code
|
|
103
|
+
without library involvement. The adapter was always intentionally
|
|
104
|
+
narrow — it bound Rapier bodies to scene nodes from the outside,
|
|
105
|
+
no rendering / application / core scene code referenced physics.
|
|
106
|
+
Removing it is therefore mechanical for downstream consumers:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// Before (≤ 0.6.7)
|
|
110
|
+
import { createRapierPhysicsWorld } from '@codexo/exojs';
|
|
111
|
+
const physics = await createRapierPhysicsWorld({ gravityY: 9.81 });
|
|
112
|
+
|
|
113
|
+
// After (0.6.8+) — pull Rapier directly:
|
|
114
|
+
import RAPIER from '@dimforge/rapier2d-compat';
|
|
115
|
+
await RAPIER.init();
|
|
116
|
+
const physics = new RAPIER.World({ x: 0, y: 9.81 });
|
|
117
|
+
// Sync bodies to your scene-node positions in your app's update loop.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The motivation: ExoJS doesn't want to be a thin wrapper around
|
|
121
|
+
Rapier's API, and keeping the integration around tied the library
|
|
122
|
+
to a specific physics library forever. Removing it cleans the
|
|
123
|
+
boundary — ExoJS is rendering + scene + input; physics is the
|
|
124
|
+
user's choice.
|
|
125
|
+
|
|
7
126
|
## [0.6.7] - 2026-05-02
|
|
8
127
|
|
|
9
128
|
Touch / multi-touch / pointer support, fully unified — no separate
|
package/README.md
CHANGED
|
@@ -14,7 +14,6 @@ ExoJS is **pre-1.0**. The public API is still under active design — scene grap
|
|
|
14
14
|
- Practical visuals: filters, masks, render passes, cache-as-bitmap
|
|
15
15
|
- Gameplay tools: animated sprites, scene stacking, camera helpers, audio sprites
|
|
16
16
|
- Performance visibility with built-in render stats and benchmark harness
|
|
17
|
-
- Optional Rapier physics integration without forcing physics on every app
|
|
18
17
|
|
|
19
18
|
## What Is Shipped Today
|
|
20
19
|
|
|
@@ -25,7 +24,6 @@ ExoJS is **pre-1.0**. The public API is still under active design — scene grap
|
|
|
25
24
|
- View/camera helpers (`follow`, bounds clamp, shake, zoom)
|
|
26
25
|
- Rendering composition primitives (`RenderTexture`, `RenderTargetPass`, filter chains, visual masks, cache-as-bitmap)
|
|
27
26
|
- Render stats (`submittedNodes`, `culledNodes`, `drawCalls`, `batches`, `renderPasses`, ...)
|
|
28
|
-
- Optional Rapier adapter (`createRapierPhysicsWorld`)
|
|
29
27
|
|
|
30
28
|
## Installation
|
|
31
29
|
|
|
@@ -98,40 +96,6 @@ new Application({ backend: { type: 'webgl2' } });
|
|
|
98
96
|
new Application({ backend: { type: 'auto' } });
|
|
99
97
|
```
|
|
100
98
|
|
|
101
|
-
## Optional Rapier Physics
|
|
102
|
-
|
|
103
|
-
Rapier integration is opt-in and loaded only when you use it.
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
import { createRapierPhysicsWorld } from '@codexo/exojs';
|
|
107
|
-
|
|
108
|
-
const physics = await createRapierPhysicsWorld({ gravityY: 9.81 });
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
If Rapier is unavailable, creation fails with a clear setup error.
|
|
112
|
-
|
|
113
|
-
### Physics scope policy
|
|
114
|
-
|
|
115
|
-
ExoJS ships **one** physics adapter: Rapier. The integration is intentionally
|
|
116
|
-
narrow:
|
|
117
|
-
|
|
118
|
-
- Physics is **optional**. `@dimforge/rapier2d-compat` is a peer dependency
|
|
119
|
-
marked `optional`. Apps that do not call `createRapierPhysicsWorld` never
|
|
120
|
-
load it and never pay for it at runtime.
|
|
121
|
-
- Rendering, application, and core scene code **do not** depend on physics.
|
|
122
|
-
The adapter binds Rapier bodies to scene nodes from the outside; the core
|
|
123
|
-
has no knowledge of physics.
|
|
124
|
-
- ExoJS is **not** a physics-engine abstraction layer. There is no
|
|
125
|
-
`PhysicsWorld` interface that spans multiple backends, and no plan to
|
|
126
|
-
add one. If you need a different physics library, integrate it directly
|
|
127
|
-
in your app code without library involvement.
|
|
128
|
-
- A second physics adapter (Box2D, Matter.js, Planck, etc.) is **not** on
|
|
129
|
-
the 1.0 roadmap and will not be accepted as a contribution. The honesty
|
|
130
|
-
rule that applies to rendering backends applies here too: one chosen
|
|
131
|
-
physics, not a fake-universal physics layer.
|
|
132
|
-
|
|
133
|
-
For full integration details see [docs/physics/rapier-integration.md](docs/physics/rapier-integration.md).
|
|
134
|
-
|
|
135
99
|
## Examples
|
|
136
100
|
|
|
137
101
|
The runnable live site (Astro + Lit + Monaco preview) lives in [`examples/`](examples/README.md) and is deployed as the repository's GitHub Pages site at <https://exoridus.github.io/ExoJS/>.
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -70,7 +70,9 @@ export { ShaderUniform } from './rendering/shader/ShaderUniform.js';
|
|
|
70
70
|
export { Sprite, SpriteFlags } from './rendering/sprite/Sprite.js';
|
|
71
71
|
export { Spritesheet } from './rendering/sprite/Spritesheet.js';
|
|
72
72
|
export { AnimatedSprite } from './rendering/sprite/AnimatedSprite.js';
|
|
73
|
+
export { DynamicGlyphAtlas } from './rendering/text/DynamicGlyphAtlas.js';
|
|
73
74
|
export { Text } from './rendering/text/Text.js';
|
|
75
|
+
export { layoutText } from './rendering/text/TextLayout.js';
|
|
74
76
|
export { TextStyle } from './rendering/text/TextStyle.js';
|
|
75
77
|
export { RenderTexture } from './rendering/texture/RenderTexture.js';
|
|
76
78
|
export { Sampler } from './rendering/texture/Sampler.js';
|
|
@@ -129,5 +131,4 @@ export { VideoFactory } from './resources/factories/VideoFactory.js';
|
|
|
129
131
|
export { BinaryFactory } from './resources/factories/BinaryFactory.js';
|
|
130
132
|
export { VttFactory } from './resources/factories/VttFactory.js';
|
|
131
133
|
export { WasmFactory } from './resources/factories/WasmFactory.js';
|
|
132
|
-
export { RapierPhysicsBinding, RapierPhysicsWorld, createRapierPhysicsWorld } from './physics/RapierPhysicsWorld.js';
|
|
133
134
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -6,8 +6,11 @@ export * from './shader/ShaderUniform';
|
|
|
6
6
|
export * from './sprite/Sprite';
|
|
7
7
|
export * from './sprite/Spritesheet';
|
|
8
8
|
export * from './sprite/AnimatedSprite';
|
|
9
|
+
export * from './text/DynamicGlyphAtlas';
|
|
9
10
|
export * from './text/Text';
|
|
11
|
+
export * from './text/TextLayout';
|
|
10
12
|
export * from './text/TextStyle';
|
|
13
|
+
export * from './text/types';
|
|
11
14
|
export * from './texture/RenderTexture';
|
|
12
15
|
export * from './texture/Sampler';
|
|
13
16
|
export * from './texture/Texture';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Texture } from '@/rendering/texture/Texture';
|
|
2
|
+
import type { GlyphInfo } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A shared atlas that rasterizes glyphs on demand into an offscreen canvas
|
|
5
|
+
* and wraps it as a Texture for use by the Mesh-based Text renderer.
|
|
6
|
+
*
|
|
7
|
+
* Glyphs are always rasterized in white so that runtime tinting via
|
|
8
|
+
* `Mesh.tint` applies the fill color without requiring re-rasterization.
|
|
9
|
+
*
|
|
10
|
+
* Use `getDefaultGlyphAtlas()` from `atlas-singleton.ts` rather than
|
|
11
|
+
* constructing directly.
|
|
12
|
+
*/
|
|
13
|
+
export declare class DynamicGlyphAtlas {
|
|
14
|
+
readonly texture: Texture;
|
|
15
|
+
private readonly _canvas;
|
|
16
|
+
private readonly _ctx;
|
|
17
|
+
private readonly _packer;
|
|
18
|
+
private readonly _cache;
|
|
19
|
+
private readonly _width;
|
|
20
|
+
private readonly _height;
|
|
21
|
+
constructor(width?: number, height?: number);
|
|
22
|
+
/**
|
|
23
|
+
* Returns the cached GlyphInfo for the given character + font parameters,
|
|
24
|
+
* rasterizing it into the atlas if not already present.
|
|
25
|
+
*/
|
|
26
|
+
getGlyph(char: string, family: string, size: number, weight: string | number, style: 'normal' | 'italic'): GlyphInfo;
|
|
27
|
+
/**
|
|
28
|
+
* Clears all cached glyphs and resets the atlas packer.
|
|
29
|
+
* The underlying canvas pixels are also cleared.
|
|
30
|
+
*/
|
|
31
|
+
clear(): void;
|
|
32
|
+
private _rasterize;
|
|
33
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Texture } from '../texture/Texture.js';
|
|
2
|
+
|
|
3
|
+
const glyphPadding = 2;
|
|
4
|
+
class ShelfPacker {
|
|
5
|
+
_shelves = [];
|
|
6
|
+
_width;
|
|
7
|
+
_height;
|
|
8
|
+
constructor(width, height) {
|
|
9
|
+
this._width = width;
|
|
10
|
+
this._height = height;
|
|
11
|
+
}
|
|
12
|
+
insert(width, height) {
|
|
13
|
+
// Try existing shelves in order (ascending y)
|
|
14
|
+
for (const shelf of this._shelves) {
|
|
15
|
+
if (shelf.height >= height && shelf.cursorX + width <= this._width) {
|
|
16
|
+
const x = shelf.cursorX;
|
|
17
|
+
shelf.cursorX += width;
|
|
18
|
+
return { x, y: shelf.y };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Create a new shelf at the bottom
|
|
22
|
+
const last = this._shelves[this._shelves.length - 1];
|
|
23
|
+
const bottomY = last === undefined ? 0 : last.y + last.height;
|
|
24
|
+
if (bottomY + height > this._height) {
|
|
25
|
+
throw new Error(`GlyphAtlas full — clear() and re-render, or instantiate with larger dims`);
|
|
26
|
+
}
|
|
27
|
+
this._shelves.push({ y: bottomY, height, cursorX: width });
|
|
28
|
+
return { x: 0, y: bottomY };
|
|
29
|
+
}
|
|
30
|
+
reset() {
|
|
31
|
+
this._shelves.length = 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A shared atlas that rasterizes glyphs on demand into an offscreen canvas
|
|
36
|
+
* and wraps it as a Texture for use by the Mesh-based Text renderer.
|
|
37
|
+
*
|
|
38
|
+
* Glyphs are always rasterized in white so that runtime tinting via
|
|
39
|
+
* `Mesh.tint` applies the fill color without requiring re-rasterization.
|
|
40
|
+
*
|
|
41
|
+
* Use `getDefaultGlyphAtlas()` from `atlas-singleton.ts` rather than
|
|
42
|
+
* constructing directly.
|
|
43
|
+
*/
|
|
44
|
+
class DynamicGlyphAtlas {
|
|
45
|
+
texture;
|
|
46
|
+
_canvas;
|
|
47
|
+
_ctx;
|
|
48
|
+
_packer;
|
|
49
|
+
_cache = new Map();
|
|
50
|
+
_width;
|
|
51
|
+
_height;
|
|
52
|
+
constructor(width = 1024, height = 1024) {
|
|
53
|
+
this._width = width;
|
|
54
|
+
this._height = height;
|
|
55
|
+
// Use OffscreenCanvas when available, fall back to HTMLCanvasElement.
|
|
56
|
+
// In jsdom / Node the global may be absent; createCanvas falls through
|
|
57
|
+
// to document.createElement which jsdom provides.
|
|
58
|
+
const canvas = typeof OffscreenCanvas !== 'undefined'
|
|
59
|
+
? new OffscreenCanvas(width, height)
|
|
60
|
+
: document.createElement('canvas');
|
|
61
|
+
if ('width' in canvas) {
|
|
62
|
+
canvas.width = width;
|
|
63
|
+
canvas.height = height;
|
|
64
|
+
}
|
|
65
|
+
this._canvas = canvas;
|
|
66
|
+
const ctx = canvas.getContext('2d');
|
|
67
|
+
if (ctx === null) {
|
|
68
|
+
throw new Error('DynamicGlyphAtlas: could not obtain a 2D context.');
|
|
69
|
+
}
|
|
70
|
+
this._ctx = ctx;
|
|
71
|
+
this._packer = new ShelfPacker(width, height);
|
|
72
|
+
this.texture = new Texture(canvas);
|
|
73
|
+
this.texture.setSize(width, height);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Returns the cached GlyphInfo for the given character + font parameters,
|
|
77
|
+
* rasterizing it into the atlas if not already present.
|
|
78
|
+
*/
|
|
79
|
+
getGlyph(char, family, size, weight, style) {
|
|
80
|
+
const key = `${char}:${family}:${size}:${weight}:${style}`;
|
|
81
|
+
const cached = this._cache.get(key);
|
|
82
|
+
if (cached !== undefined) {
|
|
83
|
+
return cached;
|
|
84
|
+
}
|
|
85
|
+
const info = this._rasterize(char, family, size, weight, style, key);
|
|
86
|
+
this._cache.set(key, info);
|
|
87
|
+
// Bump texture version so GPU backends re-upload the canvas data.
|
|
88
|
+
this.texture.updateSource();
|
|
89
|
+
return info;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Clears all cached glyphs and resets the atlas packer.
|
|
93
|
+
* The underlying canvas pixels are also cleared.
|
|
94
|
+
*/
|
|
95
|
+
clear() {
|
|
96
|
+
this._cache.clear();
|
|
97
|
+
this._packer.reset();
|
|
98
|
+
this._ctx.clearRect(0, 0, this._width, this._height);
|
|
99
|
+
this.texture.updateSource();
|
|
100
|
+
}
|
|
101
|
+
// -----------------------------------------------------------------------
|
|
102
|
+
_rasterize(char, family, size, weight, fontStyle, _key) {
|
|
103
|
+
const ctx = this._ctx;
|
|
104
|
+
const padding = glyphPadding;
|
|
105
|
+
ctx.font = `${fontStyle} ${weight} ${size}px ${family}`;
|
|
106
|
+
ctx.textBaseline = 'alphabetic';
|
|
107
|
+
ctx.fillStyle = '#ffffff';
|
|
108
|
+
const metrics = ctx.measureText(char);
|
|
109
|
+
const ascent = Math.ceil(metrics.fontBoundingBoxAscent
|
|
110
|
+
?? metrics.actualBoundingBoxAscent
|
|
111
|
+
?? size * 0.8);
|
|
112
|
+
const descent = Math.ceil(metrics.fontBoundingBoxDescent
|
|
113
|
+
?? metrics.actualBoundingBoxDescent
|
|
114
|
+
?? size * 0.2);
|
|
115
|
+
const advance = metrics.width;
|
|
116
|
+
const glyphWidth = Math.max(1, Math.ceil((metrics.actualBoundingBoxLeft ?? 0) + (metrics.actualBoundingBoxRight ?? 0)) || Math.ceil(advance));
|
|
117
|
+
const glyphHeight = Math.max(1, ascent + descent);
|
|
118
|
+
const slotW = glyphWidth + padding * 2;
|
|
119
|
+
const slotH = glyphHeight + padding * 2;
|
|
120
|
+
const slot = this._packer.insert(slotW, slotH);
|
|
121
|
+
// Draw the glyph white into the atlas slot
|
|
122
|
+
ctx.fillText(char, slot.x + padding + (metrics.actualBoundingBoxLeft ?? 0), slot.y + padding + ascent);
|
|
123
|
+
const info = {
|
|
124
|
+
x: slot.x,
|
|
125
|
+
y: slot.y,
|
|
126
|
+
width: glyphWidth,
|
|
127
|
+
height: glyphHeight,
|
|
128
|
+
advance,
|
|
129
|
+
ascent,
|
|
130
|
+
uvLeft: slot.x / this._width,
|
|
131
|
+
uvTop: slot.y / this._height,
|
|
132
|
+
uvRight: (slot.x + slotW) / this._width,
|
|
133
|
+
uvBottom: (slot.y + slotH) / this._height,
|
|
134
|
+
};
|
|
135
|
+
return info;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { DynamicGlyphAtlas };
|
|
140
|
+
//# sourceMappingURL=DynamicGlyphAtlas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DynamicGlyphAtlas.js","sources":["../../../../../src/rendering/text/DynamicGlyphAtlas.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Container } from '@/rendering/Container';
|
|
2
2
|
import type { TextStyleOptions } from './TextStyle';
|
|
3
3
|
import { TextStyle } from './TextStyle';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* GPU-accelerated text node that rasterizes individual glyphs into a shared
|
|
6
|
+
* atlas ({@link DynamicGlyphAtlas}) and renders them as a single quad-per-
|
|
7
|
+
* glyph {@link Mesh} (one draw call per Text instance).
|
|
8
|
+
*
|
|
9
|
+
* Glyphs are always rasterized in white and tinted at runtime via
|
|
10
|
+
* `Mesh.tint`; changing `style.fillColor` only updates the mesh tint —
|
|
11
|
+
* no atlas re-rasterization is needed.
|
|
12
|
+
*
|
|
13
|
+
* The internal {@link Mesh} is the sole child of this {@link Container}.
|
|
14
|
+
* All transform properties (position, rotation, scale, origin) are
|
|
15
|
+
* inherited from {@link Container} → {@link RenderNode}.
|
|
16
|
+
*/
|
|
17
|
+
export declare class Text extends Container {
|
|
7
18
|
private _text;
|
|
8
19
|
private _style;
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
private _dirty;
|
|
12
|
-
constructor(text: string, style?: TextStyle | TextStyleOptions, samplerOptions?: Partial<SamplerOptions>, canvas?: HTMLCanvasElement);
|
|
20
|
+
private _mesh;
|
|
21
|
+
constructor(text: string, style?: TextStyle | TextStyleOptions);
|
|
13
22
|
get text(): string;
|
|
14
|
-
set text(
|
|
23
|
+
set text(value: string);
|
|
15
24
|
get style(): TextStyle;
|
|
16
|
-
set style(style: TextStyle);
|
|
17
|
-
get canvas(): HTMLCanvasElement;
|
|
18
|
-
set canvas(canvas: HTMLCanvasElement);
|
|
25
|
+
set style(style: TextStyle | TextStyleOptions);
|
|
19
26
|
setText(text: string): this;
|
|
20
27
|
setStyle(style: TextStyle | TextStyleOptions): this;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
getWordWrappedText(): string;
|
|
24
|
-
render(backend: RenderBackend): this;
|
|
25
|
-
private _getContext;
|
|
28
|
+
destroy(): void;
|
|
29
|
+
private _rebuild;
|
|
26
30
|
}
|