@codexo/exojs 0.6.8 → 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 +68 -0
- package/dist/esm/index.js +2 -0
- 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 -104
- package/dist/exo.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/exo.esm.js
CHANGED
|
@@ -6211,19 +6211,6 @@ const createCanvas = (options = {}) => {
|
|
|
6211
6211
|
context.fillRect(0, 0, newCanvas.width, newCanvas.height);
|
|
6212
6212
|
return newCanvas;
|
|
6213
6213
|
};
|
|
6214
|
-
const heightCache = new Map();
|
|
6215
|
-
const determineFontHeight = (font) => {
|
|
6216
|
-
if (!heightCache.has(font)) {
|
|
6217
|
-
const body = document.body;
|
|
6218
|
-
const dummy = document.createElement('div');
|
|
6219
|
-
dummy.appendChild(document.createTextNode('M'));
|
|
6220
|
-
dummy.setAttribute('style', `font: ${font};position:absolute;top:0;left:0`);
|
|
6221
|
-
body.appendChild(dummy);
|
|
6222
|
-
heightCache.set(font, dummy.offsetHeight);
|
|
6223
|
-
body.removeChild(dummy);
|
|
6224
|
-
}
|
|
6225
|
-
return heightCache.get(font);
|
|
6226
|
-
};
|
|
6227
6214
|
|
|
6228
6215
|
class Texture {
|
|
6229
6216
|
static _black = null;
|
|
@@ -17629,14 +17616,227 @@ class AnimatedSprite extends Sprite {
|
|
|
17629
17616
|
}
|
|
17630
17617
|
}
|
|
17631
17618
|
|
|
17619
|
+
const glyphPadding = 2;
|
|
17620
|
+
class ShelfPacker {
|
|
17621
|
+
_shelves = [];
|
|
17622
|
+
_width;
|
|
17623
|
+
_height;
|
|
17624
|
+
constructor(width, height) {
|
|
17625
|
+
this._width = width;
|
|
17626
|
+
this._height = height;
|
|
17627
|
+
}
|
|
17628
|
+
insert(width, height) {
|
|
17629
|
+
// Try existing shelves in order (ascending y)
|
|
17630
|
+
for (const shelf of this._shelves) {
|
|
17631
|
+
if (shelf.height >= height && shelf.cursorX + width <= this._width) {
|
|
17632
|
+
const x = shelf.cursorX;
|
|
17633
|
+
shelf.cursorX += width;
|
|
17634
|
+
return { x, y: shelf.y };
|
|
17635
|
+
}
|
|
17636
|
+
}
|
|
17637
|
+
// Create a new shelf at the bottom
|
|
17638
|
+
const last = this._shelves[this._shelves.length - 1];
|
|
17639
|
+
const bottomY = last === undefined ? 0 : last.y + last.height;
|
|
17640
|
+
if (bottomY + height > this._height) {
|
|
17641
|
+
throw new Error(`GlyphAtlas full — clear() and re-render, or instantiate with larger dims`);
|
|
17642
|
+
}
|
|
17643
|
+
this._shelves.push({ y: bottomY, height, cursorX: width });
|
|
17644
|
+
return { x: 0, y: bottomY };
|
|
17645
|
+
}
|
|
17646
|
+
reset() {
|
|
17647
|
+
this._shelves.length = 0;
|
|
17648
|
+
}
|
|
17649
|
+
}
|
|
17650
|
+
/**
|
|
17651
|
+
* A shared atlas that rasterizes glyphs on demand into an offscreen canvas
|
|
17652
|
+
* and wraps it as a Texture for use by the Mesh-based Text renderer.
|
|
17653
|
+
*
|
|
17654
|
+
* Glyphs are always rasterized in white so that runtime tinting via
|
|
17655
|
+
* `Mesh.tint` applies the fill color without requiring re-rasterization.
|
|
17656
|
+
*
|
|
17657
|
+
* Use `getDefaultGlyphAtlas()` from `atlas-singleton.ts` rather than
|
|
17658
|
+
* constructing directly.
|
|
17659
|
+
*/
|
|
17660
|
+
class DynamicGlyphAtlas {
|
|
17661
|
+
texture;
|
|
17662
|
+
_canvas;
|
|
17663
|
+
_ctx;
|
|
17664
|
+
_packer;
|
|
17665
|
+
_cache = new Map();
|
|
17666
|
+
_width;
|
|
17667
|
+
_height;
|
|
17668
|
+
constructor(width = 1024, height = 1024) {
|
|
17669
|
+
this._width = width;
|
|
17670
|
+
this._height = height;
|
|
17671
|
+
// Use OffscreenCanvas when available, fall back to HTMLCanvasElement.
|
|
17672
|
+
// In jsdom / Node the global may be absent; createCanvas falls through
|
|
17673
|
+
// to document.createElement which jsdom provides.
|
|
17674
|
+
const canvas = typeof OffscreenCanvas !== 'undefined'
|
|
17675
|
+
? new OffscreenCanvas(width, height)
|
|
17676
|
+
: document.createElement('canvas');
|
|
17677
|
+
if ('width' in canvas) {
|
|
17678
|
+
canvas.width = width;
|
|
17679
|
+
canvas.height = height;
|
|
17680
|
+
}
|
|
17681
|
+
this._canvas = canvas;
|
|
17682
|
+
const ctx = canvas.getContext('2d');
|
|
17683
|
+
if (ctx === null) {
|
|
17684
|
+
throw new Error('DynamicGlyphAtlas: could not obtain a 2D context.');
|
|
17685
|
+
}
|
|
17686
|
+
this._ctx = ctx;
|
|
17687
|
+
this._packer = new ShelfPacker(width, height);
|
|
17688
|
+
this.texture = new Texture(canvas);
|
|
17689
|
+
this.texture.setSize(width, height);
|
|
17690
|
+
}
|
|
17691
|
+
/**
|
|
17692
|
+
* Returns the cached GlyphInfo for the given character + font parameters,
|
|
17693
|
+
* rasterizing it into the atlas if not already present.
|
|
17694
|
+
*/
|
|
17695
|
+
getGlyph(char, family, size, weight, style) {
|
|
17696
|
+
const key = `${char}:${family}:${size}:${weight}:${style}`;
|
|
17697
|
+
const cached = this._cache.get(key);
|
|
17698
|
+
if (cached !== undefined) {
|
|
17699
|
+
return cached;
|
|
17700
|
+
}
|
|
17701
|
+
const info = this._rasterize(char, family, size, weight, style, key);
|
|
17702
|
+
this._cache.set(key, info);
|
|
17703
|
+
// Bump texture version so GPU backends re-upload the canvas data.
|
|
17704
|
+
this.texture.updateSource();
|
|
17705
|
+
return info;
|
|
17706
|
+
}
|
|
17707
|
+
/**
|
|
17708
|
+
* Clears all cached glyphs and resets the atlas packer.
|
|
17709
|
+
* The underlying canvas pixels are also cleared.
|
|
17710
|
+
*/
|
|
17711
|
+
clear() {
|
|
17712
|
+
this._cache.clear();
|
|
17713
|
+
this._packer.reset();
|
|
17714
|
+
this._ctx.clearRect(0, 0, this._width, this._height);
|
|
17715
|
+
this.texture.updateSource();
|
|
17716
|
+
}
|
|
17717
|
+
// -----------------------------------------------------------------------
|
|
17718
|
+
_rasterize(char, family, size, weight, fontStyle, _key) {
|
|
17719
|
+
const ctx = this._ctx;
|
|
17720
|
+
const padding = glyphPadding;
|
|
17721
|
+
ctx.font = `${fontStyle} ${weight} ${size}px ${family}`;
|
|
17722
|
+
ctx.textBaseline = 'alphabetic';
|
|
17723
|
+
ctx.fillStyle = '#ffffff';
|
|
17724
|
+
const metrics = ctx.measureText(char);
|
|
17725
|
+
const ascent = Math.ceil(metrics.fontBoundingBoxAscent
|
|
17726
|
+
?? metrics.actualBoundingBoxAscent
|
|
17727
|
+
?? size * 0.8);
|
|
17728
|
+
const descent = Math.ceil(metrics.fontBoundingBoxDescent
|
|
17729
|
+
?? metrics.actualBoundingBoxDescent
|
|
17730
|
+
?? size * 0.2);
|
|
17731
|
+
const advance = metrics.width;
|
|
17732
|
+
const glyphWidth = Math.max(1, Math.ceil((metrics.actualBoundingBoxLeft ?? 0) + (metrics.actualBoundingBoxRight ?? 0)) || Math.ceil(advance));
|
|
17733
|
+
const glyphHeight = Math.max(1, ascent + descent);
|
|
17734
|
+
const slotW = glyphWidth + padding * 2;
|
|
17735
|
+
const slotH = glyphHeight + padding * 2;
|
|
17736
|
+
const slot = this._packer.insert(slotW, slotH);
|
|
17737
|
+
// Draw the glyph white into the atlas slot
|
|
17738
|
+
ctx.fillText(char, slot.x + padding + (metrics.actualBoundingBoxLeft ?? 0), slot.y + padding + ascent);
|
|
17739
|
+
const info = {
|
|
17740
|
+
x: slot.x,
|
|
17741
|
+
y: slot.y,
|
|
17742
|
+
width: glyphWidth,
|
|
17743
|
+
height: glyphHeight,
|
|
17744
|
+
advance,
|
|
17745
|
+
ascent,
|
|
17746
|
+
uvLeft: slot.x / this._width,
|
|
17747
|
+
uvTop: slot.y / this._height,
|
|
17748
|
+
uvRight: (slot.x + slotW) / this._width,
|
|
17749
|
+
uvBottom: (slot.y + slotH) / this._height,
|
|
17750
|
+
};
|
|
17751
|
+
return info;
|
|
17752
|
+
}
|
|
17753
|
+
}
|
|
17754
|
+
|
|
17755
|
+
let _defaultAtlas = null;
|
|
17756
|
+
/**
|
|
17757
|
+
* Returns the shared process-wide glyph atlas, creating it lazily on first
|
|
17758
|
+
* call. All `Text` instances share this atlas so that identical glyph shapes
|
|
17759
|
+
* (same char + family + size + weight + style) are rasterized only once.
|
|
17760
|
+
*/
|
|
17761
|
+
function getDefaultGlyphAtlas() {
|
|
17762
|
+
if (_defaultAtlas === null) {
|
|
17763
|
+
_defaultAtlas = new DynamicGlyphAtlas();
|
|
17764
|
+
}
|
|
17765
|
+
return _defaultAtlas;
|
|
17766
|
+
}
|
|
17767
|
+
|
|
17768
|
+
/**
|
|
17769
|
+
* Computes per-glyph quad placements for the given text and style.
|
|
17770
|
+
*
|
|
17771
|
+
* Handles `\n` line breaks and left/center/right alignment. No word-wrap,
|
|
17772
|
+
* no RTL, no ligature shaping — Unicode/diacritics are delegated to the
|
|
17773
|
+
* browser's font engine via canvas `fillText`.
|
|
17774
|
+
*
|
|
17775
|
+
* Returns an empty array for empty text.
|
|
17776
|
+
*/
|
|
17777
|
+
function layoutText(text, style, atlas) {
|
|
17778
|
+
if (text.length === 0) {
|
|
17779
|
+
return [];
|
|
17780
|
+
}
|
|
17781
|
+
const { fontSize, fontFamily, fontWeight, fontStyle, lineHeight, align } = style;
|
|
17782
|
+
const computedLineHeight = fontSize * lineHeight;
|
|
17783
|
+
const lines = text.split('\n');
|
|
17784
|
+
// Pass 1: gather glyph info per line, track line widths
|
|
17785
|
+
const linePlacements = [];
|
|
17786
|
+
let maxLineWidth = 0;
|
|
17787
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
17788
|
+
const line = lines[lineIndex];
|
|
17789
|
+
const y = lineIndex * computedLineHeight;
|
|
17790
|
+
let cursorX = 0;
|
|
17791
|
+
const placements = [];
|
|
17792
|
+
for (const char of line) {
|
|
17793
|
+
const info = atlas.getGlyph(char, fontFamily, fontSize, fontWeight, fontStyle);
|
|
17794
|
+
placements.push({ info, x: cursorX, y });
|
|
17795
|
+
cursorX += info.advance;
|
|
17796
|
+
}
|
|
17797
|
+
const lineWidth = cursorX;
|
|
17798
|
+
if (lineWidth > maxLineWidth) {
|
|
17799
|
+
maxLineWidth = lineWidth;
|
|
17800
|
+
}
|
|
17801
|
+
linePlacements.push({ placements, width: lineWidth });
|
|
17802
|
+
}
|
|
17803
|
+
// Pass 2: apply alignment and build final GlyphPlacement array
|
|
17804
|
+
const result = [];
|
|
17805
|
+
for (const line of linePlacements) {
|
|
17806
|
+
let offsetX = 0;
|
|
17807
|
+
if (align === 'right') {
|
|
17808
|
+
offsetX = maxLineWidth - line.width;
|
|
17809
|
+
}
|
|
17810
|
+
else if (align === 'center') {
|
|
17811
|
+
offsetX = (maxLineWidth - line.width) / 2;
|
|
17812
|
+
}
|
|
17813
|
+
for (const { info, x, y } of line.placements) {
|
|
17814
|
+
result.push({
|
|
17815
|
+
x: x + offsetX,
|
|
17816
|
+
y,
|
|
17817
|
+
width: info.width,
|
|
17818
|
+
height: info.height,
|
|
17819
|
+
uvLeft: info.uvLeft,
|
|
17820
|
+
uvTop: info.uvTop,
|
|
17821
|
+
uvRight: info.uvRight,
|
|
17822
|
+
uvBottom: info.uvBottom,
|
|
17823
|
+
});
|
|
17824
|
+
}
|
|
17825
|
+
}
|
|
17826
|
+
return result;
|
|
17827
|
+
}
|
|
17828
|
+
|
|
17632
17829
|
class TextStyle {
|
|
17633
17830
|
_align;
|
|
17634
17831
|
_fill;
|
|
17832
|
+
_fillColor;
|
|
17833
|
+
_lineHeight;
|
|
17635
17834
|
_stroke;
|
|
17636
17835
|
_strokeThickness;
|
|
17637
17836
|
_fontSize;
|
|
17638
17837
|
_fontWeight;
|
|
17639
17838
|
_fontFamily;
|
|
17839
|
+
_fontStyle;
|
|
17640
17840
|
_wordWrap;
|
|
17641
17841
|
_wordWrapWidth;
|
|
17642
17842
|
_baseline;
|
|
@@ -17647,11 +17847,14 @@ class TextStyle {
|
|
|
17647
17847
|
constructor(options = {}) {
|
|
17648
17848
|
this._align = options.align ?? 'left';
|
|
17649
17849
|
this._fill = options.fill ?? 'black';
|
|
17850
|
+
this._fillColor = options.fillColor ?? Color.white.clone();
|
|
17851
|
+
this._lineHeight = options.lineHeight ?? 1.2;
|
|
17650
17852
|
this._stroke = options.stroke ?? 'black';
|
|
17651
17853
|
this._strokeThickness = options.strokeThickness ?? 1;
|
|
17652
17854
|
this._fontSize = options.fontSize ?? 20;
|
|
17653
17855
|
this._fontWeight = options.fontWeight ?? 'bold';
|
|
17654
17856
|
this._fontFamily = options.fontFamily ?? 'Arial';
|
|
17857
|
+
this._fontStyle = options.fontStyle ?? 'normal';
|
|
17655
17858
|
this._wordWrap = options.wordWrap ?? false;
|
|
17656
17859
|
this._wordWrapWidth = options.wordWrapWidth ?? 100;
|
|
17657
17860
|
this._baseline = options.baseline ?? 'alphabetic';
|
|
@@ -17677,6 +17880,31 @@ class TextStyle {
|
|
|
17677
17880
|
this._dirty = true;
|
|
17678
17881
|
}
|
|
17679
17882
|
}
|
|
17883
|
+
/**
|
|
17884
|
+
* Runtime fill color applied via `Mesh.tint`. Glyphs are always
|
|
17885
|
+
* rasterized white; this color multiplies them at draw time so that
|
|
17886
|
+
* changing the color never requires re-rasterizing cached glyphs.
|
|
17887
|
+
*/
|
|
17888
|
+
get fillColor() {
|
|
17889
|
+
return this._fillColor;
|
|
17890
|
+
}
|
|
17891
|
+
set fillColor(color) {
|
|
17892
|
+
this._fillColor = color;
|
|
17893
|
+
this._dirty = true;
|
|
17894
|
+
}
|
|
17895
|
+
/**
|
|
17896
|
+
* Line-height multiplier applied to `fontSize` when computing vertical
|
|
17897
|
+
* spacing between lines. Defaults to 1.2.
|
|
17898
|
+
*/
|
|
17899
|
+
get lineHeight() {
|
|
17900
|
+
return this._lineHeight;
|
|
17901
|
+
}
|
|
17902
|
+
set lineHeight(lineHeight) {
|
|
17903
|
+
if (this._lineHeight !== lineHeight) {
|
|
17904
|
+
this._lineHeight = lineHeight;
|
|
17905
|
+
this._dirty = true;
|
|
17906
|
+
}
|
|
17907
|
+
}
|
|
17680
17908
|
get stroke() {
|
|
17681
17909
|
return this._stroke;
|
|
17682
17910
|
}
|
|
@@ -17722,6 +17950,15 @@ class TextStyle {
|
|
|
17722
17950
|
this._dirty = true;
|
|
17723
17951
|
}
|
|
17724
17952
|
}
|
|
17953
|
+
get fontStyle() {
|
|
17954
|
+
return this._fontStyle;
|
|
17955
|
+
}
|
|
17956
|
+
set fontStyle(fontStyle) {
|
|
17957
|
+
if (this._fontStyle !== fontStyle) {
|
|
17958
|
+
this._fontStyle = fontStyle;
|
|
17959
|
+
this._dirty = true;
|
|
17960
|
+
}
|
|
17961
|
+
}
|
|
17725
17962
|
get wordWrap() {
|
|
17726
17963
|
return this._wordWrap;
|
|
17727
17964
|
}
|
|
@@ -17799,11 +18036,14 @@ class TextStyle {
|
|
|
17799
18036
|
if (style !== this) {
|
|
17800
18037
|
this.align = style.align;
|
|
17801
18038
|
this.fill = style.fill;
|
|
18039
|
+
this._fillColor = style.fillColor.clone();
|
|
18040
|
+
this.lineHeight = style.lineHeight;
|
|
17802
18041
|
this.stroke = style.stroke;
|
|
17803
18042
|
this.strokeThickness = style.strokeThickness;
|
|
17804
18043
|
this.fontSize = style.fontSize;
|
|
17805
18044
|
this.fontWeight = style.fontWeight;
|
|
17806
18045
|
this.fontFamily = style.fontFamily;
|
|
18046
|
+
this.fontStyle = style.fontStyle;
|
|
17807
18047
|
this.wordWrap = style.wordWrap;
|
|
17808
18048
|
this.wordWrapWidth = style.wordWrapWidth;
|
|
17809
18049
|
this.baseline = style.baseline;
|
|
@@ -17819,26 +18059,81 @@ class TextStyle {
|
|
|
17819
18059
|
}
|
|
17820
18060
|
}
|
|
17821
18061
|
|
|
17822
|
-
|
|
17823
|
-
|
|
18062
|
+
function buildMesh(placements, style) {
|
|
18063
|
+
const n = placements.length;
|
|
18064
|
+
const vertices = new Float32Array(n * 4 * 2);
|
|
18065
|
+
const uvs = new Float32Array(n * 4 * 2);
|
|
18066
|
+
const indices = new Uint16Array(n * 6);
|
|
18067
|
+
for (let i = 0; i < n; i++) {
|
|
18068
|
+
const p = placements[i];
|
|
18069
|
+
const v = i * 8;
|
|
18070
|
+
const u = i * 8;
|
|
18071
|
+
const idx = i * 6;
|
|
18072
|
+
const baseV = i * 4;
|
|
18073
|
+
// Vertices: TL, TR, BR, BL
|
|
18074
|
+
vertices[v + 0] = p.x;
|
|
18075
|
+
vertices[v + 1] = p.y;
|
|
18076
|
+
vertices[v + 2] = p.x + p.width;
|
|
18077
|
+
vertices[v + 3] = p.y;
|
|
18078
|
+
vertices[v + 4] = p.x + p.width;
|
|
18079
|
+
vertices[v + 5] = p.y + p.height;
|
|
18080
|
+
vertices[v + 6] = p.x;
|
|
18081
|
+
vertices[v + 7] = p.y + p.height;
|
|
18082
|
+
// UVs: TL, TR, BR, BL
|
|
18083
|
+
uvs[u + 0] = p.uvLeft;
|
|
18084
|
+
uvs[u + 1] = p.uvTop;
|
|
18085
|
+
uvs[u + 2] = p.uvRight;
|
|
18086
|
+
uvs[u + 3] = p.uvTop;
|
|
18087
|
+
uvs[u + 4] = p.uvRight;
|
|
18088
|
+
uvs[u + 5] = p.uvBottom;
|
|
18089
|
+
uvs[u + 6] = p.uvLeft;
|
|
18090
|
+
uvs[u + 7] = p.uvBottom;
|
|
18091
|
+
// Indices: [TL, TR, BR, TL, BR, BL]
|
|
18092
|
+
indices[idx + 0] = baseV + 0;
|
|
18093
|
+
indices[idx + 1] = baseV + 1;
|
|
18094
|
+
indices[idx + 2] = baseV + 2;
|
|
18095
|
+
indices[idx + 3] = baseV + 0;
|
|
18096
|
+
indices[idx + 4] = baseV + 2;
|
|
18097
|
+
indices[idx + 5] = baseV + 3;
|
|
18098
|
+
}
|
|
18099
|
+
const atlas = getDefaultGlyphAtlas();
|
|
18100
|
+
const mesh = new Mesh({
|
|
18101
|
+
vertices,
|
|
18102
|
+
uvs,
|
|
18103
|
+
indices,
|
|
18104
|
+
texture: atlas.texture,
|
|
18105
|
+
});
|
|
18106
|
+
mesh.tint = style.fillColor;
|
|
18107
|
+
return mesh;
|
|
18108
|
+
}
|
|
18109
|
+
/**
|
|
18110
|
+
* GPU-accelerated text node that rasterizes individual glyphs into a shared
|
|
18111
|
+
* atlas ({@link DynamicGlyphAtlas}) and renders them as a single quad-per-
|
|
18112
|
+
* glyph {@link Mesh} (one draw call per Text instance).
|
|
18113
|
+
*
|
|
18114
|
+
* Glyphs are always rasterized in white and tinted at runtime via
|
|
18115
|
+
* `Mesh.tint`; changing `style.fillColor` only updates the mesh tint —
|
|
18116
|
+
* no atlas re-rasterization is needed.
|
|
18117
|
+
*
|
|
18118
|
+
* The internal {@link Mesh} is the sole child of this {@link Container}.
|
|
18119
|
+
* All transform properties (position, rotation, scale, origin) are
|
|
18120
|
+
* inherited from {@link Container} → {@link RenderNode}.
|
|
18121
|
+
*/
|
|
18122
|
+
class Text extends Container {
|
|
17824
18123
|
_text;
|
|
17825
18124
|
_style;
|
|
17826
|
-
|
|
17827
|
-
|
|
17828
|
-
|
|
17829
|
-
constructor(text, style, samplerOptions, canvas = document.createElement('canvas')) {
|
|
17830
|
-
super(new Texture(canvas, samplerOptions));
|
|
18125
|
+
_mesh = null;
|
|
18126
|
+
constructor(text, style) {
|
|
18127
|
+
super();
|
|
17831
18128
|
this._text = text;
|
|
17832
18129
|
this._style = (style && style instanceof TextStyle) ? style : new TextStyle(style);
|
|
17833
|
-
this.
|
|
17834
|
-
this._context = canvas.getContext('2d');
|
|
17835
|
-
this.updateTexture();
|
|
18130
|
+
this._rebuild();
|
|
17836
18131
|
}
|
|
17837
18132
|
get text() {
|
|
17838
18133
|
return this._text;
|
|
17839
18134
|
}
|
|
17840
|
-
set text(
|
|
17841
|
-
this.setText(
|
|
18135
|
+
set text(value) {
|
|
18136
|
+
this.setText(value);
|
|
17842
18137
|
}
|
|
17843
18138
|
get style() {
|
|
17844
18139
|
return this._style;
|
|
@@ -17846,98 +18141,43 @@ class Text extends Sprite {
|
|
|
17846
18141
|
set style(style) {
|
|
17847
18142
|
this.setStyle(style);
|
|
17848
18143
|
}
|
|
17849
|
-
get canvas() {
|
|
17850
|
-
return this._canvas;
|
|
17851
|
-
}
|
|
17852
|
-
set canvas(canvas) {
|
|
17853
|
-
this.setCanvas(canvas);
|
|
17854
|
-
}
|
|
17855
18144
|
setText(text) {
|
|
17856
18145
|
if (this._text !== text) {
|
|
17857
18146
|
this._text = text;
|
|
17858
|
-
this.
|
|
18147
|
+
this._rebuild();
|
|
17859
18148
|
}
|
|
17860
18149
|
return this;
|
|
17861
18150
|
}
|
|
17862
18151
|
setStyle(style) {
|
|
17863
18152
|
this._style = (style instanceof TextStyle) ? style : new TextStyle(style);
|
|
17864
|
-
this.
|
|
17865
|
-
return this;
|
|
17866
|
-
}
|
|
17867
|
-
setCanvas(canvas) {
|
|
17868
|
-
if (this._canvas !== canvas) {
|
|
17869
|
-
this._canvas = canvas;
|
|
17870
|
-
this._context = this._getContext(canvas);
|
|
17871
|
-
this._dirty = true;
|
|
17872
|
-
this.texture.setSource.call(this.texture, canvas);
|
|
17873
|
-
this.setTextureFrame(Rectangle.temp.set(0, 0, canvas.width, canvas.height));
|
|
17874
|
-
}
|
|
18153
|
+
this._rebuild();
|
|
17875
18154
|
return this;
|
|
17876
18155
|
}
|
|
17877
|
-
|
|
17878
|
-
if (this.
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
canvas.width = canvasWidth;
|
|
17882
|
-
canvas.height = canvasHeight;
|
|
17883
|
-
this.setTextureFrame(Rectangle.temp.set(0, 0, canvasWidth, canvasHeight));
|
|
17884
|
-
}
|
|
17885
|
-
else {
|
|
17886
|
-
context.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
17887
|
-
}
|
|
17888
|
-
style.apply(context);
|
|
17889
|
-
for (let i = 0; i < lines.length; i++) {
|
|
17890
|
-
const metrics = lineMetrics[i], lineWidth = (maxLineWidth - metrics.width), offset = (style.align === 'right') ? lineWidth : lineWidth / 2, padding = style.padding + (style.strokeThickness / 2), lineX = metrics.actualBoundingBoxLeft + (style.align === 'left' ? 0 : offset) + padding, lineY = metrics.actualBoundingBoxAscent + (lineHeight * i) + padding;
|
|
17891
|
-
if (style.stroke && style.strokeThickness) {
|
|
17892
|
-
context.strokeText(lines[i], lineX, lineY);
|
|
17893
|
-
}
|
|
17894
|
-
if (style.fill) {
|
|
17895
|
-
context.fillText(lines[i], lineX, lineY);
|
|
17896
|
-
}
|
|
17897
|
-
}
|
|
17898
|
-
this.texture.updateSource();
|
|
17899
|
-
this._dirty = false;
|
|
17900
|
-
this._style.dirty = false;
|
|
17901
|
-
}
|
|
17902
|
-
return this;
|
|
17903
|
-
}
|
|
17904
|
-
getWordWrappedText() {
|
|
17905
|
-
const context = this._context, wrapWidth = this._style.wordWrapWidth, lines = this._text.split('\n'), spaceWidth = context.measureText(' ').width;
|
|
17906
|
-
let spaceLeft = wrapWidth, result = '';
|
|
17907
|
-
for (let y = 0; y < lines.length; y++) {
|
|
17908
|
-
const words = lines[y].split(' ');
|
|
17909
|
-
if (y > 0) {
|
|
17910
|
-
result += '\n';
|
|
17911
|
-
}
|
|
17912
|
-
for (let x = 0; x < words.length; x++) {
|
|
17913
|
-
const word = words[x], wordWidth = context.measureText(word).width, pairWidth = wordWidth + spaceWidth;
|
|
17914
|
-
if (pairWidth > spaceLeft) {
|
|
17915
|
-
if (x > 0) {
|
|
17916
|
-
result += '\n';
|
|
17917
|
-
}
|
|
17918
|
-
spaceLeft = wrapWidth;
|
|
17919
|
-
}
|
|
17920
|
-
else {
|
|
17921
|
-
spaceLeft -= pairWidth;
|
|
17922
|
-
}
|
|
17923
|
-
result += `${word} `;
|
|
17924
|
-
}
|
|
18156
|
+
destroy() {
|
|
18157
|
+
if (this._mesh !== null) {
|
|
18158
|
+
this._mesh.destroy();
|
|
18159
|
+
this._mesh = null;
|
|
17925
18160
|
}
|
|
17926
|
-
|
|
18161
|
+
super.destroy();
|
|
17927
18162
|
}
|
|
17928
|
-
|
|
17929
|
-
|
|
17930
|
-
|
|
17931
|
-
|
|
18163
|
+
// -----------------------------------------------------------------------
|
|
18164
|
+
_rebuild() {
|
|
18165
|
+
// Remove and discard the old mesh (if any).
|
|
18166
|
+
if (this._mesh !== null) {
|
|
18167
|
+
this.removeChild(this._mesh);
|
|
18168
|
+
this._mesh.destroy();
|
|
18169
|
+
this._mesh = null;
|
|
18170
|
+
}
|
|
18171
|
+
if (this._text.length === 0) {
|
|
18172
|
+
return;
|
|
17932
18173
|
}
|
|
17933
|
-
|
|
17934
|
-
|
|
17935
|
-
|
|
17936
|
-
|
|
17937
|
-
if (context === null) {
|
|
17938
|
-
throw new Error('Could not create a 2D canvas context.');
|
|
18174
|
+
const atlas = getDefaultGlyphAtlas();
|
|
18175
|
+
const placements = layoutText(this._text, this._style, atlas);
|
|
18176
|
+
if (placements.length === 0) {
|
|
18177
|
+
return;
|
|
17939
18178
|
}
|
|
17940
|
-
|
|
18179
|
+
this._mesh = buildMesh(placements, this._style);
|
|
18180
|
+
this.addChild(this._mesh);
|
|
17941
18181
|
}
|
|
17942
18182
|
}
|
|
17943
18183
|
|
|
@@ -18519,5 +18759,5 @@ class IndexedDbStore {
|
|
|
18519
18759
|
}
|
|
18520
18760
|
}
|
|
18521
18761
|
|
|
18522
|
-
export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, Circle, Clock, CollisionType, Color, ColorAffector, ColorFilter, Container, Drawable, Ellipse, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadChannel, GamepadControl, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, Graphics, ImageFactory, IndexedDbDatabase, IndexedDbStore, Input, InputManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, Particle, ParticleOptions, ParticleSystem, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuSpriteRenderer, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, clamp, createRenderStats, createWebGl2ShaderProgram, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, lerp, matchesIds, maxPointers, milliseconds, minutes, noop$1 as noop, normalizeIds, onAudioContextReady, parseGamepadDescriptor, pointerSlotSize, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign$1 as sign, stopEvent, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, tau, trimRotation, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
|
|
18762
|
+
export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, Circle, Clock, CollisionType, Color, ColorAffector, ColorFilter, Container, Drawable, DynamicGlyphAtlas, Ellipse, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadChannel, GamepadControl, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, Graphics, ImageFactory, IndexedDbDatabase, IndexedDbStore, Input, InputManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, Particle, ParticleOptions, ParticleSystem, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuSpriteRenderer, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, clamp, createRenderStats, createWebGl2ShaderProgram, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, layoutText, lerp, matchesIds, maxPointers, milliseconds, minutes, noop$1 as noop, normalizeIds, onAudioContextReady, parseGamepadDescriptor, pointerSlotSize, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign$1 as sign, stopEvent, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, tau, trimRotation, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
|
|
18523
18763
|
//# sourceMappingURL=exo.esm.js.map
|