@zephyr3d/device 0.1.0

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.
Files changed (40) hide show
  1. package/dist/base_types.js +602 -0
  2. package/dist/base_types.js.map +1 -0
  3. package/dist/builder/ast.js +2135 -0
  4. package/dist/builder/ast.js.map +1 -0
  5. package/dist/builder/base.js +477 -0
  6. package/dist/builder/base.js.map +1 -0
  7. package/dist/builder/builtinfunc.js +4101 -0
  8. package/dist/builder/builtinfunc.js.map +1 -0
  9. package/dist/builder/constructors.js +173 -0
  10. package/dist/builder/constructors.js.map +1 -0
  11. package/dist/builder/errors.js +148 -0
  12. package/dist/builder/errors.js.map +1 -0
  13. package/dist/builder/programbuilder.js +2323 -0
  14. package/dist/builder/programbuilder.js.map +1 -0
  15. package/dist/builder/reflection.js +60 -0
  16. package/dist/builder/reflection.js.map +1 -0
  17. package/dist/builder/types.js +1563 -0
  18. package/dist/builder/types.js.map +1 -0
  19. package/dist/device.js +515 -0
  20. package/dist/device.js.map +1 -0
  21. package/dist/gpuobject.js +2047 -0
  22. package/dist/gpuobject.js.map +1 -0
  23. package/dist/helpers/drawtext.js +187 -0
  24. package/dist/helpers/drawtext.js.map +1 -0
  25. package/dist/helpers/font.js +189 -0
  26. package/dist/helpers/font.js.map +1 -0
  27. package/dist/helpers/glyphmanager.js +121 -0
  28. package/dist/helpers/glyphmanager.js.map +1 -0
  29. package/dist/helpers/textureatlas.js +170 -0
  30. package/dist/helpers/textureatlas.js.map +1 -0
  31. package/dist/index.d.ts +3873 -0
  32. package/dist/index.js +16 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/timer.js +38 -0
  35. package/dist/timer.js.map +1 -0
  36. package/dist/uniformdata.js +147 -0
  37. package/dist/uniformdata.js.map +1 -0
  38. package/dist/vertexdata.js +135 -0
  39. package/dist/vertexdata.js.map +1 -0
  40. package/package.json +69 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpuobject.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,187 @@
