@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.
- package/dist/base_types.js +602 -0
- package/dist/base_types.js.map +1 -0
- package/dist/builder/ast.js +2135 -0
- package/dist/builder/ast.js.map +1 -0
- package/dist/builder/base.js +477 -0
- package/dist/builder/base.js.map +1 -0
- package/dist/builder/builtinfunc.js +4101 -0
- package/dist/builder/builtinfunc.js.map +1 -0
- package/dist/builder/constructors.js +173 -0
- package/dist/builder/constructors.js.map +1 -0
- package/dist/builder/errors.js +148 -0
- package/dist/builder/errors.js.map +1 -0
- package/dist/builder/programbuilder.js +2323 -0
- package/dist/builder/programbuilder.js.map +1 -0
- package/dist/builder/reflection.js +60 -0
- package/dist/builder/reflection.js.map +1 -0
- package/dist/builder/types.js +1563 -0
- package/dist/builder/types.js.map +1 -0
- package/dist/device.js +515 -0
- package/dist/device.js.map +1 -0
- package/dist/gpuobject.js +2047 -0
- package/dist/gpuobject.js.map +1 -0
- package/dist/helpers/drawtext.js +187 -0
- package/dist/helpers/drawtext.js.map +1 -0
- package/dist/helpers/font.js +189 -0
- package/dist/helpers/font.js.map +1 -0
- package/dist/helpers/glyphmanager.js +121 -0
- package/dist/helpers/glyphmanager.js.map +1 -0
- package/dist/helpers/textureatlas.js +170 -0
- package/dist/helpers/textureatlas.js.map +1 -0
- package/dist/index.d.ts +3873 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/timer.js +38 -0
- package/dist/timer.js.map +1 -0
- package/dist/uniformdata.js +147 -0
- package/dist/uniformdata.js.map +1 -0
- package/dist/vertexdata.js +135 -0
- package/dist/vertexdata.js.map +1 -0
- 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|