q5 2.4.5 → 2.5.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/README.md +5 -2
- package/package.json +1 -1
- package/q5.d.ts +27 -4
- package/q5.js +939 -275
- package/q5.min.js +1 -1
- package/src/q5-2d-canvas.js +10 -19
- package/src/q5-2d-text.js +162 -127
- 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 +81 -64
- package/src/q5-webgpu-drawing.js +21 -13
- package/src/q5-webgpu-image.js +33 -16
- package/src/q5-webgpu-text.js +601 -25
- package/src/readme.md +76 -9
package/src/q5-2d-text.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
Q5.renderers.q2d.text = ($, q) => {
|
|
2
|
-
$.
|
|
2
|
+
$._textAlign = 'left';
|
|
3
|
+
$._textBaseline = 'alphabetic';
|
|
3
4
|
$._textSize = 12;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
let font = 'sans-serif',
|
|
7
|
+
leadingSet = false,
|
|
8
|
+
leading = 15,
|
|
9
|
+
leadDiff = 3,
|
|
10
|
+
emphasis = 'normal',
|
|
11
|
+
fontMod = false,
|
|
12
|
+
styleHash = 0,
|
|
13
|
+
styleHashes = [],
|
|
14
|
+
useCache = false,
|
|
15
|
+
genTextImage = false,
|
|
16
|
+
cacheSize = 0,
|
|
17
|
+
cacheMax = 12000;
|
|
18
|
+
|
|
19
|
+
let cache = ($._textCache = {});
|
|
7
20
|
|
|
8
21
|
$.loadFont = (url, cb) => {
|
|
9
22
|
q._preloadCount++;
|
|
@@ -16,156 +29,160 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
16
29
|
});
|
|
17
30
|
return name;
|
|
18
31
|
};
|
|
19
|
-
|
|
32
|
+
|
|
33
|
+
$.textFont = (x) => {
|
|
34
|
+
font = x;
|
|
35
|
+
fontMod = true;
|
|
36
|
+
styleHash = -1;
|
|
37
|
+
};
|
|
20
38
|
$.textSize = (x) => {
|
|
21
39
|
if (x === undefined) return $._textSize;
|
|
22
40
|
if ($._da) x *= $._da;
|
|
23
41
|
$._textSize = x;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
fontMod = true;
|
|
43
|
+
styleHash = -1;
|
|
44
|
+
if (!leadingSet) {
|
|
45
|
+
leading = x * 1.25;
|
|
46
|
+
leadDiff = leading - x;
|
|
27
47
|
}
|
|
28
48
|
};
|
|
49
|
+
$.textStyle = (x) => {
|
|
50
|
+
emphasis = x;
|
|
51
|
+
fontMod = true;
|
|
52
|
+
styleHash = -1;
|
|
53
|
+
};
|
|
29
54
|
$.textLeading = (x) => {
|
|
30
|
-
if (x === undefined) return
|
|
55
|
+
if (x === undefined) return leading;
|
|
31
56
|
if ($._da) x *= $._da;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
57
|
+
leading = x;
|
|
58
|
+
leadDiff = x - $._textSize;
|
|
59
|
+
leadingSet = true;
|
|
60
|
+
styleHash = -1;
|
|
35
61
|
};
|
|
36
|
-
$.textStyle = (x) => ($._textStyle = x);
|
|
37
62
|
$.textAlign = (horiz, vert) => {
|
|
38
|
-
$.ctx.textAlign = horiz;
|
|
63
|
+
$.ctx.textAlign = $._textAlign = horiz;
|
|
39
64
|
if (vert) {
|
|
40
|
-
$.ctx.textBaseline = vert == $.CENTER ? 'middle' : vert;
|
|
65
|
+
$.ctx.textBaseline = $._textBaseline = vert == $.CENTER ? 'middle' : vert;
|
|
41
66
|
}
|
|
42
67
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
$.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
|
|
49
|
-
return $.ctx.measureText(str).actualBoundingBoxAscent;
|
|
50
|
-
};
|
|
51
|
-
$.textDescent = (str) => {
|
|
52
|
-
$.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
|
|
53
|
-
return $.ctx.measureText(str).actualBoundingBoxDescent;
|
|
54
|
-
};
|
|
68
|
+
|
|
69
|
+
$.textWidth = (str) => $.ctx.measureText(str).width;
|
|
70
|
+
$.textAscent = (str) => $.ctx.measureText(str).actualBoundingBoxAscent;
|
|
71
|
+
$.textDescent = (str) => $.ctx.measureText(str).actualBoundingBoxDescent;
|
|
72
|
+
|
|
55
73
|
$.textFill = $.fill;
|
|
56
74
|
$.textStroke = $.stroke;
|
|
57
75
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
set(k, v) {
|
|
65
|
-
v.lastAccessed = Date.now();
|
|
66
|
-
super.set(k, v);
|
|
67
|
-
if (this.size > this.maxSize) this.gc();
|
|
68
|
-
}
|
|
69
|
-
get(k) {
|
|
70
|
-
const v = super.get(k);
|
|
71
|
-
if (v) v.lastAccessed = Date.now();
|
|
72
|
-
return v;
|
|
73
|
-
}
|
|
74
|
-
gc() {
|
|
75
|
-
let t = Infinity;
|
|
76
|
-
let oldest;
|
|
77
|
-
let i = 0;
|
|
78
|
-
for (const [k, v] of this.entries()) {
|
|
79
|
-
if (v.lastAccessed < t) {
|
|
80
|
-
t = v.lastAccessed;
|
|
81
|
-
oldest = i;
|
|
82
|
-
}
|
|
83
|
-
i++;
|
|
84
|
-
}
|
|
85
|
-
i = oldest;
|
|
86
|
-
for (const k of this.keys()) {
|
|
87
|
-
if (i == 0) {
|
|
88
|
-
oldest = k;
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
i--;
|
|
92
|
-
}
|
|
93
|
-
this.delete(oldest);
|
|
76
|
+
let updateStyleHash = () => {
|
|
77
|
+
let styleString = font + $._textSize + emphasis + leading;
|
|
78
|
+
|
|
79
|
+
let hash = 5381;
|
|
80
|
+
for (let i = 0; i < styleString.length; i++) {
|
|
81
|
+
hash = (hash * 33) ^ styleString.charCodeAt(i);
|
|
94
82
|
}
|
|
83
|
+
styleHash = hash >>> 0;
|
|
95
84
|
};
|
|
96
|
-
|
|
97
|
-
$.textCache = (
|
|
98
|
-
if (maxSize)
|
|
99
|
-
if (
|
|
100
|
-
return
|
|
101
|
-
};
|
|
102
|
-
$._genTextImageKey = (str, w, h) => {
|
|
103
|
-
return (
|
|
104
|
-
str.slice(0, 200) +
|
|
105
|
-
$._textStyle +
|
|
106
|
-
$._textSize +
|
|
107
|
-
$._textFont +
|
|
108
|
-
($._doFill ? $.ctx.fillStyle : '') +
|
|
109
|
-
'_' +
|
|
110
|
-
($._doStroke && $._strokeSet ? $.ctx.lineWidth + $.ctx.strokeStyle + '_' : '') +
|
|
111
|
-
(w || '') +
|
|
112
|
-
(h ? 'x' + h : '')
|
|
113
|
-
);
|
|
85
|
+
|
|
86
|
+
$.textCache = (enable, maxSize) => {
|
|
87
|
+
if (maxSize) cacheMax = maxSize;
|
|
88
|
+
if (enable !== undefined) useCache = enable;
|
|
89
|
+
return useCache;
|
|
114
90
|
};
|
|
115
91
|
$.createTextImage = (str, w, h) => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
$._textCache = true;
|
|
121
|
-
$._genTextImage = true;
|
|
122
|
-
$.text(str, 0, 0, w, h);
|
|
123
|
-
$._genTextImage = false;
|
|
124
|
-
$._textCache = og;
|
|
125
|
-
return $._tic.get(k);
|
|
92
|
+
genTextImage = true;
|
|
93
|
+
img = $.text(str, 0, 0, w, h);
|
|
94
|
+
genTextImage = false;
|
|
95
|
+
return img;
|
|
126
96
|
};
|
|
97
|
+
|
|
98
|
+
let lines = [];
|
|
127
99
|
$.text = (str, x, y, w, h) => {
|
|
128
100
|
if (str === undefined || (!$._doFill && !$._doStroke)) return;
|
|
129
101
|
str = str.toString();
|
|
130
|
-
let lines = str.split('\n');
|
|
131
102
|
if ($._da) {
|
|
132
103
|
x *= $._da;
|
|
133
104
|
y *= $._da;
|
|
134
105
|
}
|
|
135
106
|
let ctx = $.ctx;
|
|
136
|
-
|
|
107
|
+
let img, tX, tY;
|
|
137
108
|
|
|
138
|
-
|
|
109
|
+
if (fontMod) {
|
|
110
|
+
ctx.font = `${emphasis} ${$._textSize}px ${font}`;
|
|
111
|
+
fontMod = false;
|
|
112
|
+
}
|
|
139
113
|
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
114
|
+
if (useCache || genTextImage) {
|
|
115
|
+
if (styleHash == -1) updateStyleHash();
|
|
116
|
+
|
|
117
|
+
img = cache[str];
|
|
118
|
+
if (img) img = img[styleHash];
|
|
119
|
+
|
|
120
|
+
if (img) {
|
|
121
|
+
if (img._fill == $._fill && img._stroke == $._stroke && img._strokeWeight == $._strokeWeight) {
|
|
122
|
+
if (genTextImage) return img;
|
|
123
|
+
return $.textImage(img, x, y);
|
|
124
|
+
} else img.clear();
|
|
125
|
+
}
|
|
143
126
|
}
|
|
144
127
|
|
|
145
|
-
if (
|
|
128
|
+
if (str.indexOf('\n') == -1) lines[0] = str;
|
|
129
|
+
else lines = str.split('\n');
|
|
130
|
+
|
|
131
|
+
if (str.length > w) {
|
|
132
|
+
let wrapped = [];
|
|
133
|
+
for (let line of lines) {
|
|
134
|
+
let i = 0;
|
|
135
|
+
|
|
136
|
+
while (i < line.length) {
|
|
137
|
+
let max = i + w;
|
|
138
|
+
if (max >= line.length) {
|
|
139
|
+
wrapped.push(line.slice(i));
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
let end = line.lastIndexOf(' ', max);
|
|
143
|
+
if (end === -1 || end < i) end = max;
|
|
144
|
+
wrapped.push(line.slice(i, end));
|
|
145
|
+
i = end + 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
lines = wrapped;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!useCache && !genTextImage) {
|
|
146
152
|
tX = x;
|
|
147
153
|
tY = y;
|
|
148
154
|
} else {
|
|
149
|
-
cacheKey = $._genTextImageKey(str, w, h);
|
|
150
|
-
img = $._tic.get(cacheKey);
|
|
151
|
-
if (img && !$._genTextImage) return $.textImage(img, x, y);
|
|
152
|
-
|
|
153
155
|
tX = 0;
|
|
154
|
-
tY =
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
tY = leading * lines.length;
|
|
157
|
+
|
|
158
|
+
if (!img) {
|
|
159
|
+
let measure = ctx.measureText(' ');
|
|
160
|
+
let ascent = measure.fontBoundingBoxAscent;
|
|
161
|
+
let descent = measure.fontBoundingBoxDescent;
|
|
162
|
+
h ??= tY + descent;
|
|
159
163
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
img = $.createImage.call($, Math.ceil(ctx.measureText(str).width), Math.ceil(h), {
|
|
165
|
+
pixelDensity: $._pixelDensity
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
img._ascent = ascent;
|
|
169
|
+
img._descent = descent;
|
|
170
|
+
img._top = descent + leadDiff;
|
|
171
|
+
img._middle = img._top + ascent * 0.5;
|
|
172
|
+
img._bottom = img._top + ascent;
|
|
173
|
+
img._leading = leading;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
img._fill = $._fill;
|
|
177
|
+
img._stroke = $._stroke;
|
|
178
|
+
img._strokeWeight = $._strokeWeight;
|
|
179
|
+
img.modified = true;
|
|
163
180
|
|
|
164
181
|
ctx = img.ctx;
|
|
165
182
|
|
|
166
183
|
ctx.font = $.ctx.font;
|
|
167
|
-
ctx.fillStyle = $.
|
|
168
|
-
ctx.strokeStyle = $.
|
|
184
|
+
ctx.fillStyle = $._fill;
|
|
185
|
+
ctx.strokeStyle = $._stroke;
|
|
169
186
|
ctx.lineWidth = $.ctx.lineWidth;
|
|
170
187
|
}
|
|
171
188
|
|
|
@@ -175,31 +192,49 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
175
192
|
ctx.fillStyle = 'black';
|
|
176
193
|
}
|
|
177
194
|
|
|
178
|
-
for (let
|
|
179
|
-
if ($._doStroke && $._strokeSet) ctx.strokeText(
|
|
180
|
-
if ($._doFill) ctx.fillText(
|
|
181
|
-
tY +=
|
|
195
|
+
for (let line of lines) {
|
|
196
|
+
if ($._doStroke && $._strokeSet) ctx.strokeText(line, tX, tY);
|
|
197
|
+
if ($._doFill) ctx.fillText(line, tX, tY);
|
|
198
|
+
tY += leading;
|
|
182
199
|
if (tY > h) break;
|
|
183
200
|
}
|
|
201
|
+
lines.length = 0;
|
|
184
202
|
|
|
185
203
|
if (!$._fillSet) ctx.fillStyle = ogFill;
|
|
186
204
|
|
|
187
|
-
if (useCache) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
205
|
+
if (useCache || genTextImage) {
|
|
206
|
+
styleHashes.push(styleHash);
|
|
207
|
+
(cache[str] ??= {})[styleHash] = img;
|
|
208
|
+
|
|
209
|
+
cacheSize++;
|
|
210
|
+
if (cacheSize > cacheMax) {
|
|
211
|
+
let half = Math.ceil(cacheSize / 2);
|
|
212
|
+
let hashes = styleHashes.splice(0, half);
|
|
213
|
+
for (let s in cache) {
|
|
214
|
+
s = cache[s];
|
|
215
|
+
for (let h of hashes) delete s[h];
|
|
216
|
+
}
|
|
217
|
+
cacheSize -= half;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (genTextImage) return img;
|
|
221
|
+
$.textImage(img, x, y);
|
|
192
222
|
}
|
|
193
223
|
};
|
|
194
224
|
$.textImage = (img, x, y) => {
|
|
195
225
|
let og = $._imageMode;
|
|
196
226
|
$._imageMode = 'corner';
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
227
|
+
|
|
228
|
+
let ta = $._textAlign;
|
|
229
|
+
if (ta == 'center') x -= img.canvas.hw;
|
|
230
|
+
else if (ta == 'right') x -= img.width;
|
|
231
|
+
|
|
232
|
+
let bl = $._textBaseline;
|
|
233
|
+
if (bl == 'alphabetic') y -= img._leading;
|
|
234
|
+
else if (bl == 'middle') y -= img._middle;
|
|
235
|
+
else if (bl == 'bottom') y -= img._bottom;
|
|
236
|
+
else if (bl == 'top') y -= img._top;
|
|
237
|
+
|
|
203
238
|
$.image(img, x, y);
|
|
204
239
|
$._imageMode = og;
|
|
205
240
|
};
|
package/src/q5-canvas.js
CHANGED
|
@@ -201,7 +201,7 @@ Q5.modules.canvas = ($, q) => {
|
|
|
201
201
|
|
|
202
202
|
function parentResized() {
|
|
203
203
|
if ($.frameCount > 1) {
|
|
204
|
-
$.
|
|
204
|
+
$._didResize = true;
|
|
205
205
|
$._adjustDisplay();
|
|
206
206
|
}
|
|
207
207
|
}
|
|
@@ -261,14 +261,9 @@ Q5.modules.canvas = ($, q) => {
|
|
|
261
261
|
'_imageMode',
|
|
262
262
|
'_rectMode',
|
|
263
263
|
'_ellipseMode',
|
|
264
|
-
'_textFont',
|
|
265
|
-
'_textLeading',
|
|
266
|
-
'_leadingSet',
|
|
267
264
|
'_textSize',
|
|
268
265
|
'_textAlign',
|
|
269
|
-
'_textBaseline'
|
|
270
|
-
'_textStyle',
|
|
271
|
-
'_textWrap'
|
|
266
|
+
'_textBaseline'
|
|
272
267
|
];
|
|
273
268
|
$._styles = [];
|
|
274
269
|
|
|
@@ -281,6 +276,15 @@ Q5.modules.canvas = ($, q) => {
|
|
|
281
276
|
let styles = $._styles.pop();
|
|
282
277
|
for (let s of $._styleNames) $[s] = styles[s];
|
|
283
278
|
};
|
|
279
|
+
|
|
280
|
+
if (window && $._scope != 'graphics') {
|
|
281
|
+
window.addEventListener('resize', () => {
|
|
282
|
+
$._didResize = true;
|
|
283
|
+
q.windowWidth = window.innerWidth;
|
|
284
|
+
q.windowHeight = window.innerHeight;
|
|
285
|
+
q.deviceOrientation = window.screen?.orientation?.type;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
284
288
|
};
|
|
285
289
|
|
|
286
290
|
Q5.canvasOptions = {
|
package/src/q5-core.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);
|
package/src/q5-util.js
CHANGED
|
@@ -5,10 +5,11 @@ Q5.modules.util = ($, q) => {
|
|
|
5
5
|
fetch(path)
|
|
6
6
|
.then((r) => {
|
|
7
7
|
if (type == 'json') return r.json();
|
|
8
|
-
|
|
8
|
+
return r.text();
|
|
9
9
|
})
|
|
10
10
|
.then((r) => {
|
|
11
11
|
q._preloadCount--;
|
|
12
|
+
if (type == 'csv') r = $.CSV.parse(r);
|
|
12
13
|
Object.assign(ret, r);
|
|
13
14
|
if (cb) cb(r);
|
|
14
15
|
});
|
|
@@ -17,6 +18,21 @@ Q5.modules.util = ($, q) => {
|
|
|
17
18
|
|
|
18
19
|
$.loadStrings = (path, cb) => $._loadFile(path, cb, 'text');
|
|
19
20
|
$.loadJSON = (path, cb) => $._loadFile(path, cb, 'json');
|
|
21
|
+
$.loadCSV = (path, cb) => $._loadFile(path, cb, 'csv');
|
|
22
|
+
|
|
23
|
+
$.CSV = {};
|
|
24
|
+
$.CSV.parse = (csv, sep = ',', lineSep = '\n') => {
|
|
25
|
+
let a = [],
|
|
26
|
+
lns = csv.split(lineSep),
|
|
27
|
+
headers = lns[0].split(sep);
|
|
28
|
+
for (let i = 1; i < lns.length; i++) {
|
|
29
|
+
let o = {},
|
|
30
|
+
ln = lns[i].split(sep);
|
|
31
|
+
headers.forEach((h, i) => (o[h] = JSON.parse(ln[i])));
|
|
32
|
+
a.push(o);
|
|
33
|
+
}
|
|
34
|
+
return a;
|
|
35
|
+
};
|
|
20
36
|
|
|
21
37
|
if (typeof localStorage == 'object') {
|
|
22
38
|
$.storeItem = localStorage.setItem;
|