1
+ import { Matrix4x4, Vector4, Vector3, parseColor } from '@zephyr3d/base';
2
+ import { Font } from './font.js';
3
+ import { GlyphManager } from './glyphmanager.js';
4
+
5
+ const MAX_GLYPH_COUNT = 1024;
6
+ /**
7
+ * Helper class to draw some text onto the screen
8
+ * @public
9
+ */ class DrawText {
10
+ /** @internal */ static GLYPH_COUNT = MAX_GLYPH_COUNT;
11
+ /** @internal */ static glyphManager = null;
12
+ /** @internal */ static prepared = false;
13
+ /** @internal */ static textVertexBuffer = null;
14
+ /** @internal */ static textVertexLayout = null;
15
+ /** @internal */ static textProgram = null;
16
+ /** @internal */ static textBindGroup = null;
17
+ /** @internal */ static textRenderStates = null;
18
+ /** @internal */ static textOffset = 0;
19
+ /** @internal */ static textMatrix = new Matrix4x4();
20
+ /** @internal */ static font = null;
21
+ /** @internal */ static vertexCache = null;
22
+ /** @internal */ static colorValue = new Vector4();
23
+ /** @internal */ static calculateTextMatrix(device, matrix) {
24
+ const viewport = device.getViewport();
25
+ const projectionMatrix = Matrix4x4.ortho(0, viewport.width, 0, viewport.height, 1, 100);
26
+ const flipMatrix = Matrix4x4.translation(new Vector3(0, viewport.height, 0)).scaleRight(new Vector3(1, -1, 1));
27
+ Matrix4x4.multiply(projectionMatrix, flipMatrix, matrix);
28
+ }
29
+ /**
30
+ * Set the font that will be used to draw strings
31
+ * @param device - The render device
32
+ * @param name - The font name
33
+ */ static setFont(device, name) {
34
+ this.font = Font.fetchFont(name, device.getScale()) || Font.fetchFont('12px arial', device.getScale());
35
+ }
36
+ /**
37
+ * Draw text onto the screen
38
+ * @param device - The render device
39
+ * @param text - The text to be drawn
40
+ * @param color - The text color
41
+ * @param x - X coordinate of the text
42
+ * @param y - Y coordinate of the text
43
+ */ static drawText(device, text, color, x, y) {
44
+ if (text.length > 0) {
45
+ device.pushDeviceStates();
46
+ this.prepareDrawText(device);
47
+ this.calculateTextMatrix(device, this.textMatrix);
48
+ const colorValue = parseColor(color);
49
+ this.colorValue.x = colorValue.r;
50
+ this.colorValue.y = colorValue.g;
51
+ this.colorValue.z = colorValue.b;
52
+ this.colorValue.w = colorValue.a;
53
+ this.textBindGroup.setValue('flip', device.type === 'webgpu' && device.getFramebuffer() ? 1 : 0);
54
+ this.textBindGroup.setValue('srgbOut', device.getFramebuffer() ? 0 : 1);
55
+ this.textBindGroup.setValue('textMatrix', this.textMatrix);
56
+ this.textBindGroup.setValue('textColor', this.colorValue);
57
+ device.setProgram(this.textProgram);
58
+ device.setVertexLayout(this.textVertexLayout);
59
+ device.setRenderStates(this.textRenderStates);
60
+ device.setBindGroup(0, this.textBindGroup);
61
+ let drawn = 0;
62
+ const total = text.length;
63
+ while(drawn < total){
64
+ const count = Math.min(total - drawn, this.GLYPH_COUNT - this.textOffset);
65
+ if (count > 0) {
66
+ x = this.drawTextNoOverflow(device, text, drawn, count, x, y);
67
+ drawn += count;
68
+ this.textOffset += count;
69
+ }
70
+ if (this.GLYPH_COUNT === this.textOffset) {
71
+ this.textOffset = 0;
72
+ device.flush();
73
+ }
74
+ }
75
+ device.popDeviceStates();
76
+ }
77
+ }
78
+ /** @internal */ static drawTextNoOverflow(device, text, start, count, x, y) {
79
+ let drawn = 0;
80
+ let atlasIndex = -1;
81
+ let i = 0;
82
+ for(; i < count; i++){
83
+ const glyph = this.glyphManager.getGlyphInfo(text[i + start], this.font) || this.glyphManager.getGlyphInfo('?', this.font);
84
+ if (atlasIndex >= 0 && glyph.atlasIndex !== atlasIndex) {
85
+ this.textVertexBuffer.bufferSubData((this.textOffset + drawn) * 16 * 4, this.vertexCache, (this.textOffset + drawn) * 16, (i - drawn) * 16);
86
+ this.textBindGroup.setTexture('tex', this.glyphManager.getAtlasTexture(atlasIndex));
87
+ device.draw('triangle-list', (this.textOffset + drawn) * 6, (i - drawn) * 6);
88
+ drawn = i;
89
+ }
90
+ atlasIndex = glyph.atlasIndex;
91
+ const base = (this.textOffset + i) * 16;
92
+ this.vertexCache[base + 0] = x;
93
+ this.vertexCache[base + 1] = y;
94
+ this.vertexCache[base + 2] = glyph.uMin;
95
+ this.vertexCache[base + 3] = glyph.vMin;
96
+ this.vertexCache[base + 4] = x + glyph.width;
97
+ this.vertexCache[base + 5] = y;
98
+ this.vertexCache[base + 6] = glyph.uMax;
99
+ this.vertexCache[base + 7] = glyph.vMin;
100
+ this.vertexCache[base + 8] = x + glyph.width;
101
+ this.vertexCache[base + 9] = y + glyph.height;
102
+ this.vertexCache[base + 10] = glyph.uMax;
103
+ this.vertexCache[base + 11] = glyph.vMax;
104
+ this.vertexCache[base + 12] = x;
105
+ this.vertexCache[base + 13] = y + glyph.height;
106
+ this.vertexCache[base + 14] = glyph.uMin;
107
+ this.vertexCache[base + 15] = glyph.vMax;
108
+ x += glyph.width;
109
+ }
110
+ this.textVertexBuffer.bufferSubData((this.textOffset + drawn) * 16 * 4, this.vertexCache, (this.textOffset + drawn) * 16, (i - drawn) * 16);
111
+ this.textBindGroup.setTexture('tex', this.glyphManager.getAtlasTexture(atlasIndex));
112
+ device.draw('triangle-list', (this.textOffset + drawn) * 6, (i - drawn) * 6);
113
+ return x;
114
+ }
115
+ /** @internal */ static prepareDrawText(device) {
116
+ if (!this.prepared) {
117
+ this.prepared = true;
118
+ this.font = this.font || Font.fetchFont('16px arial', device.getScale());
119
+ this.glyphManager = new GlyphManager(device, 1024, 1024, 1);
120
+ this.vertexCache = new Float32Array(this.GLYPH_COUNT * 16);
121
+ this.textVertexBuffer = device.createInterleavedVertexBuffer([
122
+ 'position_f32x2',
123
+ 'tex0_f32x2'
124
+ ], this.vertexCache, {
125
+ dynamic: true
126
+ });
127
+ const indices = new Uint16Array(this.GLYPH_COUNT * 6);
128
+ for(let i = 0; i < this.GLYPH_COUNT; i++){
129
+ const base = i * 4;
130
+ indices[i * 6 + 0] = base + 0;
131
+ indices[i * 6 + 1] = base + 1;
132
+ indices[i * 6 + 2] = base + 2;
133
+ indices[i * 6 + 3] = base + 0;
134
+ indices[i * 6 + 4] = base + 2;
135
+ indices[i * 6 + 5] = base + 3;
136
+ }
137
+ const textIndexBuffer = device.createIndexBuffer(indices);
138
+ this.textVertexLayout = device.createVertexLayout({
139
+ vertexBuffers: [
140
+ {
141
+ buffer: this.textVertexBuffer
142
+ }
143
+ ],
144
+ indexBuffer: textIndexBuffer
145
+ });
146
+ this.textOffset = 0;
147
+ this.textProgram = device.buildRenderProgram({
148
+ vertex (pb) {
149
+ this.$inputs.pos = pb.vec2().attrib('position');
150
+ this.$inputs.uv = pb.vec2().attrib('texCoord0');
151
+ this.$outputs.uv = pb.vec2();
152
+ this.flip = pb.int(0).uniform(0);
153
+ this.textMatrix = pb.mat4().uniform(0);
154
+ pb.main(function() {
155
+ this.$builtins.position = pb.mul(this.textMatrix, pb.vec4(this.$inputs.pos, -50, 1));
156
+ this.$if(pb.notEqual(this.flip, 0), function() {
157
+ this.$builtins.position.y = pb.neg(this.$builtins.position.y);
158
+ });
159
+ this.$outputs.uv = this.$inputs.uv;
160
+ });
161
+ },
162
+ fragment (pb) {
163
+ this.$outputs.color = pb.vec4();
164
+ this.textColor = pb.vec4().uniform(0);
165
+ this.tex = pb.tex2D().uniform(0);
166
+ this.srgbOut = pb.int().uniform(0);
167
+ pb.main(function() {
168
+ this.alpha = pb.mul(pb.textureSample(this.tex, this.$inputs.uv).a, this.textColor.a);
169
+ this.$if(pb.notEqual(this.srgbOut, 0), function() {
170
+ this.$outputs.color = pb.vec4(pb.mul(pb.pow(this.textColor.rgb, pb.vec3(1 / 2.2)), this.alpha), this.alpha);
171
+ }).$else(function() {
172
+ this.$outputs.color = pb.vec4(pb.mul(this.textColor.rgb, this.alpha), this.alpha);
173
+ });
174
+ });
175
+ }
176
+ });
177
+ this.textBindGroup = device.createBindGroup(this.textProgram.bindGroupLayouts[0]);
178
+ this.textRenderStates = device.createRenderStateSet();
179
+ this.textRenderStates.useBlendingState().enable(true).setBlendFuncRGB('one', 'inv-src-alpha').setBlendFuncAlpha('zero', 'one');
180
+ this.textRenderStates.useDepthState().enableTest(false).enableWrite(false);
181
+ this.textRenderStates.useRasterizerState().setCullMode('none');
182
+ }
183
+ }
184
+ }
185
+
186
+ export { DrawText };
187
+ //# sourceMappingURL=drawtext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drawtext.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,189 @@
1
+ /** @internal */ class FontCanvas {
2
+ static _canvas = null;
3
+ static _context = null;
4
+ static get canvas() {
5
+ this._realize();
6
+ return this._canvas;
7
+ }
8
+ static get context() {
9
+ this._realize();
10
+ return this._context;
11
+ }
12
+ static get font() {
13
+ return this.context.font;
14
+ }
15
+ static set font(font) {
16
+ this.context.font = font;
17
+ }
18
+ static _realize() {
19
+ if (!this._canvas) {
20
+ this._canvas = document.createElement('canvas');
21
+ this._canvas.width = 512;
22
+ this._canvas.height = 512;
23
+ this._canvas.style.left = '-10000px';
24
+ this._canvas.style.position = 'absolute';
25
+ //document.body.appendChild(this._canvas);
26
+ this._context = this._canvas.getContext('2d', {
27
+ willReadFrequently: true
28
+ });
29
+ this._context.textBaseline = 'top';
30
+ this._context.textAlign = 'left';
31
+ this._context.fillStyle = 'transparent';
32
+ this._context.fillRect(0, 0, this._canvas.width, this._canvas.height);
33
+ this._context.fillStyle = '#ffffff';
34
+ this._context.imageSmoothingEnabled = true;
35
+ }
36
+ }
37
+ }
38
+ /**
39
+ * The font class
40
+ * @public
41
+ */ class Font {
42
+ /** @internal */ static fontCache = {};
43
+ /** @internal */ _name;
44
+ /** @internal */ _nameScaled;
45
+ /** @internal */ _scale;
46
+ /** @internal */ _size;
47
+ /** @internal */ _family;
48
+ /** @internal */ _top;
49
+ /** @internal */ _bottom;
50
+ /** @internal */ _topScaled;
51
+ /** @internal */ _bottomScaled;
52
+ /** @internal */ _div;
53
+ /**
54
+ * Creates a instance of font class from font name and the scale value
55
+ * @param name - The font name
56
+ * @param scale - The scale value
57
+ */ constructor(name, scale){
58
+ this._top = 0;
59
+ this._bottom = 0;
60
+ this._size = 0;
61
+ this._topScaled = 0;
62
+ this._bottomScaled = 0;
63
+ this._family = '';
64
+ this._scale = scale;
65
+ this._name = name;
66
+ this._nameScaled = null;
67
+ this._div = document.createElement('div');
68
+ if (this._name) {
69
+ this._normalizeFont();
70
+ }
71
+ }
72
+ /**
73
+ * Fetch a font from cache
74
+ * @param name - The font name
75
+ * @param scale - The scale value
76
+ * @returns The font object
77
+ */ static fetchFont(name, scale) {
78
+ let fontlist = this.fontCache[name];
79
+ if (!fontlist) {
80
+ fontlist = {};
81
+ this.fontCache[name] = fontlist;
82
+ }
83
+ let font = fontlist[scale];
84
+ if (!font) {
85
+ font = new Font(name, scale);
86
+ fontlist[scale] = font;
87
+ }
88
+ return font;
89
+ }
90
+ /** Gets the font name */ get fontName() {
91
+ return this._name;
92
+ }
93
+ set fontName(name) {
94
+ this._name = name;
95
+ this._normalizeFont();
96
+ }
97
+ /** Gets the scaled font name */ get fontNameScaled() {
98
+ return this._nameScaled;
99
+ }
100
+ /** Gets the font size */ get size() {
101
+ return this._size;
102
+ }
103
+ /** Gets the font family */ get family() {
104
+ return this._family;
105
+ }
106
+ /** Gets top position of the font */ get top() {
107
+ return this._top;
108
+ }
109
+ /** Gets the bottom position of the font */ get bottom() {
110
+ return this._bottom;
111
+ }
112
+ /** Gets the scaled top position of the font */ get topScaled() {
113
+ return this._topScaled;
114
+ }
115
+ /** Gets the scaled bottom position of the font */ get bottomScaled() {
116
+ return this._bottomScaled;
117
+ }
118
+ /** Gets the maximum height of the font */ get maxHeight() {
119
+ return this._bottom - this._top + 1;
120
+ }
121
+ /** Gets the scaled maximum height of the font */ get maxHeightScaled() {
122
+ return this._bottomScaled - this._topScaled + 1;
123
+ }
124
+ /** Tests if two fonts are the same */ equalTo(other) {
125
+ return this._size === other._size && this._family === other._family;
126
+ }
127
+ /** @internal */ _measureFontHeight(fontName) {
128
+ const oldFont = FontCanvas.context.font;
129
+ const oldTextBaseline = FontCanvas.context.textBaseline;
130
+ const oldFillStyle = FontCanvas.context.fillStyle;
131
+ FontCanvas.context.font = fontName;
132
+ this._div.style.font = FontCanvas.context.font;
133
+ const fontSize = this._div.style.fontSize;
134
+ const size = parseInt(fontSize.substring(0, fontSize.length - 2));
135
+ const family = this._div.style.fontFamily;
136
+ const testString = 'bdfghijklpq国美|_~';
137
+ const metric = FontCanvas.context.measureText(testString);
138
+ let top, bottom;
139
+ top = 0;
140
+ bottom = size - 1;
141
+ const extra = 10;
142
+ const halfExtra = extra >> 1;
143
+ const maxWidth = Math.ceil(metric.width) + extra;
144
+ const maxHeight = size + extra;
145
+ FontCanvas.context.clearRect(0, 0, maxWidth, maxHeight);
146
+ FontCanvas.context.textBaseline = 'top';
147
+ FontCanvas.context.fillStyle = '#ffffff';
148
+ FontCanvas.context.fillText(testString, halfExtra, halfExtra);
149
+ const bitmap = FontCanvas.context.getImageData(0, 0, maxWidth, maxHeight);
150
+ const pixels = bitmap.data;
151
+ for(let i = 0; i < maxWidth * maxHeight; i++){
152
+ if (pixels[i * 4 + 3] > 0) {
153
+ top = Math.floor(i / maxWidth);
154
+ break;
155
+ }
156
+ }
157
+ for(let i = maxWidth * maxHeight - 1; i >= 0; i--){
158
+ if (pixels[i * 4 + 3] > 0) {
159
+ bottom = Math.floor(i / maxWidth);
160
+ break;
161
+ }
162
+ }
163
+ top -= halfExtra;
164
+ bottom -= halfExtra;
165
+ FontCanvas.context.font = oldFont;
166
+ FontCanvas.context.textBaseline = oldTextBaseline;
167
+ FontCanvas.context.fillStyle = oldFillStyle;
168
+ return {
169
+ size,
170
+ family,
171
+ top,
172
+ bottom
173
+ };
174
+ }
175
+ /** @internal */ _normalizeFont() {
176
+ const info = this._measureFontHeight(this._name);
177
+ this._nameScaled = `${Math.round(info.size * this._scale)}px ${info.family}`;
178
+ const infoScaled = this._measureFontHeight(this._nameScaled);
179
+ this._size = info.size;
180
+ this._family = info.family;
181
+ this._top = info.top;
182
+ this._bottom = info.bottom;
183
+ this._topScaled = infoScaled.top;
184
+ this._bottomScaled = infoScaled.bottom;
185
+ }
186
+ }
187
+
188
+ export { Font, FontCanvas };
189
+ //# sourceMappingURL=font.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"font.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,121 @@
1
+ import { FontCanvas } from './font.js';
2
+ import { TextureAtlasManager } from './textureatlas.js';
3
+
4
+ /**
5
+ * Manager of texture glyphs
6
+ * @public
7
+ */ class GlyphManager extends TextureAtlasManager {
8
+ /**
9
+ * Creates a new glyph manager instance
10
+ * @param device - The render device
11
+ * @param binWidth - Width of an atlas bin
12
+ * @param binHeight - Height of an atlas bin
13
+ * @param border - Border width of an atlas
14
+ */ constructor(device, binWidth, binHeight, border){
15
+ super(device, binWidth, binHeight, border, true);
16
+ this.atlasTextureRestoreHandler = async ()=>{
17
+ if (!this.isEmpty()) {
18
+ this.clear();
19
+ }
20
+ };
21
+ }
22
+ /**
23
+ * Gets the atlas information for given character
24
+ * @param char - The character
25
+ * @param font - Font of the character
26
+ * @returns Atlas information for the glyph
27
+ */ getGlyphInfo(char, font) {
28
+ if (!char || !font) {
29
+ return null;
30
+ }
31
+ let glyphInfo = this.getAtlasInfo(this._hash(char, font));
32
+ if (!glyphInfo) {
33
+ glyphInfo = this._cacheGlyph(char, font);
34
+ glyphInfo.width = Math.round(glyphInfo.width * (font.maxHeight / font.maxHeightScaled));
35
+ glyphInfo.height = font.maxHeight;
36
+ }
37
+ return glyphInfo;
38
+ }
39
+ /**
40
+ * Measuring the width of a string
41
+ * @param str - The string to be measured
42
+ * @param charMargin - margin size between characters
43
+ * @param font - Font of the string
44
+ * @returns Width of the string
45
+ */ measureStringWidth(str, charMargin, font) {
46
+ let w = 0;
47
+ for (const ch of str){
48
+ w += charMargin + this.getCharWidth(ch, font);
49
+ }
50
+ return w;
51
+ }
52
+ /**
53
+ * Clips a string so that it's width is not larger than the given value
54
+ * @param str - The string to be clipped
55
+ * @param width - The desired maximum width
56
+ * @param charMargin - Margin size between characters
57
+ * @param start - Start index of the string to be clipped
58
+ * @param font - Font of the string
59
+ * @returns
60
+ */ clipStringToWidth(str, width, charMargin, start, font) {
61
+ let sum = 0;
62
+ let i = start;
63
+ for(; i < str.length; i++){
64
+ sum += charMargin + this.getCharWidth(str[i], font);
65
+ if (sum > width) {
66
+ break;
67
+ }
68
+ }
69
+ return i - start;
70
+ }
71
+ /** @internal */ _hash(char, font) {
72
+ return `${font.family}@${font.size}&${char}`;
73
+ }
74
+ /** @internal */ _cacheGlyph(char, font) {
75
+ const bitmap = this._getGlyphBitmap(char, font);
76
+ return this.pushBitmap(this._hash(char, font), bitmap);
77
+ }
78
+ /**
79
+ * Measuring width of a character
80
+ * @param char - The character to be measured
81
+ * @param font - Font of the character
82
+ * @returns Width of the character
83
+ */ getCharWidth(char, font) {
84
+ if (!font) {
85
+ return 0;
86
+ }
87
+ FontCanvas.font = font.fontNameScaled;
88
+ const metric = FontCanvas.context.measureText(char);
89
+ let w = metric.width;
90
+ if (w === 0) {
91
+ return 0;
92
+ }
93
+ if (typeof metric.actualBoundingBoxRight === 'number') {
94
+ w = Math.floor(Math.max(w, metric.actualBoundingBoxRight) + 0.8);
95
+ }
96
+ w = Math.round(w * (font.maxHeight / font.maxHeightScaled));
97
+ return w;
98
+ }
99
+ /** @internal */ _getGlyphBitmap(char, font) {
100
+ if (!font) {
101
+ return null;
102
+ }
103
+ FontCanvas.font = font.fontNameScaled;
104
+ const metric = FontCanvas.context.measureText(char);
105
+ let w = metric.width;
106
+ if (w === 0) {
107
+ return null;
108
+ }
109
+ if (typeof metric.actualBoundingBoxRight === 'number') {
110
+ w = Math.floor(Math.max(w, metric.actualBoundingBoxRight) + 0.8);
111
+ }
112
+ const h = font.maxHeightScaled;
113
+ FontCanvas.context.fillStyle = '#fff';
114
+ FontCanvas.context.clearRect(0, 0, w + 2, h);
115
+ FontCanvas.context.fillText(char, 0, -font.topScaled);
116
+ return FontCanvas.context.getImageData(0, 0, w, h);
117
+ }
118
+ }
119
+
120
+ export { GlyphManager };
121
+ //# sourceMappingURL=glyphmanager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glyphmanager.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,170 @@
1
+ import { RectsPacker } from '@zephyr3d/base';
2
+
3
+ /**
4
+ * Texture atlas manager
5
+ * @public
6
+ */ class TextureAtlasManager {
7
+ /** @internal */ static ATLAS_WIDTH = 1024;
8
+ /** @internal */ static ATLAS_HEIGHT = 1024;
9
+ /** @internal */ _packer;
10
+ /** @internal */ _device;
11
+ /** @internal */ _binWidth;
12
+ /** @internal */ _binHeight;
13
+ /** @internal */ _rectBorderWidth;
14
+ /** @internal */ _linearSpace;
15
+ /** @internal */ _atlasList;
16
+ /** @internal */ _atlasInfoMap;
17
+ /** @internal */ _atlasRestoreHandler;
18
+ /**
19
+ * Creates a new texture atlas manager instance
20
+ * @param device - The render device
21
+ * @param binWidth - Width of an atlas bin
22
+ * @param binHeight - Height of an atlas bin
23
+ * @param rectBorderWidth - Border width of an atlas
24
+ * @param linearSpace - true if the texture space is linear
25
+ */ constructor(device, binWidth, binHeight, rectBorderWidth, linearSpace){
26
+ this._device = device;
27
+ this._binWidth = binWidth;
28
+ this._binHeight = binHeight;
29
+ this._rectBorderWidth = rectBorderWidth;
30
+ this._linearSpace = !!linearSpace;
31
+ this._packer = new RectsPacker(this._binWidth, this._binHeight);
32
+ this._atlasList = [];
33
+ this._atlasInfoMap = {};
34
+ this._atlasRestoreHandler = null;
35
+ }
36
+ /**
37
+ * The texture restore handler callback function
38
+ * This callback function will be called whenever the device has been restored
39
+ */ get atlasTextureRestoreHandler() {
40
+ return this._atlasRestoreHandler;
41
+ }
42
+ set atlasTextureRestoreHandler(f) {
43
+ this._atlasRestoreHandler = f;
44
+ }
45
+ /**
46
+ * Gets the atlas texture of a given index
47
+ * @param index - Index of the atlas bin
48
+ * @returns Atlas texture for given index
49
+ */ getAtlasTexture(index) {
50
+ return this._atlasList[index];
51
+ }
52
+ /**
53
+ * Gets the information about specified atlas
54
+ * @param key - Key of the atlas
55
+ * @returns Information of the atlas
56
+ */ getAtlasInfo(key) {
57
+ return this._atlasInfoMap[key] || null;
58
+ }
59
+ /**
60
+ * Check if no atlas has been created
61
+ * @returns true if no atlas has been created
62
+ */ isEmpty() {
63
+ return this._atlasList.length === 0;
64
+ }
65
+ /**
66
+ * Removes all created atlases
67
+ */ clear() {
68
+ this._packer.clear();
69
+ for (const tex of this._atlasList){
70
+ tex.dispose();
71
+ }
72
+ this._atlasList = [];
73
+ this._atlasInfoMap = {};
74
+ }
75
+ /**
76
+ * Inserts a rectangle of a canvas to the atlas texture
77
+ * @param key - Key of the atlas
78
+ * @param ctx - The canvas context
79
+ * @param x - x offset of the rectangle
80
+ * @param y - y offset of the rectangle
81
+ * @param w - width of the rectangle
82
+ * @param h - height of the rectangle
83
+ * @returns The atals info or null if insert failed
84
+ */ pushCanvas(key, ctx, x, y, w, h) {
85
+ const rc = this._packer.insert(w + 2 * this._rectBorderWidth, h + 2 * this._rectBorderWidth);
86
+ if (rc) {
87
+ const atlasX = rc.x + this._rectBorderWidth;
88
+ const atlasY = rc.y + this._rectBorderWidth;
89
+ this._updateAtlasTextureCanvas(rc.binIndex, ctx, atlasX, atlasY, w, h, x, y);
90
+ const info = {
91
+ atlasIndex: rc.binIndex,
92
+ uMin: atlasX / this._binWidth,
93
+ vMin: atlasY / this._binHeight,
94
+ uMax: (atlasX + w) / this._binWidth,
95
+ vMax: (atlasY + h) / this._binHeight,
96
+ width: w,
97
+ height: h
98
+ };
99
+ this._atlasInfoMap[key] = info;
100
+ return info;
101
+ }
102
+ return null;
103
+ }
104
+ /**
105
+ * Inserts a bitmap to the atlas texture
106
+ * @param key - Key of the atlas
107
+ * @param bitmap - The bitmap object
108
+ * @returns The atals info or null if insert failed
109
+ */ pushBitmap(key, bitmap) {
110
+ const rc = this._packer.insert(bitmap.width + 2 * this._rectBorderWidth, bitmap.height + 2 * this._rectBorderWidth);
111
+ if (rc) {
112
+ const atlasX = rc.x + this._rectBorderWidth;
113
+ const atlasY = rc.y + this._rectBorderWidth;
114
+ this._updateAtlasTexture(rc.binIndex, bitmap, atlasX, atlasY);
115
+ const info = {
116
+ atlasIndex: rc.binIndex,
117
+ uMin: atlasX / this._binWidth,
118
+ vMin: atlasY / this._binHeight,
119
+ uMax: (atlasX + bitmap.width) / this._binWidth,
120
+ vMax: (atlasY + bitmap.height) / this._binHeight,
121
+ width: bitmap.width,
122
+ height: bitmap.height
123
+ };
124
+ this._atlasInfoMap[key] = info;
125
+ return info;
126
+ }
127
+ return null;
128
+ }
129
+ /** @internal */ _createAtlasTexture() {
130
+ const tex = this._device.createTexture2D('rgba8unorm', this._binWidth, this._binHeight, {
131
+ samplerOptions: {
132
+ mipFilter: 'none'
133
+ }
134
+ });
135
+ tex.update(new Uint8Array(tex.width * tex.height * 4), 0, 0, tex.width, tex.height);
136
+ tex.restoreHandler = async ()=>{
137
+ tex.update(new Uint8Array(tex.width * tex.height * 4), 0, 0, tex.width, tex.height);
138
+ this._atlasRestoreHandler && await this._atlasRestoreHandler(tex);
139
+ };
140
+ return tex;
141
+ }
142
+ /** @internal */ _updateAtlasTextureCanvas(atlasIndex, ctx, x, y, w, h, xOffset, yOffset) {
143
+ let textureAtlas = null;
144
+ if (atlasIndex === this._atlasList.length) {
145
+ textureAtlas = this._createAtlasTexture();
146
+ this._atlasList.push(textureAtlas);
147
+ } else {
148
+ textureAtlas = this._atlasList[atlasIndex];
149
+ }
150
+ textureAtlas.updateFromElement(ctx.canvas, x, y, xOffset, yOffset, w, h);
151
+ }
152
+ /** @internal */ _updateAtlasTexture(atlasIndex, bitmap, x, y) {
153
+ let textureAtlas = null;
154
+ if (atlasIndex === this._atlasList.length) {
155
+ textureAtlas = this._createAtlasTexture();
156
+ this._atlasList.push(textureAtlas);
157
+ } else {
158
+ textureAtlas = this._atlasList[atlasIndex];
159
+ }
160
+ if (bitmap instanceof ImageBitmap) {
161
+ textureAtlas.updateFromElement(bitmap, x, y, 0, 0, bitmap.width, bitmap.height);
162
+ } else {
163
+ const originValues = new Uint8Array(bitmap.data.buffer);
164
+ textureAtlas.update(originValues, x, y, bitmap.width, bitmap.height);
165
+ }
166
+ }
167
+ }
168
+
169
+ export { TextureAtlasManager };
170
+ //# sourceMappingURL=textureatlas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textureatlas.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}