q5 1.9.21 → 1.9.46

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.
@@ -0,0 +1,216 @@
1
+ Q5.modules.q2d_text = ($) => {
2
+ $.NORMAL = 'normal';
3
+ $.ITALIC = 'italic';
4
+ $.BOLD = 'bold';
5
+ $.BOLDITALIC = 'italic bold';
6
+
7
+ $.CENTER = 'center';
8
+ $.LEFT = 'left';
9
+ $.RIGHT = 'right';
10
+ $.TOP = 'top';
11
+ $.BOTTOM = 'bottom';
12
+ $.BASELINE = 'alphabetic';
13
+
14
+ $._textFont = 'sans-serif';
15
+ $._textSize = 12;
16
+ $._textLeading = 15;
17
+ $._textLeadDiff = 3;
18
+ $._textStyle = 'normal';
19
+
20
+ $.loadFont = (url, cb) => {
21
+ $._preloadCount++;
22
+ let sp = url.split('/');
23
+ let name = sp[sp.length - 1].split('.')[0].replace(' ', '');
24
+ let f = new FontFace(name, 'url(' + url + ')');
25
+ document.fonts.add(f);
26
+ f.load().then(() => {
27
+ $._preloadCount--;
28
+ if (cb) cb(name);
29
+ });
30
+ return name;
31
+ };
32
+ $.textFont = (x) => ($._textFont = x);
33
+ $.textSize = (x) => {
34
+ if (x === undefined) return $._textSize;
35
+ if ($._da) x *= $._da;
36
+ $._textSize = x;
37
+ if (!$._leadingSet) {
38
+ $._textLeading = x * 1.25;
39
+ $._textLeadDiff = $._textLeading - x;
40
+ }
41
+ };
42
+ $.textLeading = (x) => {
43
+ if (x === undefined) return $._textLeading;
44
+ if ($._da) x *= $._da;
45
+ $._textLeading = x;
46
+ $._textLeadDiff = x - $._textSize;
47
+ $._leadingSet = true;
48
+ };
49
+ $.textStyle = (x) => ($._textStyle = x);
50
+ $.textAlign = (horiz, vert) => {
51
+ $.ctx.textAlign = horiz;
52
+ if (vert) {
53
+ $.ctx.textBaseline = vert == $.CENTER ? 'middle' : vert;
54
+ }
55
+ };
56
+ $.textWidth = (str) => {
57
+ $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
58
+ return $.ctx.measureText(str).width;
59
+ };
60
+ $.textAscent = (str) => {
61
+ $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
62
+ return $.ctx.measureText(str).actualBoundingBoxAscent;
63
+ };
64
+ $.textDescent = (str) => {
65
+ $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
66
+ return $.ctx.measureText(str).actualBoundingBoxDescent;
67
+ };
68
+ $._textCache = true;
69
+ $._TimedCache = class extends Map {
70
+ constructor() {
71
+ super();
72
+ this.maxSize = 500;
73
+ }
74
+ set(k, v) {
75
+ v.lastAccessed = Date.now();
76
+ super.set(k, v);
77
+ if (this.size > this.maxSize) this.gc();
78
+ }
79
+ get(k) {
80
+ const v = super.get(k);
81
+ if (v) v.lastAccessed = Date.now();
82
+ return v;
83
+ }
84
+ gc() {
85
+ let t = Infinity;
86
+ let oldest;
87
+ let i = 0;
88
+ for (const [k, v] of this.entries()) {
89
+ if (v.lastAccessed < t) {
90
+ t = v.lastAccessed;
91
+ oldest = i;
92
+ }
93
+ i++;
94
+ }
95
+ i = oldest;
96
+ for (const k of this.keys()) {
97
+ if (i == 0) {
98
+ oldest = k;
99
+ break;
100
+ }
101
+ i--;
102
+ }
103
+ this.delete(oldest);
104
+ }
105
+ };
106
+ $._tic = new $._TimedCache();
107
+ $.textCache = (b, maxSize) => {
108
+ if (maxSize) $._tic.maxSize = maxSize;
109
+ if (b !== undefined) $._textCache = b;
110
+ return $._textCache;
111
+ };
112
+ $._genTextImageKey = (str, w, h) => {
113
+ return (
114
+ str.slice(0, 200) +
115
+ $._textStyle +
116
+ $._textSize +
117
+ $._textFont +
118
+ ($._doFill ? $.ctx.fillStyle : '') +
119
+ '_' +
120
+ ($._doStroke && $._strokeSet ? $.ctx.lineWidth + $.ctx.strokeStyle + '_' : '') +
121
+ (w || '') +
122
+ (h ? 'x' + h : '')
123
+ );
124
+ };
125
+ $.createTextImage = (str, w, h) => {
126
+ let og = $._textCache;
127
+ $._textCache = true;
128
+ $._genTextImage = true;
129
+ $.text(str, 0, 0, w, h);
130
+ $._genTextImage = false;
131
+ let k = $._genTextImageKey(str, w, h);
132
+ $._textCache = og;
133
+ return $._tic.get(k);
134
+ };
135
+ $.text = (str, x, y, w, h) => {
136
+ if (str === undefined) return;
137
+ str = str.toString();
138
+ if ($._da) {
139
+ x *= $._da;
140
+ y *= $._da;
141
+ }
142
+ if (!$._doFill && !$._doStroke) return;
143
+ let c, ti, tg, k, cX, cY, _ascent, _descent;
144
+ let pd = 1;
145
+ let t = $.ctx.getTransform();
146
+ let useCache = $._genTextImage || ($._textCache && (t.b != 0 || t.c != 0));
147
+ if (!useCache) {
148
+ c = $.ctx;
149
+ cX = x;
150
+ cY = y;
151
+ } else {
152
+ k = $._genTextImageKey(str, w, h);
153
+ ti = $._tic.get(k);
154
+ if (ti && !$._genTextImage) {
155
+ $.textImage(ti, x, y);
156
+ return;
157
+ }
158
+ tg = $.createGraphics.call($, 1, 1);
159
+ c = tg.ctx;
160
+ pd = $._pixelDensity;
161
+ }
162
+ c.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
163
+ let lines = str.split('\n');
164
+ if (useCache) {
165
+ cX = 0;
166
+ cY = $._textLeading * lines.length;
167
+ let m = c.measureText(' ');
168
+ _ascent = m.fontBoundingBoxAscent;
169
+ _descent = m.fontBoundingBoxDescent;
170
+ h ??= cY + _descent;
171
+ tg.resizeCanvas(Math.ceil(c.measureText(str).width), Math.ceil(h));
172
+
173
+ c.fillStyle = $.ctx.fillStyle;
174
+ c.strokeStyle = $.ctx.strokeStyle;
175
+ c.lineWidth = $.ctx.lineWidth;
176
+ }
177
+ let f = c.fillStyle;
178
+ if (!$._fillSet) c.fillStyle = 'black';
179
+ for (let i = 0; i < lines.length; i++) {
180
+ if ($._doStroke && $._strokeSet) c.strokeText(lines[i], cX, cY);
181
+ if ($._doFill) c.fillText(lines[i], cX, cY);
182
+ cY += $._textLeading;
183
+ if (cY > h) break;
184
+ }
185
+ if (!$._fillSet) c.fillStyle = f;
186
+ if (useCache) {
187
+ ti = tg.get();
188
+ ti._ascent = _ascent;
189
+ ti._descent = _descent;
190
+ $._tic.set(k, ti);
191
+ if (!$._genTextImage) $.textImage(ti, x, y);
192
+ }
193
+ };
194
+ $.textImage = (img, x, y) => {
195
+ let og = $._imageMode;
196
+ $._imageMode = 'corner';
197
+ if ($.ctx.textAlign == 'center') x -= img.width * 0.5;
198
+ else if ($.ctx.textAlign == 'right') x -= img.width;
199
+ if ($.ctx.textBaseline == 'alphabetic') y -= $._textLeading;
200
+ if ($.ctx.textBaseline == 'middle') y -= img._descent + img._ascent * 0.5 + $._textLeadDiff;
201
+ else if ($.ctx.textBaseline == 'bottom') y -= img._ascent + img._descent + $._textLeadDiff;
202
+ else if ($.ctx.textBaseline == 'top') y -= img._descent + $._textLeadDiff;
203
+ $.image(img, x, y);
204
+ $._imageMode = og;
205
+ };
206
+
207
+ $.nf = (n, l, r) => {
208
+ let neg = n < 0;
209
+ n = Math.abs(n);
210
+ let parts = n.toFixed(r).split('.');
211
+ parts[0] = parts[0].padStart(l, '0');
212
+ let s = parts.join('.');
213
+ if (neg) s = '-' + s;
214
+ return s;
215
+ };
216
+ };
package/src/q5-ai.js ADDED
@@ -0,0 +1,65 @@
1
+ Q5.modules.ai = ($) => {
2
+ $.askAI = (q = '') => {
3
+ throw Error('Ask AI ✨ ' + q);
4
+ };
5
+
6
+ $._aiErrorAssistance = async (e) => {
7
+ let askAI = e.message.includes('Ask AI ✨');
8
+ if (!askAI) console.error(e);
9
+ if (Q5.disableFriendlyErrors) return;
10
+ if (askAI || !Q5.errorTolerant) noLoop();
11
+ let stackLines = e.stack.split('\n');
12
+ if (stackLines.length <= 1) return;
13
+
14
+ let idx = 1;
15
+ let sep = '(';
16
+ if (navigator.userAgent.indexOf('Chrome') == -1) {
17
+ idx = 0;
18
+ sep = '@';
19
+ }
20
+ while (stackLines[idx].indexOf('q5.js:') >= 0) idx++;
21
+
22
+ let parts = stackLines[idx].split(sep).at(-1);
23
+ parts = parts.split(':');
24
+ let lineNum = parseInt(parts.at(-2));
25
+ if (askAI) lineNum++;
26
+ let fileUrl = parts.slice(0, -2).join(':');
27
+ let fileBase = fileUrl.split('/').at(-1);
28
+
29
+ try {
30
+ let res = await (await fetch(fileUrl)).text();
31
+ let lines = res.split('\n');
32
+ let errLine = lines[lineNum - 1].trim();
33
+
34
+ let context = '';
35
+ let i = 1;
36
+ while (context.length < 1600) {
37
+ if (lineNum - i >= 0) {
38
+ context = lines[lineNum - i].trim() + '\n' + context;
39
+ }
40
+ if (lineNum + i < lines.length) {
41
+ context += lines[lineNum + i].trim() + '\n';
42
+ } else break;
43
+ i++;
44
+ }
45
+
46
+ let question =
47
+ askAI && e.message.length > 10 ? e.message.slice(10) : 'Whats+wrong+with+this+line%3F+short+answer';
48
+
49
+ let url =
50
+ 'https://chatgpt.com/?q=q5.js+' +
51
+ question +
52
+ (askAI ? '' : '%0A%0A' + encodeURIComponent(e.name + ': ' + e.message)) +
53
+ '%0A%0ALine%3A+' +
54
+ encodeURIComponent(errLine) +
55
+ '%0A%0AExcerpt+for+context%3A%0A%0A' +
56
+ encodeURIComponent(context);
57
+
58
+ if (!askAI) console.log('Error in ' + fileBase + ' on line ' + lineNum + ':\n\n' + errLine);
59
+
60
+ console.warn('Ask AI ✨ ' + url);
61
+
62
+ if (askAI) window.open(url, '_blank');
63
+ } catch (err) {}
64
+ };
65
+ };
@@ -0,0 +1,202 @@
1
+ Q5.modules.color = ($) => {
2
+ $.RGB = $.RGBA = $._colorMode = 'rgb';
3
+
4
+ if (Q5.supportsHDR) $.Color = Q5.ColorRGBA_P3;
5
+ else $.Color = Q5.ColorRGBA;
6
+
7
+ $.colorMode = (mode) => {
8
+ $._colorMode = mode;
9
+ if (mode == 'oklch') {
10
+ $.Color = Q5.ColorOKLCH;
11
+ } else if (mode == 'rgb') {
12
+ if ($.canvas.colorSpace == 'srgb') $.Color = Q5.ColorRGBA;
13
+ else $.Color = Q5.ColorRGBA_P3;
14
+ } else if (mode == 'srgb') {
15
+ $.Color = Q5.ColorRGBA;
16
+ $._colorMode = 'rgb';
17
+ }
18
+ };
19
+
20
+ $._basicColors = {
21
+ aqua: [0, 255, 255],
22
+ black: [0, 0, 0],
23
+ blue: [0, 0, 255],
24
+ brown: [165, 42, 42],
25
+ crimson: [220, 20, 60],
26
+ cyan: [0, 255, 255],
27
+ darkviolet: [148, 0, 211],
28
+ gold: [255, 215, 0],
29
+ green: [0, 128, 0],
30
+ gray: [128, 128, 128],
31
+ grey: [128, 128, 128],
32
+ hotpink: [255, 105, 180],
33
+ indigo: [75, 0, 130],
34
+ khaki: [240, 230, 140],
35
+ lightgreen: [144, 238, 144],
36
+ lime: [0, 255, 0],
37
+ magenta: [255, 0, 255],
38
+ navy: [0, 0, 128],
39
+ orange: [255, 165, 0],
40
+ olive: [128, 128, 0],
41
+ peachpuff: [255, 218, 185],
42
+ pink: [255, 192, 203],
43
+ purple: [128, 0, 128],
44
+ red: [255, 0, 0],
45
+ skyblue: [135, 206, 235],
46
+ tan: [210, 180, 140],
47
+ turquoise: [64, 224, 208],
48
+ transparent: [0, 0, 0, 0],
49
+ white: [255, 255, 255],
50
+ violet: [238, 130, 238],
51
+ yellow: [255, 255, 0]
52
+ };
53
+
54
+ $.color = function (c0, c1, c2, c3) {
55
+ let C = $.Color;
56
+ if (c0._q5Color) return new C(...c0.levels);
57
+ let args = arguments;
58
+ if (args.length == 1) {
59
+ if (typeof c0 == 'string') {
60
+ if (c0[0] == '#') {
61
+ return new C(
62
+ parseInt(c0.slice(1, 3), 16),
63
+ parseInt(c0.slice(3, 5), 16),
64
+ parseInt(c0.slice(5, 7), 16),
65
+ c0.length != 9 ? null : parseInt(c0.slice(7, 9), 16)
66
+ );
67
+ } else if ($._basicColors[c0]) return new C(...$._basicColors[c0]);
68
+ else return new C(0, 0, 0);
69
+ } else if (Array.isArray(c0)) return new C(...c0);
70
+ }
71
+ if ($._colorMode == 'rgb') {
72
+ if (args.length == 1) return new C(c0, c0, c0);
73
+ else if (args.length == 2) return new C(c0, c0, c0, c1);
74
+ else if (args.length == 3) return new C(c0, c1, c2);
75
+ else if (args.length == 4) return new C(c0, c1, c2, c3);
76
+ }
77
+ };
78
+
79
+ $.red = (c) => c.r;
80
+ $.green = (c) => c.g;
81
+ $.blue = (c) => c.b;
82
+ $.alpha = (c) => c.a;
83
+ $.lightness = (c) => {
84
+ return ((0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * 100) / 255;
85
+ };
86
+
87
+ $.lerpColor = (a, b, t) => {
88
+ if ($._colorMode == 'rgb') {
89
+ return new $.Color(
90
+ $.constrain($.lerp(a.r, b.r, t), 0, 255),
91
+ $.constrain($.lerp(a.g, b.g, t), 0, 255),
92
+ $.constrain($.lerp(a.b, b.b, t), 0, 255),
93
+ $.constrain($.lerp(a.a, b.a, t), 0, 255)
94
+ );
95
+ } else {
96
+ let deltaH = b.h - a.h;
97
+ if (deltaH > 180) deltaH -= 360;
98
+ if (deltaH < -180) deltaH += 360;
99
+ let h = a.h + t * deltaH;
100
+ if (h < 0) h += 360;
101
+ if (h > 360) h -= 360;
102
+ return new $.Color(
103
+ $.constrain($.lerp(a.l, b.l, t), 0, 100),
104
+ $.constrain($.lerp(a.c, b.c, t), 0, 100),
105
+ h,
106
+ $.constrain($.lerp(a.a, b.a, t), 0, 255)
107
+ );
108
+ }
109
+ };
110
+ };
111
+
112
+ // COLOR CLASSES
113
+
114
+ Q5.Color = class {
115
+ constructor() {
116
+ this._q5Color = true;
117
+ }
118
+ };
119
+ Q5.ColorOKLCH = class extends Q5.Color {
120
+ constructor(l, c, h, a) {
121
+ super();
122
+ this.l = l;
123
+ this.c = c;
124
+ this.h = h;
125
+ this.a = a ?? 1;
126
+ }
127
+ toString() {
128
+ return `color(oklch ${this.l} ${this.c} ${this.h} / ${this.a})`;
129
+ }
130
+ };
131
+ Q5.ColorRGBA = class extends Q5.Color {
132
+ constructor(r, g, b, a) {
133
+ super();
134
+ this.r = r;
135
+ this.g = g;
136
+ this.b = b;
137
+ this.a = a ?? 255;
138
+ }
139
+ setRed(v) {
140
+ this.r = v;
141
+ }
142
+ setGreen(v) {
143
+ this.g = v;
144
+ }
145
+ setBlue(v) {
146
+ this.b = v;
147
+ }
148
+ setAlpha(v) {
149
+ this.a = v;
150
+ }
151
+ get levels() {
152
+ return [this.r, this.g, this.b, this.a];
153
+ }
154
+ toString() {
155
+ return `rgb(${this.r} ${this.g} ${this.b} / ${this.a / 255})`;
156
+ }
157
+ };
158
+ Q5.ColorRGBA_P3 = class extends Q5.ColorRGBA {
159
+ constructor(r, g, b, a) {
160
+ super(r, g, b, a);
161
+ this._edited = true;
162
+ }
163
+ get r() {
164
+ return this._r;
165
+ }
166
+ set r(v) {
167
+ this._r = v;
168
+ this._edited = true;
169
+ }
170
+ get g() {
171
+ return this._g;
172
+ }
173
+ set g(v) {
174
+ this._g = v;
175
+ this._edited = true;
176
+ }
177
+ get b() {
178
+ return this._b;
179
+ }
180
+ set b(v) {
181
+ this._b = v;
182
+ this._edited = true;
183
+ }
184
+ get a() {
185
+ return this._a;
186
+ }
187
+ set a(v) {
188
+ this._a = v;
189
+ this._edited = true;
190
+ }
191
+ toString() {
192
+ if (this._edited) {
193
+ let r = (this._r / 255).toFixed(3);
194
+ let g = (this._g / 255).toFixed(3);
195
+ let b = (this._b / 255).toFixed(3);
196
+ let a = (this._a / 255).toFixed(3);
197
+ this._css = `color(display-p3 ${r} ${g} ${b} / ${a})`;
198
+ this._edited = false;
199
+ }
200
+ return this._css;
201
+ }
202
+ };