q5 2.14.3 → 2.14.5
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/README.md +1 -1
- package/package.json +1 -1
- package/q5.js +190 -75
- package/q5.min.js +1 -1
- package/q5js_brand.webp +0 -0
- package/q5js_icon.png +0 -0
- package/src/q5-2d-canvas.js +0 -202
- package/src/q5-2d-drawing.js +0 -398
- package/src/q5-2d-image.js +0 -330
- package/src/q5-2d-soft-filters.js +0 -145
- package/src/q5-2d-text.js +0 -279
- package/src/q5-ai.js +0 -65
- package/src/q5-canvas.js +0 -367
- package/src/q5-color.js +0 -322
- package/src/q5-core.js +0 -321
- package/src/q5-display.js +0 -101
- package/src/q5-dom.js +0 -2
- package/src/q5-input.js +0 -215
- package/src/q5-math.js +0 -423
- package/src/q5-noisier.js +0 -264
- package/src/q5-record.js +0 -366
- package/src/q5-sensors.js +0 -98
- package/src/q5-sound.js +0 -64
- package/src/q5-util.js +0 -50
- package/src/q5-vector.js +0 -305
- package/src/q5-webgpu-canvas.js +0 -556
- package/src/q5-webgpu-drawing.js +0 -525
- package/src/q5-webgpu-image.js +0 -268
- package/src/q5-webgpu-text.js +0 -594
- package/src/readme.md +0 -248
package/src/q5-webgpu-text.js
DELETED
|
@@ -1,594 +0,0 @@
|
|
|
1
|
-
Q5.renderers.webgpu.text = ($, q) => {
|
|
2
|
-
let textShader = Q5.device.createShaderModule({
|
|
3
|
-
label: 'MSDF text shader',
|
|
4
|
-
code: `
|
|
5
|
-
struct Uniforms {
|
|
6
|
-
halfWidth: f32,
|
|
7
|
-
halfHeight: f32
|
|
8
|
-
}
|
|
9
|
-
struct VertexParams {
|
|
10
|
-
@builtin(vertex_index) vertex : u32,
|
|
11
|
-
@builtin(instance_index) instance : u32
|
|
12
|
-
}
|
|
13
|
-
struct FragmentParams {
|
|
14
|
-
@builtin(position) position : vec4f,
|
|
15
|
-
@location(0) texCoord : vec2f,
|
|
16
|
-
@location(1) fillColor : vec4f
|
|
17
|
-
}
|
|
18
|
-
struct Char {
|
|
19
|
-
texOffset: vec2f,
|
|
20
|
-
texExtent: vec2f,
|
|
21
|
-
size: vec2f,
|
|
22
|
-
offset: vec2f,
|
|
23
|
-
}
|
|
24
|
-
struct Text {
|
|
25
|
-
pos: vec2f,
|
|
26
|
-
scale: f32,
|
|
27
|
-
matrixIndex: f32,
|
|
28
|
-
fillIndex: f32,
|
|
29
|
-
strokeIndex: f32
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
33
|
-
@group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
|
|
34
|
-
@group(0) @binding(2) var<storage> colors : array<vec4f>;
|
|
35
|
-
|
|
36
|
-
@group(1) @binding(0) var fontTexture: texture_2d<f32>;
|
|
37
|
-
@group(1) @binding(1) var fontSampler: sampler;
|
|
38
|
-
@group(1) @binding(2) var<storage> fontChars: array<Char>;
|
|
39
|
-
|
|
40
|
-
@group(2) @binding(0) var<storage> textChars: array<vec4f>;
|
|
41
|
-
@group(2) @binding(1) var<storage> textMetadata: array<Text>;
|
|
42
|
-
|
|
43
|
-
const quad = array(vec2f(0, -1), vec2f(1, -1), vec2f(0, 0), vec2f(1, 0));
|
|
44
|
-
|
|
45
|
-
@vertex
|
|
46
|
-
fn vertexMain(v : VertexParams) -> FragmentParams {
|
|
47
|
-
let char = textChars[v.instance];
|
|
48
|
-
|
|
49
|
-
let text = textMetadata[i32(char.w)];
|
|
50
|
-
|
|
51
|
-
let fontChar = fontChars[i32(char.z)];
|
|
52
|
-
|
|
53
|
-
let charPos = ((quad[v.vertex] * fontChar.size + char.xy + fontChar.offset) * text.scale) + text.pos;
|
|
54
|
-
|
|
55
|
-
var vert = vec4f(charPos, 0.0, 1.0);
|
|
56
|
-
vert = transforms[i32(text.matrixIndex)] * vert;
|
|
57
|
-
vert.x /= uniforms.halfWidth;
|
|
58
|
-
vert.y /= uniforms.halfHeight;
|
|
59
|
-
|
|
60
|
-
var f : FragmentParams;
|
|
61
|
-
f.position = vert;
|
|
62
|
-
f.texCoord = (quad[v.vertex] * vec2f(1, -1)) * fontChar.texExtent + fontChar.texOffset;
|
|
63
|
-
f.fillColor = colors[i32(text.fillIndex)];
|
|
64
|
-
return f;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
fn sampleMsdf(texCoord: vec2f) -> f32 {
|
|
68
|
-
let c = textureSample(fontTexture, fontSampler, texCoord);
|
|
69
|
-
return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@fragment
|
|
73
|
-
fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
|
|
74
|
-
// pxRange (AKA distanceRange) comes from the msdfgen tool,
|
|
75
|
-
// uses the default which is 4.
|
|
76
|
-
let pxRange = 4.0;
|
|
77
|
-
let sz = vec2f(textureDimensions(fontTexture, 0));
|
|
78
|
-
let dx = sz.x*length(vec2f(dpdxFine(f.texCoord.x), dpdyFine(f.texCoord.x)));
|
|
79
|
-
let dy = sz.y*length(vec2f(dpdxFine(f.texCoord.y), dpdyFine(f.texCoord.y)));
|
|
80
|
-
let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
|
|
81
|
-
let sigDist = sampleMsdf(f.texCoord) - 0.5;
|
|
82
|
-
let pxDist = sigDist * toPixels;
|
|
83
|
-
let edgeWidth = 0.5;
|
|
84
|
-
let alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
|
|
85
|
-
if (alpha < 0.001) {
|
|
86
|
-
discard;
|
|
87
|
-
}
|
|
88
|
-
return vec4f(f.fillColor.rgb, f.fillColor.a * alpha);
|
|
89
|
-
}
|
|
90
|
-
`
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
let textBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
94
|
-
label: 'MSDF text group layout',
|
|
95
|
-
entries: [
|
|
96
|
-
{
|
|
97
|
-
binding: 0,
|
|
98
|
-
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
99
|
-
buffer: { type: 'read-only-storage' }
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
binding: 1,
|
|
103
|
-
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
104
|
-
buffer: { type: 'read-only-storage' }
|
|
105
|
-
}
|
|
106
|
-
]
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
let fontSampler = Q5.device.createSampler({
|
|
110
|
-
minFilter: 'linear',
|
|
111
|
-
magFilter: 'linear',
|
|
112
|
-
mipmapFilter: 'linear',
|
|
113
|
-
maxAnisotropy: 16
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
let fontBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
117
|
-
label: 'MSDF font group layout',
|
|
118
|
-
entries: [
|
|
119
|
-
{
|
|
120
|
-
binding: 0,
|
|
121
|
-
visibility: GPUShaderStage.FRAGMENT,
|
|
122
|
-
texture: {}
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
binding: 1,
|
|
126
|
-
visibility: GPUShaderStage.FRAGMENT,
|
|
127
|
-
sampler: {}
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
binding: 2,
|
|
131
|
-
visibility: GPUShaderStage.VERTEX,
|
|
132
|
-
buffer: { type: 'read-only-storage' }
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
let fontPipelineLayout = Q5.device.createPipelineLayout({
|
|
138
|
-
bindGroupLayouts: [...$.bindGroupLayouts, fontBindGroupLayout, textBindGroupLayout]
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
$._pipelineConfigs[2] = {
|
|
142
|
-
label: 'msdf font pipeline',
|
|
143
|
-
layout: fontPipelineLayout,
|
|
144
|
-
vertex: { module: textShader, entryPoint: 'vertexMain' },
|
|
145
|
-
fragment: {
|
|
146
|
-
module: textShader,
|
|
147
|
-
entryPoint: 'fragmentMain',
|
|
148
|
-
targets: [{ format: 'bgra8unorm', blend: $.blendConfigs.normal }]
|
|
149
|
-
},
|
|
150
|
-
primitive: { topology: 'triangle-strip', stripIndexFormat: 'uint32' },
|
|
151
|
-
multisample: { count: 4 }
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
$._pipelines[2] = Q5.device.createRenderPipeline($._pipelineConfigs[2]);
|
|
155
|
-
|
|
156
|
-
class MsdfFont {
|
|
157
|
-
constructor(bindGroup, lineHeight, chars, kernings) {
|
|
158
|
-
this.bindGroup = bindGroup;
|
|
159
|
-
this.lineHeight = lineHeight;
|
|
160
|
-
this.chars = chars;
|
|
161
|
-
this.kernings = kernings;
|
|
162
|
-
let charArray = Object.values(chars);
|
|
163
|
-
this.charCount = charArray.length;
|
|
164
|
-
this.defaultChar = charArray[0];
|
|
165
|
-
}
|
|
166
|
-
getChar(charCode) {
|
|
167
|
-
return this.chars[charCode] ?? this.defaultChar;
|
|
168
|
-
}
|
|
169
|
-
// Gets the distance in pixels a line should advance for a given
|
|
170
|
-
// character code. If the upcoming character code is given any
|
|
171
|
-
// kerning between the two characters will be taken into account.
|
|
172
|
-
getXAdvance(charCode, nextCharCode = -1) {
|
|
173
|
-
let char = this.getChar(charCode);
|
|
174
|
-
if (nextCharCode >= 0) {
|
|
175
|
-
let kerning = this.kernings.get(charCode);
|
|
176
|
-
if (kerning) {
|
|
177
|
-
return char.xadvance + (kerning.get(nextCharCode) ?? 0);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return char.xadvance;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
$._fonts = [];
|
|
185
|
-
let fonts = {};
|
|
186
|
-
|
|
187
|
-
let createFont = async (fontJsonUrl, fontName, cb) => {
|
|
188
|
-
q._preloadCount++;
|
|
189
|
-
|
|
190
|
-
let res = await fetch(fontJsonUrl);
|
|
191
|
-
if (res.status == 404) {
|
|
192
|
-
q._preloadCount--;
|
|
193
|
-
return '';
|
|
194
|
-
}
|
|
195
|
-
let atlas = await res.json();
|
|
196
|
-
|
|
197
|
-
let slashIdx = fontJsonUrl.lastIndexOf('/');
|
|
198
|
-
let baseUrl = slashIdx != -1 ? fontJsonUrl.substring(0, slashIdx + 1) : '';
|
|
199
|
-
// load font image
|
|
200
|
-
res = await fetch(baseUrl + atlas.pages[0]);
|
|
201
|
-
let img = await createImageBitmap(await res.blob());
|
|
202
|
-
|
|
203
|
-
// convert image to texture
|
|
204
|
-
let imgSize = [img.width, img.height, 1];
|
|
205
|
-
let texture = Q5.device.createTexture({
|
|
206
|
-
label: `MSDF ${fontName}`,
|
|
207
|
-
size: imgSize,
|
|
208
|
-
format: 'rgba8unorm',
|
|
209
|
-
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
210
|
-
});
|
|
211
|
-
Q5.device.queue.copyExternalImageToTexture({ source: img }, { texture }, imgSize);
|
|
212
|
-
|
|
213
|
-
// chars and kernings can be stored as csv strings, making the file
|
|
214
|
-
// size smaller, but they need to be parsed into arrays of objects
|
|
215
|
-
if (typeof atlas.chars == 'string') {
|
|
216
|
-
atlas.chars = $.CSV.parse(atlas.chars, ' ');
|
|
217
|
-
atlas.kernings = $.CSV.parse(atlas.kernings, ' ');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
let charCount = atlas.chars.length;
|
|
221
|
-
let charsBuffer = Q5.device.createBuffer({
|
|
222
|
-
size: charCount * 32,
|
|
223
|
-
usage: GPUBufferUsage.STORAGE,
|
|
224
|
-
mappedAtCreation: true
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
let fontChars = new Float32Array(charsBuffer.getMappedRange());
|
|
228
|
-
let u = 1 / atlas.common.scaleW;
|
|
229
|
-
let v = 1 / atlas.common.scaleH;
|
|
230
|
-
let chars = {};
|
|
231
|
-
let o = 0; // offset
|
|
232
|
-
for (let [i, char] of atlas.chars.entries()) {
|
|
233
|
-
chars[char.id] = char;
|
|
234
|
-
chars[char.id].charIndex = i;
|
|
235
|
-
fontChars[o] = char.x * u; // texOffset.x
|
|
236
|
-
fontChars[o + 1] = char.y * v; // texOffset.y
|
|
237
|
-
fontChars[o + 2] = char.width * u; // texExtent.x
|
|
238
|
-
fontChars[o + 3] = char.height * v; // texExtent.y
|
|
239
|
-
fontChars[o + 4] = char.width; // size.x
|
|
240
|
-
fontChars[o + 5] = char.height; // size.y
|
|
241
|
-
fontChars[o + 6] = char.xoffset; // offset.x
|
|
242
|
-
fontChars[o + 7] = -char.yoffset; // offset.y
|
|
243
|
-
o += 8;
|
|
244
|
-
}
|
|
245
|
-
charsBuffer.unmap();
|
|
246
|
-
|
|
247
|
-
let fontBindGroup = Q5.device.createBindGroup({
|
|
248
|
-
label: 'msdf font bind group',
|
|
249
|
-
layout: fontBindGroupLayout,
|
|
250
|
-
entries: [
|
|
251
|
-
{ binding: 0, resource: texture.createView() },
|
|
252
|
-
{ binding: 1, resource: fontSampler },
|
|
253
|
-
{ binding: 2, resource: { buffer: charsBuffer } }
|
|
254
|
-
]
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
let kernings = new Map();
|
|
258
|
-
if (atlas.kernings) {
|
|
259
|
-
for (let kerning of atlas.kernings) {
|
|
260
|
-
let charKerning = kernings.get(kerning.first);
|
|
261
|
-
if (!charKerning) {
|
|
262
|
-
charKerning = new Map();
|
|
263
|
-
kernings.set(kerning.first, charKerning);
|
|
264
|
-
}
|
|
265
|
-
charKerning.set(kerning.second, kerning.amount);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
$._font = new MsdfFont(fontBindGroup, atlas.common.lineHeight, chars, kernings);
|
|
270
|
-
|
|
271
|
-
$._font.index = $._fonts.length;
|
|
272
|
-
$._fonts.push($._font);
|
|
273
|
-
fonts[fontName] = $._font;
|
|
274
|
-
|
|
275
|
-
q._preloadCount--;
|
|
276
|
-
|
|
277
|
-
if (cb) cb(fontName);
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
$._g.colorMode($.RGB, 1);
|
|
281
|
-
|
|
282
|
-
$.loadFont = (url, cb) => {
|
|
283
|
-
let ext = url.slice(url.lastIndexOf('.') + 1);
|
|
284
|
-
if (ext != 'json') return $._g.loadFont(url, cb);
|
|
285
|
-
let fontName = url.slice(url.lastIndexOf('/') + 1, url.lastIndexOf('-'));
|
|
286
|
-
createFont(url, fontName, cb);
|
|
287
|
-
return fontName;
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
$._loadDefaultFont = (fontName) => {
|
|
291
|
-
fonts[fontName] = null;
|
|
292
|
-
if (navigator.onLine) {
|
|
293
|
-
$.loadFont(`https://q5js.org/fonts/${fontName}-msdf.json`);
|
|
294
|
-
} else {
|
|
295
|
-
$.loadFont(`/node_modules/q5/builtinFonts/${fontName}-msdf.json`);
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
$._textSize = 18;
|
|
300
|
-
$._textAlign = 'left';
|
|
301
|
-
$._textBaseline = 'alphabetic';
|
|
302
|
-
let leadingSet = false,
|
|
303
|
-
leading = 22.5,
|
|
304
|
-
leadDiff = 4.5,
|
|
305
|
-
leadPercent = 1.25;
|
|
306
|
-
|
|
307
|
-
$.textFont = (fontName) => {
|
|
308
|
-
let font = fonts[fontName];
|
|
309
|
-
if (font) $._font = font;
|
|
310
|
-
else if (font === undefined) $._loadDefaultFont(fontName);
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
$.textSize = (size) => {
|
|
314
|
-
$._textSize = size;
|
|
315
|
-
if (!leadingSet) {
|
|
316
|
-
leading = size * leadPercent;
|
|
317
|
-
leadDiff = leading - size;
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
$.textLeading = (lineHeight) => {
|
|
322
|
-
$._font.lineHeight = leading = lineHeight;
|
|
323
|
-
leadDiff = leading - $._textSize;
|
|
324
|
-
leadPercent = leading / $._textSize;
|
|
325
|
-
leadingSet = true;
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
$.textAlign = (horiz, vert) => {
|
|
329
|
-
$._textAlign = horiz;
|
|
330
|
-
if (vert) $._textBaseline = vert;
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
let charStack = [],
|
|
334
|
-
textStack = [];
|
|
335
|
-
|
|
336
|
-
let measureText = (font, text, charCallback) => {
|
|
337
|
-
let maxWidth = 0,
|
|
338
|
-
offsetX = 0,
|
|
339
|
-
offsetY = 0,
|
|
340
|
-
line = 0,
|
|
341
|
-
printedCharCount = 0,
|
|
342
|
-
lineWidths = [],
|
|
343
|
-
nextCharCode = text.charCodeAt(0);
|
|
344
|
-
|
|
345
|
-
for (let i = 0; i < text.length; ++i) {
|
|
346
|
-
let charCode = nextCharCode;
|
|
347
|
-
nextCharCode = i < text.length - 1 ? text.charCodeAt(i + 1) : -1;
|
|
348
|
-
switch (charCode) {
|
|
349
|
-
case 10: // newline
|
|
350
|
-
lineWidths.push(offsetX);
|
|
351
|
-
line++;
|
|
352
|
-
maxWidth = Math.max(maxWidth, offsetX);
|
|
353
|
-
offsetX = 0;
|
|
354
|
-
offsetY -= font.lineHeight * leadPercent;
|
|
355
|
-
break;
|
|
356
|
-
case 13: // CR
|
|
357
|
-
break;
|
|
358
|
-
case 32: // space
|
|
359
|
-
// advance the offset without actually adding a character
|
|
360
|
-
offsetX += font.getXAdvance(charCode);
|
|
361
|
-
break;
|
|
362
|
-
case 9: // tab
|
|
363
|
-
offsetX += font.getXAdvance(charCode) * 2;
|
|
364
|
-
break;
|
|
365
|
-
default:
|
|
366
|
-
if (charCallback) {
|
|
367
|
-
charCallback(offsetX, offsetY, line, font.getChar(charCode));
|
|
368
|
-
}
|
|
369
|
-
offsetX += font.getXAdvance(charCode, nextCharCode);
|
|
370
|
-
printedCharCount++;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
lineWidths.push(offsetX);
|
|
374
|
-
maxWidth = Math.max(maxWidth, offsetX);
|
|
375
|
-
return {
|
|
376
|
-
width: maxWidth,
|
|
377
|
-
height: lineWidths.length * font.lineHeight * leadPercent,
|
|
378
|
-
lineWidths,
|
|
379
|
-
printedCharCount
|
|
380
|
-
};
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
$.text = (str, x, y, w, h) => {
|
|
384
|
-
if (!$._font) {
|
|
385
|
-
// if the default font hasn't been loaded yet, try to load it
|
|
386
|
-
if ($._font !== null) $.textFont('sans-serif');
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
let type = typeof str;
|
|
391
|
-
if (type != 'string') {
|
|
392
|
-
if (type == 'object') str = str.toString();
|
|
393
|
-
else str = str + '';
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (str.length > w) {
|
|
397
|
-
let wrapped = [];
|
|
398
|
-
let i = 0;
|
|
399
|
-
while (i < str.length && wrapped.length < h) {
|
|
400
|
-
let max = i + w;
|
|
401
|
-
if (max >= str.length) {
|
|
402
|
-
wrapped.push(str.slice(i));
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
let end = str.lastIndexOf(' ', max);
|
|
406
|
-
if (end == -1 || end < i) end = max;
|
|
407
|
-
wrapped.push(str.slice(i, end));
|
|
408
|
-
i = end + 1;
|
|
409
|
-
}
|
|
410
|
-
str = wrapped.join('\n');
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
let spaces = 0, // whitespace char count, not literal spaces
|
|
414
|
-
hasNewline;
|
|
415
|
-
for (let i = 0; i < str.length; i++) {
|
|
416
|
-
let c = str[i];
|
|
417
|
-
switch (c) {
|
|
418
|
-
case '\n':
|
|
419
|
-
hasNewline = true;
|
|
420
|
-
case '\r':
|
|
421
|
-
case '\t':
|
|
422
|
-
case ' ':
|
|
423
|
-
spaces++;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
let charsData = [];
|
|
428
|
-
|
|
429
|
-
let ta = $._textAlign,
|
|
430
|
-
tb = $._textBaseline,
|
|
431
|
-
textIndex = textStack.length,
|
|
432
|
-
o = 0, // offset
|
|
433
|
-
measurements;
|
|
434
|
-
|
|
435
|
-
if (ta == 'left' && !hasNewline) {
|
|
436
|
-
measurements = measureText($._font, str, (textX, textY, line, char) => {
|
|
437
|
-
charsData[o] = textX;
|
|
438
|
-
charsData[o + 1] = textY;
|
|
439
|
-
charsData[o + 2] = char.charIndex;
|
|
440
|
-
charsData[o + 3] = textIndex;
|
|
441
|
-
o += 4;
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
if (tb == 'alphabetic') y -= $._textSize;
|
|
445
|
-
else if (tb == 'center') y -= $._textSize * 0.5;
|
|
446
|
-
else if (tb == 'bottom') y -= leading;
|
|
447
|
-
} else {
|
|
448
|
-
// measure the text to get the line widths before setting
|
|
449
|
-
// the x position to properly align the text
|
|
450
|
-
measurements = measureText($._font, str);
|
|
451
|
-
|
|
452
|
-
let offsetY = 0;
|
|
453
|
-
if (tb == 'alphabetic') y -= $._textSize;
|
|
454
|
-
else if (tb == 'center') offsetY = measurements.height * 0.5;
|
|
455
|
-
else if (tb == 'bottom') offsetY = measurements.height;
|
|
456
|
-
|
|
457
|
-
measureText($._font, str, (textX, textY, line, char) => {
|
|
458
|
-
let offsetX = 0;
|
|
459
|
-
if (ta == 'center') {
|
|
460
|
-
offsetX = measurements.width * -0.5 - (measurements.width - measurements.lineWidths[line]) * -0.5;
|
|
461
|
-
} else if (ta == 'right') {
|
|
462
|
-
offsetX = -measurements.lineWidths[line];
|
|
463
|
-
}
|
|
464
|
-
charsData[o] = textX + offsetX;
|
|
465
|
-
charsData[o + 1] = textY + offsetY;
|
|
466
|
-
charsData[o + 2] = char.charIndex;
|
|
467
|
-
charsData[o + 3] = textIndex;
|
|
468
|
-
o += 4;
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
charStack.push(charsData);
|
|
472
|
-
|
|
473
|
-
let txt = [];
|
|
474
|
-
|
|
475
|
-
if ($._matrixDirty) $._saveMatrix();
|
|
476
|
-
|
|
477
|
-
txt[0] = x;
|
|
478
|
-
txt[1] = -y;
|
|
479
|
-
txt[2] = $._textSize / 44;
|
|
480
|
-
txt[3] = $._matrixIndex;
|
|
481
|
-
txt[4] = $._fillSet ? $._fill : 0;
|
|
482
|
-
txt[5] = $._stroke;
|
|
483
|
-
|
|
484
|
-
textStack.push(txt);
|
|
485
|
-
$.drawStack.push(2, measurements.printedCharCount, $._font.index);
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
$.textWidth = (str) => {
|
|
489
|
-
if (!$._font) return 0;
|
|
490
|
-
return measureText($._font, str).width;
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
$.createTextImage = (str, w, h) => {
|
|
494
|
-
$._g.textSize($._textSize);
|
|
495
|
-
|
|
496
|
-
if ($._doFill) {
|
|
497
|
-
let fi = $._fill * 4;
|
|
498
|
-
$._g.fill(colorStack.slice(fi, fi + 4));
|
|
499
|
-
}
|
|
500
|
-
if ($._doStroke) {
|
|
501
|
-
let si = $._stroke * 4;
|
|
502
|
-
$._g.stroke(colorStack.slice(si, si + 4));
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
let img = $._g.createTextImage(str, w, h);
|
|
506
|
-
|
|
507
|
-
if (img.canvas.textureIndex == undefined) {
|
|
508
|
-
$._createTexture(img);
|
|
509
|
-
} else if (img.modified) {
|
|
510
|
-
let cnv = img.canvas;
|
|
511
|
-
let textureSize = [cnv.width, cnv.height, 1];
|
|
512
|
-
let texture = $._textures[cnv.textureIndex];
|
|
513
|
-
|
|
514
|
-
Q5.device.queue.copyExternalImageToTexture(
|
|
515
|
-
{ source: cnv },
|
|
516
|
-
{ texture, colorSpace: $.canvas.colorSpace },
|
|
517
|
-
textureSize
|
|
518
|
-
);
|
|
519
|
-
img.modified = false;
|
|
520
|
-
}
|
|
521
|
-
return img;
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
$.textImage = (img, x, y) => {
|
|
525
|
-
if (typeof img == 'string') img = $.createTextImage(img);
|
|
526
|
-
|
|
527
|
-
let og = $._imageMode;
|
|
528
|
-
$._imageMode = 'corner';
|
|
529
|
-
|
|
530
|
-
let ta = $._textAlign;
|
|
531
|
-
if (ta == 'center') x -= img.canvas.hw;
|
|
532
|
-
else if (ta == 'right') x -= img.width;
|
|
533
|
-
|
|
534
|
-
let bl = $._textBaseline;
|
|
535
|
-
if (bl == 'alphabetic') y -= img._leading;
|
|
536
|
-
else if (bl == 'center') y -= img._middle;
|
|
537
|
-
else if (bl == 'bottom') y -= img._bottom;
|
|
538
|
-
else if (bl == 'top') y -= img._top;
|
|
539
|
-
|
|
540
|
-
$.image(img, x, y);
|
|
541
|
-
$._imageMode = og;
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
$._hooks.preRender.push(() => {
|
|
545
|
-
if (!charStack.length) return;
|
|
546
|
-
|
|
547
|
-
// calculate total buffer size for text data
|
|
548
|
-
let totalTextSize = 0;
|
|
549
|
-
for (let charsData of charStack) {
|
|
550
|
-
totalTextSize += charsData.length * 4;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// create a single buffer for all the char data
|
|
554
|
-
let charBuffer = Q5.device.createBuffer({
|
|
555
|
-
size: totalTextSize,
|
|
556
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
557
|
-
mappedAtCreation: true
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
// copy all the text data into the buffer
|
|
561
|
-
new Float32Array(charBuffer.getMappedRange()).set(charStack.flat());
|
|
562
|
-
charBuffer.unmap();
|
|
563
|
-
|
|
564
|
-
// calculate total buffer size for metadata
|
|
565
|
-
let totalMetadataSize = textStack.length * 6 * 4;
|
|
566
|
-
|
|
567
|
-
// create a single buffer for all metadata
|
|
568
|
-
let textBuffer = Q5.device.createBuffer({
|
|
569
|
-
label: 'textBuffer',
|
|
570
|
-
size: totalMetadataSize,
|
|
571
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
572
|
-
mappedAtCreation: true
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
// copy all metadata into the buffer
|
|
576
|
-
new Float32Array(textBuffer.getMappedRange()).set(textStack.flat());
|
|
577
|
-
textBuffer.unmap();
|
|
578
|
-
|
|
579
|
-
// create a single bind group for the text buffer and metadata buffer
|
|
580
|
-
$._textBindGroup = Q5.device.createBindGroup({
|
|
581
|
-
label: 'msdf text bind group',
|
|
582
|
-
layout: textBindGroupLayout,
|
|
583
|
-
entries: [
|
|
584
|
-
{ binding: 0, resource: { buffer: charBuffer } },
|
|
585
|
-
{ binding: 1, resource: { buffer: textBuffer } }
|
|
586
|
-
]
|
|
587
|
-
});
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
$._hooks.postRender.push(() => {
|
|
591
|
-
charStack = [];
|
|
592
|
-
textStack = [];
|
|
593
|
-
});
|
|
594
|
-
};
|