q5 2.4.10 → 2.5.1
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 +5 -2
- package/package.json +1 -1
- package/q5.d.ts +68 -11
- package/q5.js +752 -154
- package/q5.min.js +1 -1
- package/src/q5-2d-canvas.js +0 -9
- package/src/q5-2d-image.js +12 -6
- package/src/q5-2d-text.js +19 -16
- package/src/q5-canvas.js +11 -7
- package/src/q5-core.js +2 -2
- package/src/q5-util.js +17 -1
- package/src/q5-webgpu-canvas.js +79 -62
- package/src/q5-webgpu-drawing.js +21 -13
- package/src/q5-webgpu-image.js +10 -12
- package/src/q5-webgpu-text.js +581 -26
- package/src/readme.md +81 -4
package/q5.js
CHANGED
|
@@ -70,9 +70,9 @@ function Q5(scope, parent, renderer) {
|
|
|
70
70
|
let ts = timestamp || performance.now();
|
|
71
71
|
$._lastFrameTime ??= ts - $._targetFrameDuration;
|
|
72
72
|
|
|
73
|
-
if ($.
|
|
73
|
+
if ($._didResize) {
|
|
74
74
|
$.windowResized();
|
|
75
|
-
$.
|
|
75
|
+
$._didResize = false;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
if ($._loop) looper = raf($._draw);
|
|
@@ -483,7 +483,7 @@ Q5.modules.canvas = ($, q) => {
|
|
|
483
483
|
|
|
484
484
|
function parentResized() {
|
|
485
485
|
if ($.frameCount > 1) {
|
|
486
|
-
$.
|
|
486
|
+
$._didResize = true;
|
|
487
487
|
$._adjustDisplay();
|
|
488
488
|
}
|
|
489
489
|
}
|
|
@@ -543,14 +543,9 @@ Q5.modules.canvas = ($, q) => {
|
|
|
543
543
|
'_imageMode',
|
|
544
544
|
'_rectMode',
|
|
545
545
|
'_ellipseMode',
|
|
546
|
-
'_textFont',
|
|
547
|
-
'_textLeading',
|
|
548
|
-
'_leadingSet',
|
|
549
546
|
'_textSize',
|
|
550
547
|
'_textAlign',
|
|
551
|
-
'_textBaseline'
|
|
552
|
-
'_textStyle',
|
|
553
|
-
'_textWrap'
|
|
548
|
+
'_textBaseline'
|
|
554
549
|
];
|
|
555
550
|
$._styles = [];
|
|
556
551
|
|
|
@@ -563,6 +558,15 @@ Q5.modules.canvas = ($, q) => {
|
|
|
563
558
|
let styles = $._styles.pop();
|
|
564
559
|
for (let s of $._styleNames) $[s] = styles[s];
|
|
565
560
|
};
|
|
561
|
+
|
|
562
|
+
if (window && $._scope != 'graphics') {
|
|
563
|
+
window.addEventListener('resize', () => {
|
|
564
|
+
$._didResize = true;
|
|
565
|
+
q.windowWidth = window.innerWidth;
|
|
566
|
+
q.windowHeight = window.innerHeight;
|
|
567
|
+
q.deviceOrientation = window.screen?.orientation?.type;
|
|
568
|
+
});
|
|
569
|
+
}
|
|
566
570
|
};
|
|
567
571
|
|
|
568
572
|
Q5.canvasOptions = {
|
|
@@ -713,15 +717,6 @@ Q5.renderers.q2d.canvas = ($, q) => {
|
|
|
713
717
|
document.body.append(vid);
|
|
714
718
|
return vid;
|
|
715
719
|
};
|
|
716
|
-
|
|
717
|
-
if (window && $._scope != 'graphics') {
|
|
718
|
-
window.addEventListener('resize', () => {
|
|
719
|
-
$._shouldResize = true;
|
|
720
|
-
q.windowWidth = window.innerWidth;
|
|
721
|
-
q.windowHeight = window.innerHeight;
|
|
722
|
-
q.deviceOrientation = window.screen?.orientation?.type;
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
720
|
};
|
|
726
721
|
Q5.renderers.q2d.drawing = ($) => {
|
|
727
722
|
$._doStroke = true;
|
|
@@ -1352,11 +1347,18 @@ Q5.renderers.q2d.image = ($, q) => {
|
|
|
1352
1347
|
$.ctx.restore();
|
|
1353
1348
|
};
|
|
1354
1349
|
|
|
1350
|
+
$.inset = (x, y, w, h, dx, dy, dw, dh) => {
|
|
1351
|
+
let pd = $._pixelDensity || 1;
|
|
1352
|
+
$.ctx.drawImage($.canvas, x * pd, y * pd, w * pd, h * pd, dx, dy, dw, dh);
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1355
|
+
$.copy = () => $.get();
|
|
1356
|
+
|
|
1355
1357
|
$.get = (x, y, w, h) => {
|
|
1356
1358
|
let pd = $._pixelDensity || 1;
|
|
1357
1359
|
if (x !== undefined && w === undefined) {
|
|
1358
1360
|
let c = $._getImageData(x * pd, y * pd, 1, 1).data;
|
|
1359
|
-
return
|
|
1361
|
+
return [c[0], c[1], c[2], c[3] / 255];
|
|
1360
1362
|
}
|
|
1361
1363
|
x = (x || 0) * pd;
|
|
1362
1364
|
y = (y || 0) * pd;
|
|
@@ -1365,8 +1367,7 @@ Q5.renderers.q2d.image = ($, q) => {
|
|
|
1365
1367
|
w *= pd;
|
|
1366
1368
|
h *= pd;
|
|
1367
1369
|
let img = $.createImage(w, h);
|
|
1368
|
-
|
|
1369
|
-
img.ctx.putImageData(imgData, 0, 0);
|
|
1370
|
+
img.ctx.drawImage($.canvas, x, y, w, h, 0, 0, w, h);
|
|
1370
1371
|
img._pixelDensity = pd;
|
|
1371
1372
|
img.width = _w;
|
|
1372
1373
|
img.height = _h;
|
|
@@ -1386,9 +1387,9 @@ Q5.renderers.q2d.image = ($, q) => {
|
|
|
1386
1387
|
for (let i = 0; i < mod; i++) {
|
|
1387
1388
|
for (let j = 0; j < mod; j++) {
|
|
1388
1389
|
let idx = 4 * ((y * mod + i) * $.canvas.width + x * mod + j);
|
|
1389
|
-
$.pixels[idx] = c.r
|
|
1390
|
-
$.pixels[idx + 1] = c.g
|
|
1391
|
-
$.pixels[idx + 2] = c.b
|
|
1390
|
+
$.pixels[idx] = c.r;
|
|
1391
|
+
$.pixels[idx + 1] = c.g;
|
|
1392
|
+
$.pixels[idx + 2] = c.b;
|
|
1392
1393
|
$.pixels[idx + 3] = c.a;
|
|
1393
1394
|
}
|
|
1394
1395
|
}
|
|
@@ -1424,9 +1425,10 @@ Q5.BLUR = 8;
|
|
|
1424
1425
|
Q5.renderers.q2d.text = ($, q) => {
|
|
1425
1426
|
$._textAlign = 'left';
|
|
1426
1427
|
$._textBaseline = 'alphabetic';
|
|
1428
|
+
$._textSize = 12;
|
|
1427
1429
|
|
|
1428
1430
|
let font = 'sans-serif',
|
|
1429
|
-
|
|
1431
|
+
leadingSet = false,
|
|
1430
1432
|
leading = 15,
|
|
1431
1433
|
leadDiff = 3,
|
|
1432
1434
|
emphasis = 'normal',
|
|
@@ -1453,32 +1455,34 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
1453
1455
|
};
|
|
1454
1456
|
|
|
1455
1457
|
$.textFont = (x) => {
|
|
1458
|
+
if (!x || x == font) return font;
|
|
1456
1459
|
font = x;
|
|
1457
1460
|
fontMod = true;
|
|
1458
1461
|
styleHash = -1;
|
|
1459
1462
|
};
|
|
1460
1463
|
$.textSize = (x) => {
|
|
1461
|
-
if (x
|
|
1464
|
+
if (x == undefined || x == $._textSize) return $._textSize;
|
|
1462
1465
|
if ($._da) x *= $._da;
|
|
1463
|
-
|
|
1466
|
+
$._textSize = x;
|
|
1464
1467
|
fontMod = true;
|
|
1465
1468
|
styleHash = -1;
|
|
1466
|
-
if (
|
|
1469
|
+
if (!leadingSet) {
|
|
1467
1470
|
leading = x * 1.25;
|
|
1468
1471
|
leadDiff = leading - x;
|
|
1469
1472
|
}
|
|
1470
1473
|
};
|
|
1471
1474
|
$.textStyle = (x) => {
|
|
1475
|
+
if (!x || x == emphasis) return emphasis;
|
|
1472
1476
|
emphasis = x;
|
|
1473
1477
|
fontMod = true;
|
|
1474
1478
|
styleHash = -1;
|
|
1475
1479
|
};
|
|
1476
1480
|
$.textLeading = (x) => {
|
|
1477
|
-
|
|
1481
|
+
leadingSet = true;
|
|
1482
|
+
if (x == undefined || x == leading) return leading;
|
|
1478
1483
|
if ($._da) x *= $._da;
|
|
1479
1484
|
leading = x;
|
|
1480
|
-
leadDiff = x -
|
|
1481
|
-
$._leadingSet = true;
|
|
1485
|
+
leadDiff = x - $._textSize;
|
|
1482
1486
|
styleHash = -1;
|
|
1483
1487
|
};
|
|
1484
1488
|
$.textAlign = (horiz, vert) => {
|
|
@@ -1486,7 +1490,6 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
1486
1490
|
if (vert) {
|
|
1487
1491
|
$.ctx.textBaseline = $._textBaseline = vert == $.CENTER ? 'middle' : vert;
|
|
1488
1492
|
}
|
|
1489
|
-
styleHash = -1;
|
|
1490
1493
|
};
|
|
1491
1494
|
|
|
1492
1495
|
$.textWidth = (str) => $.ctx.measureText(str).width;
|
|
@@ -1497,7 +1500,7 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
1497
1500
|
$.textStroke = $.stroke;
|
|
1498
1501
|
|
|
1499
1502
|
let updateStyleHash = () => {
|
|
1500
|
-
let styleString = font +
|
|
1503
|
+
let styleString = font + $._textSize + emphasis + leading;
|
|
1501
1504
|
|
|
1502
1505
|
let hash = 5381;
|
|
1503
1506
|
for (let i = 0; i < styleString.length; i++) {
|
|
@@ -1530,7 +1533,7 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
1530
1533
|
let img, tX, tY;
|
|
1531
1534
|
|
|
1532
1535
|
if (fontMod) {
|
|
1533
|
-
ctx.font = `${emphasis} ${
|
|
1536
|
+
ctx.font = `${emphasis} ${$._textSize}px ${font}`;
|
|
1534
1537
|
fontMod = false;
|
|
1535
1538
|
}
|
|
1536
1539
|
|
|
@@ -1551,7 +1554,7 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
1551
1554
|
if (str.indexOf('\n') == -1) lines[0] = str;
|
|
1552
1555
|
else lines = str.split('\n');
|
|
1553
1556
|
|
|
1554
|
-
if (w) {
|
|
1557
|
+
if (str.length > w) {
|
|
1555
1558
|
let wrapped = [];
|
|
1556
1559
|
for (let line of lines) {
|
|
1557
1560
|
let i = 0;
|
|
@@ -1563,11 +1566,9 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
1563
1566
|
break;
|
|
1564
1567
|
}
|
|
1565
1568
|
let end = line.lastIndexOf(' ', max);
|
|
1566
|
-
if (end === -1 || end < i)
|
|
1567
|
-
end = max;
|
|
1568
|
-
}
|
|
1569
|
+
if (end === -1 || end < i) end = max;
|
|
1569
1570
|
wrapped.push(line.slice(i, end));
|
|
1570
|
-
i = end;
|
|
1571
|
+
i = end + 1;
|
|
1571
1572
|
}
|
|
1572
1573
|
}
|
|
1573
1574
|
lines = wrapped;
|
|
@@ -1595,6 +1596,7 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
1595
1596
|
img._top = descent + leadDiff;
|
|
1596
1597
|
img._middle = img._top + ascent * 0.5;
|
|
1597
1598
|
img._bottom = img._top + ascent;
|
|
1599
|
+
img._leading = leading;
|
|
1598
1600
|
}
|
|
1599
1601
|
|
|
1600
1602
|
img._fill = $._fill;
|
|
@@ -1646,6 +1648,8 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
1646
1648
|
}
|
|
1647
1649
|
};
|
|
1648
1650
|
$.textImage = (img, x, y) => {
|
|
1651
|
+
if (typeof img == 'string') img = $.createTextImage(img);
|
|
1652
|
+
|
|
1649
1653
|
let og = $._imageMode;
|
|
1650
1654
|
$._imageMode = 'corner';
|
|
1651
1655
|
|
|
@@ -1654,7 +1658,7 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
1654
1658
|
else if (ta == 'right') x -= img.width;
|
|
1655
1659
|
|
|
1656
1660
|
let bl = $._textBaseline;
|
|
1657
|
-
if (bl == 'alphabetic') y -=
|
|
1661
|
+
if (bl == 'alphabetic') y -= img._leading;
|
|
1658
1662
|
else if (bl == 'middle') y -= img._middle;
|
|
1659
1663
|
else if (bl == 'bottom') y -= img._bottom;
|
|
1660
1664
|
else if (bl == 'top') y -= img._top;
|
|
@@ -2740,10 +2744,11 @@ Q5.modules.util = ($, q) => {
|
|
|
2740
2744
|
fetch(path)
|
|
2741
2745
|
.then((r) => {
|
|
2742
2746
|
if (type == 'json') return r.json();
|
|
2743
|
-
|
|
2747
|
+
return r.text();
|
|
2744
2748
|
})
|
|
2745
2749
|
.then((r) => {
|
|
2746
2750
|
q._preloadCount--;
|
|
2751
|
+
if (type == 'csv') r = $.CSV.parse(r);
|
|
2747
2752
|
Object.assign(ret, r);
|
|
2748
2753
|
if (cb) cb(r);
|
|
2749
2754
|
});
|
|
@@ -2752,6 +2757,21 @@ Q5.modules.util = ($, q) => {
|
|
|
2752
2757
|
|
|
2753
2758
|
$.loadStrings = (path, cb) => $._loadFile(path, cb, 'text');
|
|
2754
2759
|
$.loadJSON = (path, cb) => $._loadFile(path, cb, 'json');
|
|
2760
|
+
$.loadCSV = (path, cb) => $._loadFile(path, cb, 'csv');
|
|
2761
|
+
|
|
2762
|
+
$.CSV = {};
|
|
2763
|
+
$.CSV.parse = (csv, sep = ',', lineSep = '\n') => {
|
|
2764
|
+
let a = [],
|
|
2765
|
+
lns = csv.split(lineSep),
|
|
2766
|
+
headers = lns[0].split(sep);
|
|
2767
|
+
for (let i = 1; i < lns.length; i++) {
|
|
2768
|
+
let o = {},
|
|
2769
|
+
ln = lns[i].split(sep);
|
|
2770
|
+
headers.forEach((h, i) => (o[h] = JSON.parse(ln[i])));
|
|
2771
|
+
a.push(o);
|
|
2772
|
+
}
|
|
2773
|
+
return a;
|
|
2774
|
+
};
|
|
2755
2775
|
|
|
2756
2776
|
if (typeof localStorage == 'object') {
|
|
2757
2777
|
$.storeItem = localStorage.setItem;
|
|
@@ -3064,7 +3084,8 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3064
3084
|
// colors used for each draw call
|
|
3065
3085
|
let colorsStack = ($.colorsStack = [1, 1, 1, 1]);
|
|
3066
3086
|
|
|
3067
|
-
$.
|
|
3087
|
+
$._transformLayout = Q5.device.createBindGroupLayout({
|
|
3088
|
+
label: 'transformLayout',
|
|
3068
3089
|
entries: [
|
|
3069
3090
|
{
|
|
3070
3091
|
binding: 0,
|
|
@@ -3073,14 +3094,9 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3073
3094
|
type: 'uniform',
|
|
3074
3095
|
hasDynamicOffset: false
|
|
3075
3096
|
}
|
|
3076
|
-
}
|
|
3077
|
-
]
|
|
3078
|
-
});
|
|
3079
|
-
|
|
3080
|
-
$._transformLayout = Q5.device.createBindGroupLayout({
|
|
3081
|
-
entries: [
|
|
3097
|
+
},
|
|
3082
3098
|
{
|
|
3083
|
-
binding:
|
|
3099
|
+
binding: 1,
|
|
3084
3100
|
visibility: GPUShaderStage.VERTEX,
|
|
3085
3101
|
buffer: {
|
|
3086
3102
|
type: 'read-only-storage',
|
|
@@ -3090,9 +3106,9 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3090
3106
|
]
|
|
3091
3107
|
});
|
|
3092
3108
|
|
|
3093
|
-
$.bindGroupLayouts = [$.
|
|
3109
|
+
$.bindGroupLayouts = [$._transformLayout];
|
|
3094
3110
|
|
|
3095
|
-
|
|
3111
|
+
let uniformBuffer = Q5.device.createBuffer({
|
|
3096
3112
|
size: 8, // Size of two floats
|
|
3097
3113
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
3098
3114
|
});
|
|
@@ -3107,18 +3123,6 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3107
3123
|
|
|
3108
3124
|
Q5.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([$.canvas.hw, $.canvas.hh]));
|
|
3109
3125
|
|
|
3110
|
-
$._envBindGroup = Q5.device.createBindGroup({
|
|
3111
|
-
layout: $._envLayout,
|
|
3112
|
-
entries: [
|
|
3113
|
-
{
|
|
3114
|
-
binding: 0,
|
|
3115
|
-
resource: {
|
|
3116
|
-
buffer: uniformBuffer
|
|
3117
|
-
}
|
|
3118
|
-
}
|
|
3119
|
-
]
|
|
3120
|
-
});
|
|
3121
|
-
|
|
3122
3126
|
return c;
|
|
3123
3127
|
};
|
|
3124
3128
|
|
|
@@ -3128,7 +3132,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3128
3132
|
|
|
3129
3133
|
// current color index, used to associate a vertex with a color
|
|
3130
3134
|
let colorIndex = 0;
|
|
3131
|
-
|
|
3135
|
+
let addColor = (r, g, b, a = 1) => {
|
|
3132
3136
|
if (typeof r == 'string') r = $.color(r);
|
|
3133
3137
|
else if (b == undefined) {
|
|
3134
3138
|
// grayscale mode `fill(1, 0.5)`
|
|
@@ -3140,6 +3144,8 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3140
3144
|
colorIndex++;
|
|
3141
3145
|
};
|
|
3142
3146
|
|
|
3147
|
+
$._fillIndex = $._strokeIndex = -1;
|
|
3148
|
+
|
|
3143
3149
|
$.fill = (r, g, b, a) => {
|
|
3144
3150
|
addColor(r, g, b, a);
|
|
3145
3151
|
$._doFill = true;
|
|
@@ -3171,56 +3177,70 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3171
3177
|
};
|
|
3172
3178
|
$.resetMatrix();
|
|
3173
3179
|
|
|
3174
|
-
//
|
|
3180
|
+
// tracks if the matrix has been modified
|
|
3175
3181
|
$._matrixDirty = false;
|
|
3176
3182
|
|
|
3177
|
-
//
|
|
3183
|
+
// array to store transformation matrices for the render pass
|
|
3178
3184
|
$.transformStates = [$._matrix.slice()];
|
|
3179
3185
|
|
|
3180
|
-
//
|
|
3186
|
+
// stack to keep track of transformation matrix indexes
|
|
3181
3187
|
$._transformIndexStack = [];
|
|
3182
3188
|
|
|
3183
3189
|
$.translate = (x, y, z) => {
|
|
3184
3190
|
if (!x && !y && !z) return;
|
|
3185
3191
|
// Update the translation values
|
|
3186
|
-
$._matrix[
|
|
3187
|
-
$._matrix[
|
|
3188
|
-
$._matrix[
|
|
3192
|
+
$._matrix[12] += x;
|
|
3193
|
+
$._matrix[13] -= y;
|
|
3194
|
+
$._matrix[14] += z || 0;
|
|
3189
3195
|
$._matrixDirty = true;
|
|
3190
3196
|
};
|
|
3191
3197
|
|
|
3192
|
-
$.rotate = (
|
|
3193
|
-
if (!
|
|
3194
|
-
if ($._angleMode)
|
|
3198
|
+
$.rotate = (a) => {
|
|
3199
|
+
if (!a) return;
|
|
3200
|
+
if ($._angleMode) a *= $._DEGTORAD;
|
|
3195
3201
|
|
|
3196
|
-
let cosR = Math.cos(
|
|
3197
|
-
let sinR = Math.sin(
|
|
3202
|
+
let cosR = Math.cos(a);
|
|
3203
|
+
let sinR = Math.sin(a);
|
|
3204
|
+
|
|
3205
|
+
let m = $._matrix;
|
|
3206
|
+
|
|
3207
|
+
let m0 = m[0],
|
|
3208
|
+
m1 = m[1],
|
|
3209
|
+
m4 = m[4],
|
|
3210
|
+
m5 = m[5];
|
|
3198
3211
|
|
|
3199
|
-
let m0 = $._matrix[0],
|
|
3200
|
-
m1 = $._matrix[1],
|
|
3201
|
-
m4 = $._matrix[4],
|
|
3202
|
-
m5 = $._matrix[5];
|
|
3203
3212
|
if (!m0 && !m1 && !m4 && !m5) {
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3213
|
+
m[0] = cosR;
|
|
3214
|
+
m[1] = sinR;
|
|
3215
|
+
m[4] = -sinR;
|
|
3216
|
+
m[5] = cosR;
|
|
3208
3217
|
} else {
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3218
|
+
m[0] = m0 * cosR + m4 * sinR;
|
|
3219
|
+
m[1] = m1 * cosR + m5 * sinR;
|
|
3220
|
+
m[4] = m4 * cosR - m0 * sinR;
|
|
3221
|
+
m[5] = m5 * cosR - m1 * sinR;
|
|
3213
3222
|
}
|
|
3214
3223
|
|
|
3215
3224
|
$._matrixDirty = true;
|
|
3216
3225
|
};
|
|
3217
3226
|
|
|
3218
|
-
$.scale = (
|
|
3219
|
-
|
|
3227
|
+
$.scale = (x = 1, y, z = 1) => {
|
|
3228
|
+
y ??= x;
|
|
3220
3229
|
|
|
3221
|
-
$._matrix
|
|
3222
|
-
|
|
3223
|
-
|
|
3230
|
+
let m = $._matrix;
|
|
3231
|
+
|
|
3232
|
+
m[0] *= x;
|
|
3233
|
+
m[1] *= x;
|
|
3234
|
+
m[2] *= x;
|
|
3235
|
+
m[3] *= x;
|
|
3236
|
+
m[4] *= y;
|
|
3237
|
+
m[5] *= y;
|
|
3238
|
+
m[6] *= y;
|
|
3239
|
+
m[7] *= y;
|
|
3240
|
+
m[8] *= z;
|
|
3241
|
+
m[9] *= z;
|
|
3242
|
+
m[10] *= z;
|
|
3243
|
+
m[11] *= z;
|
|
3224
3244
|
|
|
3225
3245
|
$._matrixDirty = true;
|
|
3226
3246
|
};
|
|
@@ -3292,7 +3312,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3292
3312
|
if (!$._transformIndexStack.length) {
|
|
3293
3313
|
return console.warn('Matrix index stack is empty!');
|
|
3294
3314
|
}
|
|
3295
|
-
// Pop the last matrix index
|
|
3315
|
+
// Pop the last matrix index and set it as the current matrix index
|
|
3296
3316
|
let idx = $._transformIndexStack.pop();
|
|
3297
3317
|
$._matrix = $.transformStates[idx].slice();
|
|
3298
3318
|
$._transformIndex = idx;
|
|
@@ -3315,7 +3335,6 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3315
3335
|
// left, right, top, bottom
|
|
3316
3336
|
let l, r, t, b;
|
|
3317
3337
|
if (!mode || mode == 'corner') {
|
|
3318
|
-
// CORNER
|
|
3319
3338
|
l = x;
|
|
3320
3339
|
r = x + w;
|
|
3321
3340
|
t = -y;
|
|
@@ -3355,7 +3374,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3355
3374
|
|
|
3356
3375
|
$._render = () => {
|
|
3357
3376
|
if (transformStates.length > 1 || !$._transformBindGroup) {
|
|
3358
|
-
|
|
3377
|
+
let transformBuffer = Q5.device.createBuffer({
|
|
3359
3378
|
size: transformStates.length * 64, // Size of 16 floats
|
|
3360
3379
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
3361
3380
|
});
|
|
@@ -3367,6 +3386,12 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3367
3386
|
entries: [
|
|
3368
3387
|
{
|
|
3369
3388
|
binding: 0,
|
|
3389
|
+
resource: {
|
|
3390
|
+
buffer: uniformBuffer
|
|
3391
|
+
}
|
|
3392
|
+
},
|
|
3393
|
+
{
|
|
3394
|
+
binding: 1,
|
|
3370
3395
|
resource: {
|
|
3371
3396
|
buffer: transformBuffer
|
|
3372
3397
|
}
|
|
@@ -3375,35 +3400,47 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3375
3400
|
});
|
|
3376
3401
|
}
|
|
3377
3402
|
|
|
3378
|
-
pass.setBindGroup(0, $.
|
|
3379
|
-
pass.setBindGroup(1, $._transformBindGroup);
|
|
3403
|
+
pass.setBindGroup(0, $._transformBindGroup);
|
|
3380
3404
|
|
|
3381
3405
|
for (let m of $._hooks.preRender) m();
|
|
3382
3406
|
|
|
3383
3407
|
let drawVertOffset = 0;
|
|
3384
3408
|
let imageVertOffset = 0;
|
|
3409
|
+
let textCharOffset = 0;
|
|
3385
3410
|
let curPipelineIndex = -1;
|
|
3386
3411
|
let curTextureIndex = -1;
|
|
3387
3412
|
|
|
3388
|
-
pass.setPipeline($.pipelines[0]);
|
|
3389
|
-
|
|
3390
3413
|
for (let i = 0; i < drawStack.length; i += 2) {
|
|
3391
3414
|
let v = drawStack[i + 1];
|
|
3392
3415
|
|
|
3416
|
+
if (drawStack[i] == -1) {
|
|
3417
|
+
v();
|
|
3418
|
+
continue;
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3393
3421
|
if (curPipelineIndex != drawStack[i]) {
|
|
3394
3422
|
curPipelineIndex = drawStack[i];
|
|
3395
3423
|
pass.setPipeline($.pipelines[curPipelineIndex]);
|
|
3396
3424
|
}
|
|
3397
3425
|
|
|
3398
3426
|
if (curPipelineIndex == 0) {
|
|
3399
|
-
|
|
3427
|
+
// v is the number of vertices
|
|
3428
|
+
pass.draw(v, 1, drawVertOffset);
|
|
3400
3429
|
drawVertOffset += v;
|
|
3401
3430
|
} else if (curPipelineIndex == 1) {
|
|
3402
3431
|
if (curTextureIndex != v) {
|
|
3403
|
-
|
|
3432
|
+
// v is the texture index
|
|
3433
|
+
pass.setBindGroup(2, $._textureBindGroups[v]);
|
|
3404
3434
|
}
|
|
3405
|
-
pass.draw(6, 1, imageVertOffset
|
|
3435
|
+
pass.draw(6, 1, imageVertOffset);
|
|
3406
3436
|
imageVertOffset += 6;
|
|
3437
|
+
} else if (curPipelineIndex == 2) {
|
|
3438
|
+
pass.setBindGroup(2, $._font.bindGroup);
|
|
3439
|
+
pass.setBindGroup(3, $._textBindGroup);
|
|
3440
|
+
|
|
3441
|
+
// v is the number of characters in the text
|
|
3442
|
+
pass.draw(4, v, 0, textCharOffset);
|
|
3443
|
+
textCharOffset += v;
|
|
3407
3444
|
}
|
|
3408
3445
|
}
|
|
3409
3446
|
|
|
@@ -3412,7 +3449,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3412
3449
|
|
|
3413
3450
|
$._finishRender = () => {
|
|
3414
3451
|
pass.end();
|
|
3415
|
-
|
|
3452
|
+
let commandBuffer = $.encoder.finish();
|
|
3416
3453
|
Q5.device.queue.submit([commandBuffer]);
|
|
3417
3454
|
q.pass = $.encoder = null;
|
|
3418
3455
|
|
|
@@ -3455,8 +3492,8 @@ Q5.renderers.webgpu.drawing = ($, q) => {
|
|
|
3455
3492
|
label: 'drawingVertexShader',
|
|
3456
3493
|
code: `
|
|
3457
3494
|
struct VertexOutput {
|
|
3458
|
-
@builtin(position) position:
|
|
3459
|
-
@location(
|
|
3495
|
+
@builtin(position) position: vec4f,
|
|
3496
|
+
@location(0) colorIndex: f32
|
|
3460
3497
|
};
|
|
3461
3498
|
|
|
3462
3499
|
struct Uniforms {
|
|
@@ -3465,12 +3502,12 @@ struct Uniforms {
|
|
|
3465
3502
|
};
|
|
3466
3503
|
|
|
3467
3504
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
3468
|
-
@group(
|
|
3505
|
+
@group(0) @binding(1) var<storage, read> transforms: array<mat4x4<f32>>;
|
|
3469
3506
|
|
|
3470
3507
|
@vertex
|
|
3471
|
-
fn vertexMain(@location(0) pos:
|
|
3472
|
-
var vert =
|
|
3473
|
-
vert
|
|
3508
|
+
fn vertexMain(@location(0) pos: vec2f, @location(1) colorIndex: f32, @location(2) transformIndex: f32) -> VertexOutput {
|
|
3509
|
+
var vert = vec4f(pos, 0.0, 1.0);
|
|
3510
|
+
vert = transforms[i32(transformIndex)] * vert;
|
|
3474
3511
|
vert.x /= uniforms.halfWidth;
|
|
3475
3512
|
vert.y /= uniforms.halfHeight;
|
|
3476
3513
|
|
|
@@ -3485,17 +3522,18 @@ fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32, @locati
|
|
|
3485
3522
|
let fragmentShader = Q5.device.createShaderModule({
|
|
3486
3523
|
label: 'drawingFragmentShader',
|
|
3487
3524
|
code: `
|
|
3488
|
-
@group(
|
|
3525
|
+
@group(1) @binding(0) var<storage, read> colors : array<vec4f>;
|
|
3489
3526
|
|
|
3490
3527
|
@fragment
|
|
3491
|
-
fn fragmentMain(@location(
|
|
3492
|
-
let index =
|
|
3493
|
-
return mix(
|
|
3528
|
+
fn fragmentMain(@location(0) colorIndex: f32) -> @location(0) vec4f {
|
|
3529
|
+
let index = i32(colorIndex);
|
|
3530
|
+
return mix(colors[index], colors[index + 1], fract(colorIndex));
|
|
3494
3531
|
}
|
|
3495
3532
|
`
|
|
3496
3533
|
});
|
|
3497
3534
|
|
|
3498
3535
|
colorsLayout = Q5.device.createBindGroupLayout({
|
|
3536
|
+
label: 'colorsLayout',
|
|
3499
3537
|
entries: [
|
|
3500
3538
|
{
|
|
3501
3539
|
binding: 0,
|
|
@@ -3726,10 +3764,17 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3726
3764
|
$.background = (r, g, b, a) => {
|
|
3727
3765
|
$.push();
|
|
3728
3766
|
$.resetMatrix();
|
|
3729
|
-
if (r.src)
|
|
3730
|
-
|
|
3767
|
+
if (r.src) {
|
|
3768
|
+
let og = $._imageMode;
|
|
3769
|
+
$._imageMode = 'corner';
|
|
3770
|
+
$.image(r, -c.hw, -c.hh, c.w, c.h);
|
|
3771
|
+
$._imageMode = og;
|
|
3772
|
+
} else {
|
|
3773
|
+
let og = $._rectMode;
|
|
3774
|
+
$._rectMode = 'corner';
|
|
3731
3775
|
$.fill(r, g, b, a);
|
|
3732
3776
|
$.rect(-c.hw, -c.hh, c.w, c.h);
|
|
3777
|
+
$._rectMode = og;
|
|
3733
3778
|
}
|
|
3734
3779
|
$.pop();
|
|
3735
3780
|
};
|
|
@@ -3837,7 +3882,7 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3837
3882
|
});
|
|
3838
3883
|
|
|
3839
3884
|
// set the bind group once before rendering
|
|
3840
|
-
$.pass.setBindGroup(
|
|
3885
|
+
$.pass.setBindGroup(1, $._colorsBindGroup);
|
|
3841
3886
|
});
|
|
3842
3887
|
|
|
3843
3888
|
$._hooks.postRender.push(() => {
|
|
@@ -3852,8 +3897,8 @@ Q5.renderers.webgpu.image = ($, q) => {
|
|
|
3852
3897
|
label: 'imageVertexShader',
|
|
3853
3898
|
code: `
|
|
3854
3899
|
struct VertexOutput {
|
|
3855
|
-
@builtin(position) position:
|
|
3856
|
-
@location(0) texCoord:
|
|
3900
|
+
@builtin(position) position: vec4f,
|
|
3901
|
+
@location(0) texCoord: vec2f
|
|
3857
3902
|
};
|
|
3858
3903
|
|
|
3859
3904
|
struct Uniforms {
|
|
@@ -3862,12 +3907,12 @@ struct Uniforms {
|
|
|
3862
3907
|
};
|
|
3863
3908
|
|
|
3864
3909
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
3865
|
-
@group(
|
|
3910
|
+
@group(0) @binding(1) var<storage, read> transforms: array<mat4x4<f32>>;
|
|
3866
3911
|
|
|
3867
3912
|
@vertex
|
|
3868
|
-
fn vertexMain(@location(0) pos:
|
|
3869
|
-
var vert =
|
|
3870
|
-
vert
|
|
3913
|
+
fn vertexMain(@location(0) pos: vec2f, @location(1) texCoord: vec2f, @location(2) transformIndex: f32) -> VertexOutput {
|
|
3914
|
+
var vert = vec4f(pos, 0.0, 1.0);
|
|
3915
|
+
vert = transforms[i32(transformIndex)] * vert;
|
|
3871
3916
|
vert.x /= uniforms.halfWidth;
|
|
3872
3917
|
vert.y /= uniforms.halfHeight;
|
|
3873
3918
|
|
|
@@ -3882,11 +3927,11 @@ fn vertexMain(@location(0) pos: vec2<f32>, @location(1) texCoord: vec2<f32>, @lo
|
|
|
3882
3927
|
let fragmentShader = Q5.device.createShaderModule({
|
|
3883
3928
|
label: 'imageFragmentShader',
|
|
3884
3929
|
code: `
|
|
3885
|
-
@group(
|
|
3886
|
-
@group(
|
|
3930
|
+
@group(2) @binding(0) var samp: sampler;
|
|
3931
|
+
@group(2) @binding(1) var texture: texture_2d<f32>;
|
|
3887
3932
|
|
|
3888
3933
|
@fragment
|
|
3889
|
-
fn fragmentMain(@location(0) texCoord:
|
|
3934
|
+
fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
|
3890
3935
|
// Sample the texture using the interpolated texture coordinate
|
|
3891
3936
|
return textureSample(texture, samp, texCoord);
|
|
3892
3937
|
}
|
|
@@ -3918,11 +3963,9 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
|
|
|
3918
3963
|
]
|
|
3919
3964
|
};
|
|
3920
3965
|
|
|
3921
|
-
$.bindGroupLayouts.push(textureLayout);
|
|
3922
|
-
|
|
3923
3966
|
const pipelineLayout = Q5.device.createPipelineLayout({
|
|
3924
3967
|
label: 'imagePipelineLayout',
|
|
3925
|
-
bindGroupLayouts:
|
|
3968
|
+
bindGroupLayouts: [...$.bindGroupLayouts, textureLayout]
|
|
3926
3969
|
});
|
|
3927
3970
|
|
|
3928
3971
|
$.pipelines[1] = Q5.device.createRenderPipeline({
|
|
@@ -4079,34 +4122,522 @@ Q5.DILATE = 6;
|
|
|
4079
4122
|
Q5.ERODE = 7;
|
|
4080
4123
|
Q5.BLUR = 8;
|
|
4081
4124
|
Q5.renderers.webgpu.text = ($, q) => {
|
|
4082
|
-
let
|
|
4083
|
-
|
|
4084
|
-
|
|
4125
|
+
let textShader = Q5.device.createShaderModule({
|
|
4126
|
+
label: 'MSDF text shader',
|
|
4127
|
+
code: `
|
|
4128
|
+
// Positions for simple quad geometry
|
|
4129
|
+
const pos = array(vec2f(0, -1), vec2f(1, -1), vec2f(0, 0), vec2f(1, 0));
|
|
4130
|
+
|
|
4131
|
+
struct VertexInput {
|
|
4132
|
+
@builtin(vertex_index) vertex : u32,
|
|
4133
|
+
@builtin(instance_index) instance : u32,
|
|
4134
|
+
};
|
|
4135
|
+
struct VertexOutput {
|
|
4136
|
+
@builtin(position) position : vec4f,
|
|
4137
|
+
@location(0) texcoord : vec2f,
|
|
4138
|
+
@location(1) colorIndex : f32
|
|
4139
|
+
};
|
|
4140
|
+
struct Char {
|
|
4141
|
+
texOffset: vec2f,
|
|
4142
|
+
texExtent: vec2f,
|
|
4143
|
+
size: vec2f,
|
|
4144
|
+
offset: vec2f,
|
|
4145
|
+
};
|
|
4146
|
+
struct Text {
|
|
4147
|
+
pos: vec2f,
|
|
4148
|
+
scale: f32,
|
|
4149
|
+
transformIndex: f32,
|
|
4150
|
+
fillIndex: f32,
|
|
4151
|
+
strokeIndex: f32
|
|
4152
|
+
};
|
|
4153
|
+
struct Uniforms {
|
|
4154
|
+
halfWidth: f32,
|
|
4155
|
+
halfHeight: f32
|
|
4156
|
+
};
|
|
4157
|
+
|
|
4158
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
4159
|
+
@group(0) @binding(1) var<storage, read> transforms: array<mat4x4<f32>>;
|
|
4160
|
+
|
|
4161
|
+
@group(1) @binding(0) var<storage, read> colors : array<vec4f>;
|
|
4162
|
+
|
|
4163
|
+
@group(2) @binding(0) var fontTexture: texture_2d<f32>;
|
|
4164
|
+
@group(2) @binding(1) var fontSampler: sampler;
|
|
4165
|
+
@group(2) @binding(2) var<storage> fontChars: array<Char>;
|
|
4166
|
+
|
|
4167
|
+
@group(3) @binding(0) var<storage> textChars: array<vec4f>;
|
|
4168
|
+
@group(3) @binding(1) var<storage> textMetadata: array<Text>;
|
|
4169
|
+
|
|
4170
|
+
@vertex
|
|
4171
|
+
fn vertexMain(input : VertexInput) -> VertexOutput {
|
|
4172
|
+
let char = textChars[input.instance];
|
|
4173
|
+
|
|
4174
|
+
let text = textMetadata[i32(char.w)];
|
|
4175
|
+
|
|
4176
|
+
let fontChar = fontChars[i32(char.z)];
|
|
4177
|
+
|
|
4178
|
+
let charPos = ((pos[input.vertex] * fontChar.size + char.xy + fontChar.offset) * text.scale) + text.pos;
|
|
4179
|
+
|
|
4180
|
+
var vert = vec4f(charPos, 0.0, 1.0);
|
|
4181
|
+
vert = transforms[i32(text.transformIndex)] * vert;
|
|
4182
|
+
vert.x /= uniforms.halfWidth;
|
|
4183
|
+
vert.y /= uniforms.halfHeight;
|
|
4184
|
+
|
|
4185
|
+
var output : VertexOutput;
|
|
4186
|
+
output.position = vert;
|
|
4187
|
+
output.texcoord = (pos[input.vertex] * vec2f(1, -1)) * fontChar.texExtent + fontChar.texOffset;
|
|
4188
|
+
output.colorIndex = text.fillIndex;
|
|
4189
|
+
return output;
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
fn sampleMsdf(texcoord: vec2f) -> f32 {
|
|
4193
|
+
let c = textureSample(fontTexture, fontSampler, texcoord);
|
|
4194
|
+
return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
|
|
4195
|
+
}
|
|
4196
|
+
|
|
4197
|
+
@fragment
|
|
4198
|
+
fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
4199
|
+
// pxRange (AKA distanceRange) comes from the msdfgen tool,
|
|
4200
|
+
// uses the default which is 4.
|
|
4201
|
+
let pxRange = 4.0;
|
|
4202
|
+
let sz = vec2f(textureDimensions(fontTexture, 0));
|
|
4203
|
+
let dx = sz.x*length(vec2f(dpdxFine(input.texcoord.x), dpdyFine(input.texcoord.x)));
|
|
4204
|
+
let dy = sz.y*length(vec2f(dpdxFine(input.texcoord.y), dpdyFine(input.texcoord.y)));
|
|
4205
|
+
let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
|
|
4206
|
+
let sigDist = sampleMsdf(input.texcoord) - 0.5;
|
|
4207
|
+
let pxDist = sigDist * toPixels;
|
|
4208
|
+
let edgeWidth = 0.5;
|
|
4209
|
+
let alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
|
|
4210
|
+
if (alpha < 0.001) {
|
|
4211
|
+
discard;
|
|
4212
|
+
}
|
|
4213
|
+
let fillColor = colors[i32(input.colorIndex)];
|
|
4214
|
+
return vec4f(fillColor.rgb, fillColor.a * alpha);
|
|
4215
|
+
}
|
|
4216
|
+
`
|
|
4217
|
+
});
|
|
4218
|
+
|
|
4219
|
+
class MsdfFont {
|
|
4220
|
+
constructor(pipeline, bindGroup, lineHeight, chars, kernings) {
|
|
4221
|
+
this.pipeline = pipeline;
|
|
4222
|
+
this.bindGroup = bindGroup;
|
|
4223
|
+
this.lineHeight = lineHeight;
|
|
4224
|
+
this.chars = chars;
|
|
4225
|
+
this.kernings = kernings;
|
|
4226
|
+
let charArray = Object.values(chars);
|
|
4227
|
+
this.charCount = charArray.length;
|
|
4228
|
+
this.defaultChar = charArray[0];
|
|
4229
|
+
}
|
|
4230
|
+
getChar(charCode) {
|
|
4231
|
+
return this.chars[charCode] ?? this.defaultChar;
|
|
4232
|
+
}
|
|
4233
|
+
// Gets the distance in pixels a line should advance for a given character code. If the upcoming
|
|
4234
|
+
// character code is given any kerning between the two characters will be taken into account.
|
|
4235
|
+
getXAdvance(charCode, nextCharCode = -1) {
|
|
4236
|
+
let char = this.getChar(charCode);
|
|
4237
|
+
if (nextCharCode >= 0) {
|
|
4238
|
+
let kerning = this.kernings.get(charCode);
|
|
4239
|
+
if (kerning) {
|
|
4240
|
+
return char.xadvance + (kerning.get(nextCharCode) ?? 0);
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
return char.xadvance;
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4085
4246
|
|
|
4086
|
-
|
|
4247
|
+
let textBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
4248
|
+
label: 'MSDF text group layout',
|
|
4249
|
+
entries: [
|
|
4250
|
+
{
|
|
4251
|
+
binding: 0,
|
|
4252
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
4253
|
+
buffer: { type: 'read-only-storage' }
|
|
4254
|
+
},
|
|
4255
|
+
{
|
|
4256
|
+
binding: 1,
|
|
4257
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
4258
|
+
buffer: { type: 'read-only-storage' }
|
|
4259
|
+
}
|
|
4260
|
+
]
|
|
4261
|
+
});
|
|
4262
|
+
|
|
4263
|
+
let fonts = {};
|
|
4264
|
+
|
|
4265
|
+
let createFont = async (fontJsonUrl, fontName, cb) => {
|
|
4087
4266
|
q._preloadCount++;
|
|
4088
|
-
|
|
4267
|
+
|
|
4268
|
+
let res = await fetch(fontJsonUrl);
|
|
4269
|
+
if (res.status == 404) {
|
|
4089
4270
|
q._preloadCount--;
|
|
4271
|
+
return '';
|
|
4272
|
+
}
|
|
4273
|
+
let atlas = await res.json();
|
|
4274
|
+
|
|
4275
|
+
let slashIdx = fontJsonUrl.lastIndexOf('/');
|
|
4276
|
+
let baseUrl = slashIdx != -1 ? fontJsonUrl.substring(0, slashIdx + 1) : '';
|
|
4277
|
+
// load font image
|
|
4278
|
+
res = await fetch(baseUrl + atlas.pages[0]);
|
|
4279
|
+
let img = await createImageBitmap(await res.blob());
|
|
4280
|
+
|
|
4281
|
+
// convert image to texture
|
|
4282
|
+
let imgSize = [img.width, img.height, 1];
|
|
4283
|
+
let texture = Q5.device.createTexture({
|
|
4284
|
+
label: `MSDF ${fontName}`,
|
|
4285
|
+
size: imgSize,
|
|
4286
|
+
format: 'rgba8unorm',
|
|
4287
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
4090
4288
|
});
|
|
4289
|
+
Q5.device.queue.copyExternalImageToTexture({ source: img }, { texture }, imgSize);
|
|
4290
|
+
|
|
4291
|
+
// to make q5's default font file smaller,
|
|
4292
|
+
// the chars and kernings are stored as csv strings
|
|
4293
|
+
if (typeof atlas.chars == 'string') {
|
|
4294
|
+
atlas.chars = $.CSV.parse(atlas.chars, ' ');
|
|
4295
|
+
atlas.kernings = $.CSV.parse(atlas.kernings, ' ');
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
let charCount = atlas.chars.length;
|
|
4299
|
+
let charsBuffer = Q5.device.createBuffer({
|
|
4300
|
+
size: charCount * 32,
|
|
4301
|
+
usage: GPUBufferUsage.STORAGE,
|
|
4302
|
+
mappedAtCreation: true
|
|
4303
|
+
});
|
|
4304
|
+
|
|
4305
|
+
let fontChars = new Float32Array(charsBuffer.getMappedRange());
|
|
4306
|
+
let u = 1 / atlas.common.scaleW;
|
|
4307
|
+
let v = 1 / atlas.common.scaleH;
|
|
4308
|
+
let chars = {};
|
|
4309
|
+
let o = 0; // offset
|
|
4310
|
+
for (let [i, char] of atlas.chars.entries()) {
|
|
4311
|
+
chars[char.id] = char;
|
|
4312
|
+
chars[char.id].charIndex = i;
|
|
4313
|
+
fontChars[o] = char.x * u; // texOffset.x
|
|
4314
|
+
fontChars[o + 1] = char.y * v; // texOffset.y
|
|
4315
|
+
fontChars[o + 2] = char.width * u; // texExtent.x
|
|
4316
|
+
fontChars[o + 3] = char.height * v; // texExtent.y
|
|
4317
|
+
fontChars[o + 4] = char.width; // size.x
|
|
4318
|
+
fontChars[o + 5] = char.height; // size.y
|
|
4319
|
+
fontChars[o + 6] = char.xoffset; // offset.x
|
|
4320
|
+
fontChars[o + 7] = -char.yoffset; // offset.y
|
|
4321
|
+
o += 8;
|
|
4322
|
+
}
|
|
4323
|
+
charsBuffer.unmap();
|
|
4324
|
+
|
|
4325
|
+
let fontSampler = Q5.device.createSampler({
|
|
4326
|
+
minFilter: 'linear',
|
|
4327
|
+
magFilter: 'linear',
|
|
4328
|
+
mipmapFilter: 'linear',
|
|
4329
|
+
maxAnisotropy: 16
|
|
4330
|
+
});
|
|
4331
|
+
let fontBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
4332
|
+
label: 'MSDF font group layout',
|
|
4333
|
+
entries: [
|
|
4334
|
+
{
|
|
4335
|
+
binding: 0,
|
|
4336
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
4337
|
+
texture: {}
|
|
4338
|
+
},
|
|
4339
|
+
{
|
|
4340
|
+
binding: 1,
|
|
4341
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
4342
|
+
sampler: {}
|
|
4343
|
+
},
|
|
4344
|
+
{
|
|
4345
|
+
binding: 2,
|
|
4346
|
+
visibility: GPUShaderStage.VERTEX,
|
|
4347
|
+
buffer: { type: 'read-only-storage' }
|
|
4348
|
+
}
|
|
4349
|
+
]
|
|
4350
|
+
});
|
|
4351
|
+
let fontPipeline = Q5.device.createRenderPipeline({
|
|
4352
|
+
label: 'msdf font pipeline',
|
|
4353
|
+
layout: Q5.device.createPipelineLayout({
|
|
4354
|
+
bindGroupLayouts: [...$.bindGroupLayouts, fontBindGroupLayout, textBindGroupLayout]
|
|
4355
|
+
}),
|
|
4356
|
+
vertex: {
|
|
4357
|
+
module: textShader,
|
|
4358
|
+
entryPoint: 'vertexMain'
|
|
4359
|
+
},
|
|
4360
|
+
fragment: {
|
|
4361
|
+
module: textShader,
|
|
4362
|
+
entryPoint: 'fragmentMain',
|
|
4363
|
+
targets: [
|
|
4364
|
+
{
|
|
4365
|
+
format: 'bgra8unorm',
|
|
4366
|
+
blend: {
|
|
4367
|
+
color: {
|
|
4368
|
+
srcFactor: 'src-alpha',
|
|
4369
|
+
dstFactor: 'one-minus-src-alpha'
|
|
4370
|
+
},
|
|
4371
|
+
alpha: {
|
|
4372
|
+
srcFactor: 'one',
|
|
4373
|
+
dstFactor: 'one'
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
]
|
|
4378
|
+
},
|
|
4379
|
+
primitive: {
|
|
4380
|
+
topology: 'triangle-strip',
|
|
4381
|
+
stripIndexFormat: 'uint32'
|
|
4382
|
+
}
|
|
4383
|
+
});
|
|
4384
|
+
|
|
4385
|
+
let fontBindGroup = Q5.device.createBindGroup({
|
|
4386
|
+
label: 'msdf font bind group',
|
|
4387
|
+
layout: fontBindGroupLayout,
|
|
4388
|
+
entries: [
|
|
4389
|
+
{
|
|
4390
|
+
binding: 0,
|
|
4391
|
+
resource: texture.createView()
|
|
4392
|
+
},
|
|
4393
|
+
{ binding: 1, resource: fontSampler },
|
|
4394
|
+
{ binding: 2, resource: { buffer: charsBuffer } }
|
|
4395
|
+
]
|
|
4396
|
+
});
|
|
4397
|
+
|
|
4398
|
+
let kernings = new Map();
|
|
4399
|
+
if (atlas.kernings) {
|
|
4400
|
+
for (let kerning of atlas.kernings) {
|
|
4401
|
+
let charKerning = kernings.get(kerning.first);
|
|
4402
|
+
if (!charKerning) {
|
|
4403
|
+
charKerning = new Map();
|
|
4404
|
+
kernings.set(kerning.first, charKerning);
|
|
4405
|
+
}
|
|
4406
|
+
charKerning.set(kerning.second, kerning.amount);
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
$._font = new MsdfFont(fontPipeline, fontBindGroup, atlas.common.lineHeight, chars, kernings);
|
|
4411
|
+
|
|
4412
|
+
fonts[fontName] = $._font;
|
|
4413
|
+
$.pipelines[2] = $._font.pipeline;
|
|
4414
|
+
|
|
4415
|
+
q._preloadCount--;
|
|
4416
|
+
|
|
4417
|
+
if (cb) cb(fontName);
|
|
4091
4418
|
};
|
|
4092
4419
|
|
|
4093
|
-
//
|
|
4094
|
-
|
|
4095
|
-
$.
|
|
4096
|
-
$.textLeading = t.textLeading;
|
|
4097
|
-
$.textStyle = t.textStyle;
|
|
4098
|
-
$.textAlign = t.textAlign;
|
|
4099
|
-
$.textWidth = t.textWidth;
|
|
4100
|
-
$.textAscent = t.textAscent;
|
|
4101
|
-
$.textDescent = t.textDescent;
|
|
4420
|
+
// q2d graphics context to use for text image creation
|
|
4421
|
+
let g = $.createGraphics(1, 1);
|
|
4422
|
+
g.colorMode($.RGB, 1);
|
|
4102
4423
|
|
|
4103
|
-
$.
|
|
4104
|
-
|
|
4424
|
+
$.loadFont = (url, cb) => {
|
|
4425
|
+
let ext = url.slice(url.lastIndexOf('.') + 1);
|
|
4426
|
+
if (ext != 'json') return g.loadFont(url, cb);
|
|
4427
|
+
let fontName = url.slice(url.lastIndexOf('/') + 1, url.lastIndexOf('-'));
|
|
4428
|
+
createFont(url, fontName, cb);
|
|
4429
|
+
return fontName;
|
|
4430
|
+
};
|
|
4431
|
+
|
|
4432
|
+
$._textSize = 18;
|
|
4433
|
+
$._textAlign = 'left';
|
|
4434
|
+
$._textBaseline = 'alphabetic';
|
|
4435
|
+
let leadingSet = false,
|
|
4436
|
+
leading = 22.5,
|
|
4437
|
+
leadDiff = 4.5,
|
|
4438
|
+
leadPercent = 1.25;
|
|
4439
|
+
|
|
4440
|
+
$.textFont = (fontName) => {
|
|
4441
|
+
$._font = fonts[fontName];
|
|
4442
|
+
|
|
4443
|
+
// replay the change of font in the draw stack
|
|
4444
|
+
$.drawStack.push(-1, () => {
|
|
4445
|
+
$._font = fonts[fontName];
|
|
4446
|
+
$.pipelines[2] = $._font.pipeline;
|
|
4447
|
+
});
|
|
4448
|
+
};
|
|
4449
|
+
$.textSize = (size) => {
|
|
4450
|
+
$._textSize = size;
|
|
4451
|
+
if (!leadingSet) {
|
|
4452
|
+
leading = size * leadPercent;
|
|
4453
|
+
leadDiff = leading - size;
|
|
4454
|
+
}
|
|
4455
|
+
};
|
|
4456
|
+
$.textLeading = (lineHeight) => {
|
|
4457
|
+
$._font.lineHeight = leading = lineHeight;
|
|
4458
|
+
leadDiff = leading - $._textSize;
|
|
4459
|
+
leadPercent = leading / $._textSize;
|
|
4460
|
+
leadingSet = true;
|
|
4461
|
+
};
|
|
4462
|
+
$.textAlign = (horiz, vert) => {
|
|
4463
|
+
$._textAlign = horiz;
|
|
4464
|
+
if (vert) $._textBaseline = vert;
|
|
4465
|
+
};
|
|
4466
|
+
|
|
4467
|
+
$._charStack = [];
|
|
4468
|
+
$._textStack = [];
|
|
4469
|
+
|
|
4470
|
+
let measureText = (font, text, charCallback) => {
|
|
4471
|
+
let maxWidth = 0,
|
|
4472
|
+
offsetX = 0,
|
|
4473
|
+
offsetY = 0,
|
|
4474
|
+
line = 0,
|
|
4475
|
+
printedCharCount = 0,
|
|
4476
|
+
lineWidths = [],
|
|
4477
|
+
nextCharCode = text.charCodeAt(0);
|
|
4478
|
+
|
|
4479
|
+
for (let i = 0; i < text.length; ++i) {
|
|
4480
|
+
let charCode = nextCharCode;
|
|
4481
|
+
nextCharCode = i < text.length - 1 ? text.charCodeAt(i + 1) : -1;
|
|
4482
|
+
switch (charCode) {
|
|
4483
|
+
case 10: // Newline
|
|
4484
|
+
lineWidths.push(offsetX);
|
|
4485
|
+
line++;
|
|
4486
|
+
maxWidth = Math.max(maxWidth, offsetX);
|
|
4487
|
+
offsetX = 0;
|
|
4488
|
+
offsetY -= font.lineHeight * leadPercent;
|
|
4489
|
+
break;
|
|
4490
|
+
case 13: // CR
|
|
4491
|
+
break;
|
|
4492
|
+
case 32: // Space
|
|
4493
|
+
// advance the offset without actually adding a character
|
|
4494
|
+
offsetX += font.getXAdvance(charCode);
|
|
4495
|
+
break;
|
|
4496
|
+
case 9: // Tab
|
|
4497
|
+
offsetX += font.getXAdvance(charCode) * 2;
|
|
4498
|
+
break;
|
|
4499
|
+
default:
|
|
4500
|
+
if (charCallback) {
|
|
4501
|
+
charCallback(offsetX, offsetY, line, font.getChar(charCode));
|
|
4502
|
+
}
|
|
4503
|
+
offsetX += font.getXAdvance(charCode, nextCharCode);
|
|
4504
|
+
printedCharCount++;
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
lineWidths.push(offsetX);
|
|
4508
|
+
maxWidth = Math.max(maxWidth, offsetX);
|
|
4509
|
+
return {
|
|
4510
|
+
width: maxWidth,
|
|
4511
|
+
height: lineWidths.length * font.lineHeight * leadPercent,
|
|
4512
|
+
lineWidths,
|
|
4513
|
+
printedCharCount
|
|
4514
|
+
};
|
|
4515
|
+
};
|
|
4516
|
+
|
|
4517
|
+
let initLoadDefaultFont;
|
|
4105
4518
|
|
|
4106
4519
|
$.text = (str, x, y, w, h) => {
|
|
4107
|
-
|
|
4520
|
+
if (!$._font) {
|
|
4521
|
+
// check if online and loading the default font hasn't been attempted yet
|
|
4522
|
+
if (navigator.onLine && !initLoadDefaultFont) {
|
|
4523
|
+
initLoadDefaultFont = true;
|
|
4524
|
+
$.loadFont('https://q5js.org/fonts/YaHei-msdf.json');
|
|
4525
|
+
}
|
|
4526
|
+
return;
|
|
4527
|
+
}
|
|
4108
4528
|
|
|
4109
|
-
if (
|
|
4529
|
+
if (str.length > w) {
|
|
4530
|
+
let wrapped = [];
|
|
4531
|
+
let i = 0;
|
|
4532
|
+
while (i < str.length) {
|
|
4533
|
+
let max = i + w;
|
|
4534
|
+
if (max >= str.length) {
|
|
4535
|
+
wrapped.push(str.slice(i));
|
|
4536
|
+
break;
|
|
4537
|
+
}
|
|
4538
|
+
let end = str.lastIndexOf(' ', max);
|
|
4539
|
+
if (end == -1 || end < i) end = max;
|
|
4540
|
+
wrapped.push(str.slice(i, end));
|
|
4541
|
+
i = end + 1;
|
|
4542
|
+
}
|
|
4543
|
+
str = wrapped.join('\n');
|
|
4544
|
+
}
|
|
4545
|
+
|
|
4546
|
+
let spaces = 0, // whitespace char count, not literal spaces
|
|
4547
|
+
hasNewline;
|
|
4548
|
+
for (let i = 0; i < str.length; i++) {
|
|
4549
|
+
let c = str[i];
|
|
4550
|
+
switch (c) {
|
|
4551
|
+
case '\n':
|
|
4552
|
+
hasNewline = true;
|
|
4553
|
+
case '\r':
|
|
4554
|
+
case '\t':
|
|
4555
|
+
case ' ':
|
|
4556
|
+
spaces++;
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
|
|
4560
|
+
let charsData = new Float32Array((str.length - spaces) * 4);
|
|
4561
|
+
|
|
4562
|
+
let ta = $._textAlign,
|
|
4563
|
+
tb = $._textBaseline,
|
|
4564
|
+
textIndex = $._textStack.length,
|
|
4565
|
+
o = 0, // offset
|
|
4566
|
+
measurements;
|
|
4567
|
+
|
|
4568
|
+
if (ta == 'left' && !hasNewline) {
|
|
4569
|
+
measurements = measureText($._font, str, (textX, textY, line, char) => {
|
|
4570
|
+
charsData[o] = textX;
|
|
4571
|
+
charsData[o + 1] = textY;
|
|
4572
|
+
charsData[o + 2] = char.charIndex;
|
|
4573
|
+
charsData[o + 3] = textIndex;
|
|
4574
|
+
o += 4;
|
|
4575
|
+
});
|
|
4576
|
+
|
|
4577
|
+
if (tb == 'alphabetic') y -= $._textSize;
|
|
4578
|
+
else if (tb == 'center') y -= $._textSize * 0.5;
|
|
4579
|
+
else if (tb == 'bottom') y -= leading;
|
|
4580
|
+
} else {
|
|
4581
|
+
// measure the text to get the line widths before setting
|
|
4582
|
+
// the x position to properly align the text
|
|
4583
|
+
measurements = measureText($._font, str);
|
|
4584
|
+
|
|
4585
|
+
let offsetY = 0;
|
|
4586
|
+
if (tb == 'alphabetic') y -= $._textSize;
|
|
4587
|
+
else if (tb == 'center') offsetY = measurements.height * 0.5;
|
|
4588
|
+
else if (tb == 'bottom') offsetY = measurements.height;
|
|
4589
|
+
|
|
4590
|
+
measureText($._font, str, (textX, textY, line, char) => {
|
|
4591
|
+
let offsetX = 0;
|
|
4592
|
+
if (ta == 'center') {
|
|
4593
|
+
offsetX = measurements.width * -0.5 - (measurements.width - measurements.lineWidths[line]) * -0.5;
|
|
4594
|
+
} else if (ta == 'right') {
|
|
4595
|
+
offsetX = measurements.width - measurements.lineWidths[line];
|
|
4596
|
+
}
|
|
4597
|
+
charsData[o] = textX + offsetX;
|
|
4598
|
+
charsData[o + 1] = textY + offsetY;
|
|
4599
|
+
charsData[o + 2] = char.charIndex;
|
|
4600
|
+
charsData[o + 3] = textIndex;
|
|
4601
|
+
o += 4;
|
|
4602
|
+
});
|
|
4603
|
+
}
|
|
4604
|
+
$._charStack.push(charsData);
|
|
4605
|
+
|
|
4606
|
+
let text = new Float32Array(6);
|
|
4607
|
+
|
|
4608
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
4609
|
+
|
|
4610
|
+
text[0] = x;
|
|
4611
|
+
text[1] = -y;
|
|
4612
|
+
text[2] = $._textSize / 44;
|
|
4613
|
+
text[3] = $._transformIndex;
|
|
4614
|
+
text[4] = $._fillIndex;
|
|
4615
|
+
text[5] = $._strokeIndex;
|
|
4616
|
+
|
|
4617
|
+
$._textStack.push(text);
|
|
4618
|
+
$.drawStack.push(2, measurements.printedCharCount);
|
|
4619
|
+
};
|
|
4620
|
+
|
|
4621
|
+
$.textWidth = (str) => {
|
|
4622
|
+
if (!$._font) return 0;
|
|
4623
|
+
return measureText($._font, str).width;
|
|
4624
|
+
};
|
|
4625
|
+
|
|
4626
|
+
$.createTextImage = (str, w, h) => {
|
|
4627
|
+
g.textSize($._textSize);
|
|
4628
|
+
|
|
4629
|
+
if ($._doFill) {
|
|
4630
|
+
let fi = $._fillIndex * 4;
|
|
4631
|
+
g.fill(colorsStack.slice(fi, fi + 4));
|
|
4632
|
+
}
|
|
4633
|
+
if ($._doStroke) {
|
|
4634
|
+
let si = $._strokeIndex * 4;
|
|
4635
|
+
g.stroke(colorsStack.slice(si, si + 4));
|
|
4636
|
+
}
|
|
4637
|
+
|
|
4638
|
+
let img = g.createTextImage(str, w, h);
|
|
4639
|
+
|
|
4640
|
+
if (img.canvas.textureIndex == undefined) {
|
|
4110
4641
|
$._createTexture(img);
|
|
4111
4642
|
} else if (img.modified) {
|
|
4112
4643
|
let cnv = img.canvas;
|
|
@@ -4120,27 +4651,94 @@ Q5.renderers.webgpu.text = ($, q) => {
|
|
|
4120
4651
|
);
|
|
4121
4652
|
img.modified = false;
|
|
4122
4653
|
}
|
|
4123
|
-
|
|
4124
|
-
$.textImage(img, x, y);
|
|
4654
|
+
return img;
|
|
4125
4655
|
};
|
|
4126
4656
|
|
|
4127
|
-
$.createTextImage = t.createTextImage;
|
|
4128
|
-
|
|
4129
4657
|
$.textImage = (img, x, y) => {
|
|
4658
|
+
if (typeof img == 'string') img = $.createTextImage(img);
|
|
4659
|
+
|
|
4130
4660
|
let og = $._imageMode;
|
|
4131
4661
|
$._imageMode = 'corner';
|
|
4132
4662
|
|
|
4133
|
-
let ta =
|
|
4663
|
+
let ta = $._textAlign;
|
|
4134
4664
|
if (ta == 'center') x -= img.canvas.hw;
|
|
4135
4665
|
else if (ta == 'right') x -= img.width;
|
|
4136
4666
|
|
|
4137
|
-
let bl =
|
|
4138
|
-
if (bl == 'alphabetic') y -=
|
|
4139
|
-
else if (bl == '
|
|
4667
|
+
let bl = $._textBaseline;
|
|
4668
|
+
if (bl == 'alphabetic') y -= img._leading;
|
|
4669
|
+
else if (bl == 'center') y -= img._middle;
|
|
4140
4670
|
else if (bl == 'bottom') y -= img._bottom;
|
|
4141
4671
|
else if (bl == 'top') y -= img._top;
|
|
4142
4672
|
|
|
4143
4673
|
$.image(img, x, y);
|
|
4144
4674
|
$._imageMode = og;
|
|
4145
4675
|
};
|
|
4676
|
+
|
|
4677
|
+
$._hooks.preRender.push(() => {
|
|
4678
|
+
if (!$._charStack.length) return;
|
|
4679
|
+
|
|
4680
|
+
// Calculate total buffer size for text data
|
|
4681
|
+
let totalTextSize = 0;
|
|
4682
|
+
for (let charsData of $._charStack) {
|
|
4683
|
+
totalTextSize += charsData.length * 4;
|
|
4684
|
+
}
|
|
4685
|
+
|
|
4686
|
+
// Create a single buffer for all text data
|
|
4687
|
+
let charBuffer = Q5.device.createBuffer({
|
|
4688
|
+
label: 'charBuffer',
|
|
4689
|
+
size: totalTextSize,
|
|
4690
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
4691
|
+
mappedAtCreation: true
|
|
4692
|
+
});
|
|
4693
|
+
|
|
4694
|
+
// Copy all text data into the buffer
|
|
4695
|
+
let textArray = new Float32Array(charBuffer.getMappedRange());
|
|
4696
|
+
let o = 0;
|
|
4697
|
+
for (let array of $._charStack) {
|
|
4698
|
+
textArray.set(array, o);
|
|
4699
|
+
o += array.length;
|
|
4700
|
+
}
|
|
4701
|
+
charBuffer.unmap();
|
|
4702
|
+
|
|
4703
|
+
// Calculate total buffer size for metadata
|
|
4704
|
+
let totalMetadataSize = $._textStack.length * 6 * 4;
|
|
4705
|
+
|
|
4706
|
+
// Create a single buffer for all metadata
|
|
4707
|
+
let textBuffer = Q5.device.createBuffer({
|
|
4708
|
+
label: 'textBuffer',
|
|
4709
|
+
size: totalMetadataSize,
|
|
4710
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
4711
|
+
mappedAtCreation: true
|
|
4712
|
+
});
|
|
4713
|
+
|
|
4714
|
+
// Copy all metadata into the buffer
|
|
4715
|
+
let metadataArray = new Float32Array(textBuffer.getMappedRange());
|
|
4716
|
+
o = 0;
|
|
4717
|
+
for (let array of $._textStack) {
|
|
4718
|
+
metadataArray.set(array, o);
|
|
4719
|
+
o += array.length;
|
|
4720
|
+
}
|
|
4721
|
+
textBuffer.unmap();
|
|
4722
|
+
|
|
4723
|
+
// Create a single bind group for the text buffer and metadata buffer
|
|
4724
|
+
$._textBindGroup = Q5.device.createBindGroup({
|
|
4725
|
+
label: 'msdf text bind group',
|
|
4726
|
+
layout: textBindGroupLayout,
|
|
4727
|
+
entries: [
|
|
4728
|
+
{
|
|
4729
|
+
binding: 0,
|
|
4730
|
+
resource: { buffer: charBuffer }
|
|
4731
|
+
},
|
|
4732
|
+
{
|
|
4733
|
+
binding: 1,
|
|
4734
|
+
resource: { buffer: textBuffer }
|
|
4735
|
+
}
|
|
4736
|
+
]
|
|
4737
|
+
});
|
|
4738
|
+
});
|
|
4739
|
+
|
|
4740
|
+
$._hooks.postRender.push(() => {
|
|
4741
|
+
$._charStack.length = 0;
|
|
4742
|
+
$._textStack.length = 0;
|
|
4743
|
+
});
|
|
4146
4744
|
};
|