@gjsify/canvas2d-core 0.3.16 → 0.3.18

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.
@@ -1,975 +1 @@
1
- import { COMPOSITE_OP_MAP, LINE_CAP_MAP, LINE_JOIN_MAP, cairoArcTo, cairoEllipse, cairoRoundRect, quadraticToCubic } from "./cairo-utils.js";
2
- import { parseColor } from "./color.js";
3
- import { CanvasGradient } from "./canvas-gradient.js";
4
- import { Path2D } from "./canvas-path.js";
5
- import { CanvasPattern } from "./canvas-pattern.js";
6
- import { cloneState, createDefaultState } from "./canvas-state.js";
7
- import { OurImageData } from "./image-data.js";
8
- import Cairo from "cairo";
9
- import Gdk from "gi://Gdk?version=4.0";
10
- import GdkPixbuf from "gi://GdkPixbuf";
11
- import Pango from "gi://Pango";
12
- import PangoCairo from "gi://PangoCairo";
13
-
14
- //#region src/canvas-rendering-context-2d.ts
15
- /**
16
- * CanvasRenderingContext2D backed by Cairo.ImageSurface.
17
- * Implements the Canvas 2D API for GJS.
18
- */
19
- var CanvasRenderingContext2D = class {
20
- constructor(canvas, _options) {
21
- this._stateStack = [];
22
- this.canvas = canvas;
23
- this._surfaceWidth = canvas.width || 300;
24
- this._surfaceHeight = canvas.height || 150;
25
- this._surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, this._surfaceWidth, this._surfaceHeight);
26
- this._ctx = new Cairo.Context(this._surface);
27
- this._state = createDefaultState();
28
- }
29
- /** Ensure the surface matches the current canvas dimensions. Recreate if resized. */
30
- _ensureSurface() {
31
- const w = this.canvas.width || 300;
32
- const h = this.canvas.height || 150;
33
- if (w !== this._surfaceWidth || h !== this._surfaceHeight) {
34
- this._ctx.$dispose();
35
- this._surface.finish();
36
- this._surfaceWidth = w;
37
- this._surfaceHeight = h;
38
- this._surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, w, h);
39
- this._ctx = new Cairo.Context(this._surface);
40
- this._stateStack = [];
41
- }
42
- }
43
- /** Reset drawing state to defaults (called when canvas dimensions are explicitly reset). */
44
- _resetState() {
45
- this._state = createDefaultState();
46
- this._stateStack = [];
47
- }
48
- /** Apply the current fill style (color, gradient, or pattern) to the Cairo context. */
49
- _applyFillStyle() {
50
- const style = this._state.fillStyle;
51
- if (typeof style === "string") {
52
- const c = this._state.fillColor;
53
- const a = c.a * this._state.globalAlpha;
54
- this._ctx.setSourceRGBA(c.r, c.g, c.b, a);
55
- } else if (style instanceof CanvasGradient) {
56
- this._ctx.setSource(style._getCairoPattern());
57
- } else if (style instanceof CanvasPattern) {
58
- this._ctx.setSource(style._getCairoPattern());
59
- this._applyPatternFilter();
60
- }
61
- }
62
- /** Apply the current stroke style to the Cairo context. */
63
- _applyStrokeStyle() {
64
- const style = this._state.strokeStyle;
65
- if (typeof style === "string") {
66
- const c = this._state.strokeColor;
67
- const a = c.a * this._state.globalAlpha;
68
- this._ctx.setSourceRGBA(c.r, c.g, c.b, a);
69
- } else if (style instanceof CanvasGradient) {
70
- this._ctx.setSource(style._getCairoPattern());
71
- } else if (style instanceof CanvasPattern) {
72
- this._ctx.setSource(style._getCairoPattern());
73
- this._applyPatternFilter();
74
- }
75
- }
76
- /**
77
- * Apply the current imageSmoothingEnabled + imageSmoothingQuality state
78
- * to the currently installed Cairo source pattern. Per Canvas 2D spec,
79
- * the filter is read from the context at *draw* time, not at pattern
80
- * creation — so we re-apply it on every fill/stroke.
81
- */
82
- _applyPatternFilter() {
83
- const pat = this._ctx.getSource?.();
84
- if (pat && typeof pat.setFilter === "function") {
85
- let filter;
86
- if (!this._state.imageSmoothingEnabled) {
87
- filter = Cairo.Filter.NEAREST;
88
- } else if (this._state.imageSmoothingQuality === "high") {
89
- filter = Cairo.Filter.BEST;
90
- } else {
91
- filter = Cairo.Filter.BILINEAR;
92
- }
93
- pat.setFilter(filter);
94
- }
95
- }
96
- /** Apply line properties to the Cairo context. */
97
- _applyLineStyle() {
98
- this._ctx.setLineWidth(this._state.lineWidth);
99
- this._ctx.setLineCap(LINE_CAP_MAP[this._state.lineCap]);
100
- this._ctx.setLineJoin(LINE_JOIN_MAP[this._state.lineJoin]);
101
- this._ctx.setMiterLimit(this._state.miterLimit);
102
- this._ctx.setDash(this._state.lineDash, this._state.lineDashOffset);
103
- }
104
- /** Apply compositing operator. */
105
- _applyCompositing() {
106
- const op = COMPOSITE_OP_MAP[this._state.globalCompositeOperation];
107
- if (op !== undefined) {
108
- this._ctx.setOperator(op);
109
- }
110
- }
111
- /** Get the Cairo ImageSurface (used by other contexts like drawImage). */
112
- _getSurface() {
113
- return this._surface;
114
- }
115
- /** Check if shadow rendering is needed. */
116
- _hasShadow() {
117
- if (this._state.shadowBlur === 0 && this._state.shadowOffsetX === 0 && this._state.shadowOffsetY === 0) {
118
- return false;
119
- }
120
- const c = parseColor(this._state.shadowColor);
121
- return c !== null && c.a > 0;
122
- }
123
- /**
124
- * Convert a distance from device pixels to Cairo user space by inverting
125
- * the linear part of the current CTM (translation doesn't affect distances).
126
- *
127
- * Canvas 2D spec: shadowOffsetX/Y are in CSS pixels and are NOT scaled by
128
- * the current transform. This helper converts them to user-space offsets so
129
- * that `ctx.moveTo(x + sdx, y + sdy)` produces the correct pixel offset
130
- * regardless of any ctx.scale() or ctx.rotate() in effect.
131
- */
132
- _deviceToUserDistance(dx, dy) {
133
- const origin = this._ctx.userToDevice(0, 0);
134
- const xAxis = this._ctx.userToDevice(1, 0);
135
- const yAxis = this._ctx.userToDevice(0, 1);
136
- const a = (xAxis[0] ?? 0) - (origin[0] ?? 0);
137
- const b = (xAxis[1] ?? 0) - (origin[1] ?? 0);
138
- const c = (yAxis[0] ?? 0) - (origin[0] ?? 0);
139
- const d = (yAxis[1] ?? 0) - (origin[1] ?? 0);
140
- const det = a * d - b * c;
141
- if (Math.abs(det) < 1e-10) return [dx, dy];
142
- return [(d * dx - c * dy) / det, (-b * dx + a * dy) / det];
143
- }
144
- /**
145
- * Shadow rendering is intentionally a no-op.
146
- *
147
- * Proper Canvas 2D shadows require a Gaussian blur pass on an isolated
148
- * temporary surface, which cannot be emulated reliably without a full
149
- * Path2D replay or pixel-level manipulation. The previous implementation
150
- * attempted to use a temp surface but never replayed the path onto it
151
- * (because `drawOp` closes over the main context), leaving the shadow
152
- * surface empty while still leaking memory.
153
- *
154
- * Excalibur and most 2D game engines bake glow/outline effects into
155
- * sprites rather than relying on canvas shadows, so this no-op does not
156
- * affect the showcase. A correct implementation is tracked as a
157
- * separate Canvas 2D Phase-5 enhancement.
158
- */
159
- _renderShadow(_drawOp) {}
160
- save() {
161
- this._ensureSurface();
162
- this._stateStack.push(cloneState(this._state));
163
- this._ctx.save();
164
- }
165
- restore() {
166
- this._ensureSurface();
167
- const prev = this._stateStack.pop();
168
- if (prev) {
169
- this._state = prev;
170
- this._ctx.restore();
171
- }
172
- }
173
- translate(x, y) {
174
- this._ensureSurface();
175
- this._ctx.translate(x, y);
176
- }
177
- rotate(angle) {
178
- this._ensureSurface();
179
- this._ctx.rotate(angle);
180
- }
181
- scale(x, y) {
182
- this._ensureSurface();
183
- this._ctx.scale(x, y);
184
- }
185
- /**
186
- * Multiply the current transformation matrix by the given values.
187
- * Matrix: [a c e]
188
- * [b d f]
189
- * [0 0 1]
190
- */
191
- transform(a, b, c, d, e, f) {
192
- this._ensureSurface();
193
- if (!Number.isFinite(a) || !Number.isFinite(b) || !Number.isFinite(c) || !Number.isFinite(d) || !Number.isFinite(e) || !Number.isFinite(f)) {
194
- return;
195
- }
196
- const tx = e;
197
- const ty = f;
198
- const sx = Math.hypot(a, b);
199
- const sy = Math.hypot(c, d);
200
- const rotation = Math.atan2(b, a);
201
- this._ctx.translate(tx, ty);
202
- if (rotation !== 0) this._ctx.rotate(rotation);
203
- if (sx !== 1 || sy !== 1) this._ctx.scale(sx, sy);
204
- }
205
- setTransform(a, b, c, d, e, f) {
206
- this._ensureSurface();
207
- if (typeof a === "object" && a !== null) {
208
- const m = a;
209
- this._ctx.identityMatrix();
210
- this.transform(m.a ?? m.m11 ?? 1, m.b ?? m.m12 ?? 0, m.c ?? m.m21 ?? 0, m.d ?? m.m22 ?? 1, m.e ?? m.m41 ?? 0, m.f ?? m.m42 ?? 0);
211
- } else if (typeof a === "number") {
212
- this._ctx.identityMatrix();
213
- this.transform(a, b, c, d, e, f);
214
- } else {
215
- this._ctx.identityMatrix();
216
- }
217
- }
218
- /**
219
- * Return the current transformation matrix as a DOMMatrix-like object.
220
- */
221
- getTransform() {
222
- const origin = this._ctx.userToDevice(0, 0);
223
- const xAxis = this._ctx.userToDevice(1, 0);
224
- const yAxis = this._ctx.userToDevice(0, 1);
225
- const e = origin[0] ?? 0;
226
- const f = origin[1] ?? 0;
227
- const a = (xAxis[0] ?? 0) - e;
228
- const b = (xAxis[1] ?? 0) - f;
229
- const c = (yAxis[0] ?? 0) - e;
230
- const d = (yAxis[1] ?? 0) - f;
231
- const DOMMatrixCtor = globalThis.DOMMatrix;
232
- if (typeof DOMMatrixCtor === "function") {
233
- return new DOMMatrixCtor([
234
- a,
235
- b,
236
- c,
237
- d,
238
- e,
239
- f
240
- ]);
241
- }
242
- return {
243
- a,
244
- b,
245
- c,
246
- d,
247
- e,
248
- f,
249
- m11: a,
250
- m12: b,
251
- m13: 0,
252
- m14: 0,
253
- m21: c,
254
- m22: d,
255
- m23: 0,
256
- m24: 0,
257
- m31: 0,
258
- m32: 0,
259
- m33: 1,
260
- m34: 0,
261
- m41: e,
262
- m42: f,
263
- m43: 0,
264
- m44: 1,
265
- is2D: true,
266
- isIdentity: a === 1 && b === 0 && c === 0 && d === 1 && e === 0 && f === 0
267
- };
268
- }
269
- resetTransform() {
270
- this._ensureSurface();
271
- this._ctx.identityMatrix();
272
- }
273
- get fillStyle() {
274
- return this._state.fillStyle;
275
- }
276
- set fillStyle(value) {
277
- if (typeof value === "string") {
278
- const parsed = parseColor(value);
279
- if (parsed) {
280
- this._state.fillStyle = value;
281
- this._state.fillColor = parsed;
282
- }
283
- } else {
284
- this._state.fillStyle = value;
285
- }
286
- }
287
- get strokeStyle() {
288
- return this._state.strokeStyle;
289
- }
290
- set strokeStyle(value) {
291
- if (typeof value === "string") {
292
- const parsed = parseColor(value);
293
- if (parsed) {
294
- this._state.strokeStyle = value;
295
- this._state.strokeColor = parsed;
296
- }
297
- } else {
298
- this._state.strokeStyle = value;
299
- }
300
- }
301
- get lineWidth() {
302
- return this._state.lineWidth;
303
- }
304
- set lineWidth(value) {
305
- if (value > 0 && isFinite(value)) this._state.lineWidth = value;
306
- }
307
- get lineCap() {
308
- return this._state.lineCap;
309
- }
310
- set lineCap(value) {
311
- if (value === "butt" || value === "round" || value === "square") {
312
- this._state.lineCap = value;
313
- }
314
- }
315
- get lineJoin() {
316
- return this._state.lineJoin;
317
- }
318
- set lineJoin(value) {
319
- if (value === "miter" || value === "round" || value === "bevel") {
320
- this._state.lineJoin = value;
321
- }
322
- }
323
- get miterLimit() {
324
- return this._state.miterLimit;
325
- }
326
- set miterLimit(value) {
327
- if (value > 0 && isFinite(value)) this._state.miterLimit = value;
328
- }
329
- get globalAlpha() {
330
- return this._state.globalAlpha;
331
- }
332
- set globalAlpha(value) {
333
- if (value >= 0 && value <= 1 && isFinite(value)) this._state.globalAlpha = value;
334
- }
335
- get globalCompositeOperation() {
336
- return this._state.globalCompositeOperation;
337
- }
338
- set globalCompositeOperation(value) {
339
- if (COMPOSITE_OP_MAP[value] !== undefined) {
340
- this._state.globalCompositeOperation = value;
341
- }
342
- }
343
- get imageSmoothingEnabled() {
344
- return this._state.imageSmoothingEnabled;
345
- }
346
- set imageSmoothingEnabled(value) {
347
- this._state.imageSmoothingEnabled = !!value;
348
- }
349
- get imageSmoothingQuality() {
350
- return this._state.imageSmoothingQuality;
351
- }
352
- set imageSmoothingQuality(value) {
353
- if (value === "low" || value === "medium" || value === "high") {
354
- this._state.imageSmoothingQuality = value;
355
- }
356
- }
357
- setLineDash(segments) {
358
- if (segments.some((v) => v < 0 || !isFinite(v))) return;
359
- this._state.lineDash = [...segments];
360
- }
361
- getLineDash() {
362
- return [...this._state.lineDash];
363
- }
364
- get lineDashOffset() {
365
- return this._state.lineDashOffset;
366
- }
367
- set lineDashOffset(value) {
368
- if (isFinite(value)) this._state.lineDashOffset = value;
369
- }
370
- get shadowColor() {
371
- return this._state.shadowColor;
372
- }
373
- set shadowColor(value) {
374
- this._state.shadowColor = value;
375
- }
376
- get shadowBlur() {
377
- return this._state.shadowBlur;
378
- }
379
- set shadowBlur(value) {
380
- if (value >= 0 && isFinite(value)) this._state.shadowBlur = value;
381
- }
382
- get shadowOffsetX() {
383
- return this._state.shadowOffsetX;
384
- }
385
- set shadowOffsetX(value) {
386
- if (isFinite(value)) this._state.shadowOffsetX = value;
387
- }
388
- get shadowOffsetY() {
389
- return this._state.shadowOffsetY;
390
- }
391
- set shadowOffsetY(value) {
392
- if (isFinite(value)) this._state.shadowOffsetY = value;
393
- }
394
- get font() {
395
- return this._state.font;
396
- }
397
- set font(value) {
398
- this._state.font = value;
399
- }
400
- get textAlign() {
401
- return this._state.textAlign;
402
- }
403
- set textAlign(value) {
404
- this._state.textAlign = value;
405
- }
406
- get textBaseline() {
407
- return this._state.textBaseline;
408
- }
409
- set textBaseline(value) {
410
- this._state.textBaseline = value;
411
- }
412
- get direction() {
413
- return this._state.direction;
414
- }
415
- set direction(value) {
416
- this._state.direction = value;
417
- }
418
- beginPath() {
419
- this._ensureSurface();
420
- this._ctx.newPath();
421
- }
422
- moveTo(x, y) {
423
- this._ensureSurface();
424
- this._ctx.moveTo(x, y);
425
- }
426
- lineTo(x, y) {
427
- this._ensureSurface();
428
- this._ctx.lineTo(x, y);
429
- }
430
- closePath() {
431
- this._ensureSurface();
432
- this._ctx.closePath();
433
- }
434
- bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
435
- this._ensureSurface();
436
- this._ctx.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
437
- }
438
- quadraticCurveTo(cpx, cpy, x, y) {
439
- this._ensureSurface();
440
- let cx, cy;
441
- if (this._ctx.hasCurrentPoint()) {
442
- [cx, cy] = this._ctx.getCurrentPoint();
443
- } else {
444
- cx = cpx;
445
- cy = cpy;
446
- }
447
- const { cp1x, cp1y, cp2x, cp2y } = quadraticToCubic(cx, cy, cpx, cpy, x, y);
448
- this._ctx.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
449
- }
450
- arc(x, y, radius, startAngle, endAngle, counterclockwise = false) {
451
- this._ensureSurface();
452
- if (Math.abs(endAngle - startAngle) >= 2 * Math.PI) {
453
- this._ctx.arc(x, y, radius, 0, 2 * Math.PI);
454
- return;
455
- }
456
- if (counterclockwise) {
457
- this._ctx.arcNegative(x, y, radius, startAngle, endAngle);
458
- } else {
459
- this._ctx.arc(x, y, radius, startAngle, endAngle);
460
- }
461
- }
462
- arcTo(x1, y1, x2, y2, radius) {
463
- this._ensureSurface();
464
- let x0, y0;
465
- if (this._ctx.hasCurrentPoint()) {
466
- [x0, y0] = this._ctx.getCurrentPoint();
467
- } else {
468
- x0 = x1;
469
- y0 = y1;
470
- this._ctx.moveTo(x1, y1);
471
- }
472
- cairoArcTo(this._ctx, x0, y0, x1, y1, x2, y2, radius);
473
- }
474
- ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise = false) {
475
- this._ensureSurface();
476
- if (radiusX < 0 || radiusY < 0) {
477
- throw new RangeError("The radii provided are negative");
478
- }
479
- cairoEllipse(this._ctx, x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
480
- }
481
- rect(x, y, w, h) {
482
- this._ensureSurface();
483
- this._ctx.rectangle(x, y, w, h);
484
- }
485
- roundRect(x, y, w, h, radii = 0) {
486
- this._ensureSurface();
487
- cairoRoundRect(this._ctx, x, y, w, h, radii);
488
- }
489
- fill(pathOrRule, fillRule) {
490
- this._ensureSurface();
491
- this._applyCompositing();
492
- this._applyFillStyle();
493
- let rule;
494
- if (pathOrRule instanceof Path2D) {
495
- this._ctx.newPath();
496
- pathOrRule._replayOnCairo(this._ctx);
497
- rule = fillRule;
498
- } else {
499
- rule = pathOrRule;
500
- }
501
- this._ctx.setFillRule(rule === "evenodd" ? Cairo.FillRule.EVEN_ODD : Cairo.FillRule.WINDING);
502
- this._ctx.fillPreserve();
503
- }
504
- stroke(path) {
505
- this._ensureSurface();
506
- this._applyCompositing();
507
- this._applyStrokeStyle();
508
- this._applyLineStyle();
509
- if (path instanceof Path2D) {
510
- this._ctx.newPath();
511
- path._replayOnCairo(this._ctx);
512
- }
513
- this._ctx.strokePreserve();
514
- }
515
- fillRect(x, y, w, h) {
516
- this._ensureSurface();
517
- this._applyCompositing();
518
- const savedPath = this._ctx.copyPath();
519
- if (this._hasShadow()) {
520
- this._renderShadow(() => {
521
- this._ctx.newPath();
522
- this._ctx.rectangle(x, y, w, h);
523
- this._ctx.fill();
524
- });
525
- }
526
- this._applyFillStyle();
527
- this._ctx.newPath();
528
- this._ctx.rectangle(x, y, w, h);
529
- this._ctx.fill();
530
- this._ctx.newPath();
531
- this._ctx.appendPath(savedPath);
532
- }
533
- strokeRect(x, y, w, h) {
534
- this._ensureSurface();
535
- this._applyCompositing();
536
- const savedPath = this._ctx.copyPath();
537
- if (this._hasShadow()) {
538
- this._renderShadow(() => {
539
- this._ctx.newPath();
540
- this._ctx.rectangle(x, y, w, h);
541
- this._ctx.stroke();
542
- });
543
- }
544
- this._applyStrokeStyle();
545
- this._applyLineStyle();
546
- this._ctx.newPath();
547
- this._ctx.rectangle(x, y, w, h);
548
- this._ctx.stroke();
549
- this._ctx.newPath();
550
- this._ctx.appendPath(savedPath);
551
- }
552
- clearRect(x, y, w, h) {
553
- this._ensureSurface();
554
- const savedPath = this._ctx.copyPath();
555
- this._ctx.save();
556
- this._ctx.setOperator(Cairo.Operator.CLEAR);
557
- this._ctx.newPath();
558
- this._ctx.rectangle(x, y, w, h);
559
- this._ctx.fill();
560
- this._ctx.restore();
561
- this._ctx.newPath();
562
- this._ctx.appendPath(savedPath);
563
- }
564
- clip(pathOrRule, fillRule) {
565
- this._ensureSurface();
566
- let rule;
567
- if (pathOrRule instanceof Path2D) {
568
- this._ctx.newPath();
569
- pathOrRule._replayOnCairo(this._ctx);
570
- rule = fillRule;
571
- } else {
572
- rule = pathOrRule;
573
- }
574
- this._ctx.setFillRule(rule === "evenodd" ? Cairo.FillRule.EVEN_ODD : Cairo.FillRule.WINDING);
575
- this._ctx.clip();
576
- }
577
- isPointInPath(pathOrX, xOrY, fillRuleOrY, fillRule) {
578
- this._ensureSurface();
579
- let x, y, rule;
580
- if (pathOrX instanceof Path2D) {
581
- this._ctx.newPath();
582
- pathOrX._replayOnCairo(this._ctx);
583
- x = xOrY;
584
- y = fillRuleOrY;
585
- rule = fillRule;
586
- } else {
587
- x = pathOrX;
588
- y = xOrY;
589
- rule = fillRuleOrY;
590
- }
591
- this._ctx.setFillRule(rule === "evenodd" ? Cairo.FillRule.EVEN_ODD : Cairo.FillRule.WINDING);
592
- return this._ctx.inFill(x, y);
593
- }
594
- isPointInStroke(pathOrX, xOrY, y) {
595
- this._ensureSurface();
596
- this._applyLineStyle();
597
- if (pathOrX instanceof Path2D) {
598
- this._ctx.newPath();
599
- pathOrX._replayOnCairo(this._ctx);
600
- return this._ctx.inStroke(xOrY, y);
601
- }
602
- return this._ctx.inStroke(pathOrX, xOrY);
603
- }
604
- createLinearGradient(x0, y0, x1, y1) {
605
- return new CanvasGradient("linear", x0, y0, x1, y1);
606
- }
607
- createRadialGradient(x0, y0, r0, x1, y1, r1) {
608
- return new CanvasGradient("radial", x0, y0, x1, y1, r0, r1);
609
- }
610
- createPattern(image, repetition) {
611
- return CanvasPattern.create(image, repetition);
612
- }
613
- createImageData(swOrImageData, sh) {
614
- if (typeof swOrImageData === "number") {
615
- return new OurImageData(Math.abs(swOrImageData), Math.abs(sh));
616
- }
617
- return new OurImageData(swOrImageData.width, swOrImageData.height);
618
- }
619
- getImageData(sx, sy, sw, sh) {
620
- this._ensureSurface();
621
- this._surface.flush();
622
- const pixbuf = Gdk.pixbuf_get_from_surface(this._surface, sx, sy, sw, sh);
623
- if (!pixbuf) {
624
- return new OurImageData(sw, sh);
625
- }
626
- const pixels = pixbuf.get_pixels();
627
- const hasAlpha = pixbuf.get_has_alpha();
628
- const rowstride = pixbuf.get_rowstride();
629
- const nChannels = pixbuf.get_n_channels();
630
- const out = new Uint8ClampedArray(sw * sh * 4);
631
- for (let y = 0; y < sh; y++) {
632
- for (let x = 0; x < sw; x++) {
633
- const srcIdx = y * rowstride + x * nChannels;
634
- const dstIdx = (y * sw + x) * 4;
635
- out[dstIdx] = pixels[srcIdx];
636
- out[dstIdx + 1] = pixels[srcIdx + 1];
637
- out[dstIdx + 2] = pixels[srcIdx + 2];
638
- out[dstIdx + 3] = hasAlpha ? pixels[srcIdx + 3] : 255;
639
- }
640
- }
641
- return new OurImageData(out, sw, sh);
642
- }
643
- putImageData(imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
644
- this._ensureSurface();
645
- const sx = dirtyX ?? 0;
646
- const sy = dirtyY ?? 0;
647
- const sw = dirtyWidth ?? imageData.width;
648
- const sh = dirtyHeight ?? imageData.height;
649
- const srcData = imageData.data;
650
- const srcWidth = imageData.width;
651
- const regionData = new Uint8Array(sw * sh * 4);
652
- for (let y = 0; y < sh; y++) {
653
- for (let x = 0; x < sw; x++) {
654
- const srcIdx = ((sy + y) * srcWidth + (sx + x)) * 4;
655
- const dstIdx = (y * sw + x) * 4;
656
- regionData[dstIdx] = srcData[srcIdx];
657
- regionData[dstIdx + 1] = srcData[srcIdx + 1];
658
- regionData[dstIdx + 2] = srcData[srcIdx + 2];
659
- regionData[dstIdx + 3] = srcData[srcIdx + 3];
660
- }
661
- }
662
- const pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(regionData, GdkPixbuf.Colorspace.RGB, true, 8, sw, sh, sw * 4);
663
- this._ctx.save();
664
- this._ctx.setOperator(Cairo.Operator.SOURCE);
665
- Gdk.cairo_set_source_pixbuf(this._ctx, pixbuf, dx + sx, dy + sy);
666
- this._ctx.rectangle(dx + sx, dy + sy, sw, sh);
667
- this._ctx.fill();
668
- this._ctx.restore();
669
- }
670
- drawImage(image, a1, a2, a3, a4, a5, a6, a7, a8) {
671
- this._ensureSurface();
672
- this._applyCompositing();
673
- let sx, sy, sw, sh;
674
- let dx, dy, dw, dh;
675
- const sourceInfo = this._getDrawImageSource(image);
676
- if (!sourceInfo) return;
677
- const { pixbuf, imgWidth, imgHeight } = sourceInfo;
678
- if (a3 === undefined) {
679
- sx = 0;
680
- sy = 0;
681
- sw = imgWidth;
682
- sh = imgHeight;
683
- dx = a1;
684
- dy = a2;
685
- dw = imgWidth;
686
- dh = imgHeight;
687
- } else if (a5 === undefined) {
688
- sx = 0;
689
- sy = 0;
690
- sw = imgWidth;
691
- sh = imgHeight;
692
- dx = a1;
693
- dy = a2;
694
- dw = a3;
695
- dh = a4;
696
- } else {
697
- sx = a1;
698
- sy = a2;
699
- sw = a3;
700
- sh = a4;
701
- dx = a5;
702
- dy = a6;
703
- dw = a7;
704
- dh = a8;
705
- }
706
- if (sw === 0 || sh === 0 || dw === 0 || dh === 0) {
707
- return;
708
- }
709
- this._ctx.save();
710
- this._ctx.rectangle(dx, dy, dw, dh);
711
- this._ctx.clip();
712
- this._ctx.translate(dx, dy);
713
- this._ctx.scale(dw / sw, dh / sh);
714
- this._ctx.translate(-sx, -sy);
715
- Gdk.cairo_set_source_pixbuf(this._ctx, pixbuf, 0, 0);
716
- const pat = this._ctx.getSource?.();
717
- if (pat && typeof pat.setFilter === "function") {
718
- let filter;
719
- if (!this._state.imageSmoothingEnabled) {
720
- filter = Cairo.Filter.NEAREST;
721
- } else if (this._state.imageSmoothingQuality === "high") {
722
- filter = Cairo.Filter.BEST;
723
- } else {
724
- filter = Cairo.Filter.BILINEAR;
725
- }
726
- pat.setFilter(filter);
727
- }
728
- if (this._state.globalAlpha < 1) {
729
- this._ctx.paintWithAlpha(this._state.globalAlpha);
730
- } else {
731
- this._ctx.paint();
732
- }
733
- this._ctx.restore();
734
- }
735
- _getDrawImageSource(image) {
736
- if (typeof image?.isPixbuf === "function" && image.isPixbuf()) {
737
- const pixbuf = image._pixbuf;
738
- return {
739
- pixbuf,
740
- imgWidth: pixbuf.get_width(),
741
- imgHeight: pixbuf.get_height()
742
- };
743
- }
744
- if (typeof image?.getContext === "function") {
745
- const ctx2d = image.getContext("2d");
746
- if (ctx2d && typeof ctx2d._getSurface === "function") {
747
- const surface = ctx2d._getSurface();
748
- surface.flush();
749
- const pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0, image.width, image.height);
750
- if (pixbuf) {
751
- return {
752
- pixbuf,
753
- imgWidth: image.width,
754
- imgHeight: image.height
755
- };
756
- }
757
- }
758
- }
759
- return null;
760
- }
761
- /** Create a PangoCairo layout configured with current font/text settings. */
762
- _createTextLayout(text) {
763
- const layout = PangoCairo.create_layout(this._ctx);
764
- layout.set_text(text, -1);
765
- const pangoCtx = layout.get_context();
766
- pangoCtx.set_base_dir(Pango.Direction.LTR);
767
- layout.context_changed();
768
- const fontDesc = this._parseFontToDescription(this._state.font);
769
- layout.set_font_description(fontDesc);
770
- return layout;
771
- }
772
- /** Parse a CSS font string (e.g. "bold 16px Arial") into a Pango.FontDescription. */
773
- _parseFontToDescription(cssFont) {
774
- const match = cssFont.match(/^\s*(italic|oblique|normal)?\s*(small-caps|normal)?\s*(bold|bolder|lighter|[1-9]00|normal)?\s*(\d+(?:\.\d+)?)(px|pt|em|rem|%)?\s*(?:\/\S+)?\s*(.+)?$/i);
775
- if (!match) {
776
- return Pango.font_description_from_string(cssFont);
777
- }
778
- const style = match[1] || "";
779
- const weight = match[3] || "";
780
- let size = parseFloat(match[4]) || 10;
781
- const unit = (match[5] || "px").toLowerCase();
782
- const family = (match[6] || "sans-serif").replace(/['"]/g, "").trim();
783
- if (unit === "pt") size = size * 96 / 72;
784
- else if (unit === "em" || unit === "rem") size = size * 16;
785
- else if (unit === "%") size = size / 100 * 16;
786
- let pangoStr = family;
787
- if (style === "italic") pangoStr += " Italic";
788
- else if (style === "oblique") pangoStr += " Oblique";
789
- if (weight === "bold" || weight === "bolder" || parseInt(weight) >= 600) pangoStr += " Bold";
790
- else if (weight === "lighter" || parseInt(weight) > 0 && parseInt(weight) <= 300) pangoStr += " Light";
791
- const desc = Pango.font_description_from_string(pangoStr);
792
- desc.set_absolute_size(size * Pango.SCALE);
793
- return desc;
794
- }
795
- /**
796
- * Compute the x-offset for text alignment relative to the given x coordinate.
797
- */
798
- _getTextAlignOffset(layout) {
799
- const [, logicalRect] = layout.get_pixel_extents();
800
- const width = logicalRect.width;
801
- switch (this._state.textAlign) {
802
- case "center": return -width / 2;
803
- case "right":
804
- case "end": return -width;
805
- case "left":
806
- case "start":
807
- default: return 0;
808
- }
809
- }
810
- /**
811
- * Compute the y-offset for text baseline positioning.
812
- *
813
- * PangoCairo.show_layout() places the layout TOP-LEFT at the current Cairo point
814
- * (not the baseline). Within the layout, the first line's baseline is at
815
- * approximately `ascent` pixels below the layout top.
816
- *
817
- * For CSS textBaseline semantics, we shift the current point UP (negative offset)
818
- * so the layout top lands at the right position relative to the user's y coordinate.
819
- */
820
- _getTextBaselineOffset(layout) {
821
- const fontDesc = layout.get_font_description() || this._parseFontToDescription(this._state.font);
822
- const context = layout.get_context();
823
- const metrics = context.get_metrics(fontDesc, null);
824
- const ascent = metrics.get_ascent() / Pango.SCALE;
825
- const descent = metrics.get_descent() / Pango.SCALE;
826
- const height = ascent + descent;
827
- switch (this._state.textBaseline) {
828
- case "top": return 0;
829
- case "hanging": return -(ascent * .2);
830
- case "middle": return -(height / 2);
831
- case "alphabetic": return -ascent;
832
- case "ideographic": return -(ascent + descent * .5);
833
- case "bottom": return -height;
834
- default: return -ascent;
835
- }
836
- }
837
- fillText(text, x, y, _maxWidth) {
838
- this._ensureSurface();
839
- this._applyCompositing();
840
- const layout = this._createTextLayout(text);
841
- const xOff = this._getTextAlignOffset(layout);
842
- const yOff = this._getTextBaselineOffset(layout);
843
- if (this._hasShadow()) {
844
- const sc = parseColor(this._state.shadowColor);
845
- if (sc) {
846
- const [sdx, sdy] = this._deviceToUserDistance(this._state.shadowOffsetX, this._state.shadowOffsetY);
847
- const blur = this._state.shadowBlur;
848
- let taps;
849
- if (blur > 0) {
850
- const [bu] = this._deviceToUserDistance(blur, 0);
851
- const [, bv] = this._deviceToUserDistance(0, blur);
852
- taps = [
853
- [
854
- sdx,
855
- sdy,
856
- sc.a
857
- ],
858
- [
859
- sdx + bu,
860
- sdy,
861
- sc.a * .5
862
- ],
863
- [
864
- sdx - bu,
865
- sdy,
866
- sc.a * .5
867
- ],
868
- [
869
- sdx,
870
- sdy + bv,
871
- sc.a * .5
872
- ],
873
- [
874
- sdx,
875
- sdy - bv,
876
- sc.a * .5
877
- ]
878
- ];
879
- } else {
880
- taps = [[
881
- sdx,
882
- sdy,
883
- sc.a
884
- ]];
885
- }
886
- const aa = this._state.imageSmoothingEnabled ? Cairo.Antialias.DEFAULT : Cairo.Antialias.NONE;
887
- for (const [tx, ty, ta] of taps) {
888
- this._ctx.save();
889
- this._ctx.setAntialias(aa);
890
- this._ctx.setSourceRGBA(sc.r, sc.g, sc.b, ta);
891
- this._ctx.moveTo(x + xOff + tx, y + yOff + ty);
892
- PangoCairo.show_layout(this._ctx, layout);
893
- this._ctx.restore();
894
- }
895
- }
896
- }
897
- this._applyFillStyle();
898
- this._ctx.save();
899
- this._ctx.setAntialias(this._state.imageSmoothingEnabled ? Cairo.Antialias.DEFAULT : Cairo.Antialias.NONE);
900
- this._ctx.moveTo(x + xOff, y + yOff);
901
- PangoCairo.show_layout(this._ctx, layout);
902
- this._ctx.restore();
903
- }
904
- strokeText(text, x, y, _maxWidth) {
905
- this._ensureSurface();
906
- this._applyCompositing();
907
- this._applyStrokeStyle();
908
- this._applyLineStyle();
909
- const layout = this._createTextLayout(text);
910
- const xOff = this._getTextAlignOffset(layout);
911
- const yOff = this._getTextBaselineOffset(layout);
912
- this._ctx.save();
913
- this._ctx.setAntialias(this._state.imageSmoothingEnabled ? Cairo.Antialias.DEFAULT : Cairo.Antialias.NONE);
914
- this._ctx.moveTo(x + xOff, y + yOff);
915
- PangoCairo.layout_path(this._ctx, layout);
916
- this._ctx.stroke();
917
- this._ctx.restore();
918
- }
919
- measureText(text) {
920
- this._ensureSurface();
921
- const layout = this._createTextLayout(text);
922
- const [inkRect, logicalRect] = layout.get_pixel_extents();
923
- const baselinePx = layout.get_baseline() / Pango.SCALE;
924
- const actualAscent = Math.max(0, baselinePx - inkRect.y);
925
- const actualDescent = Math.max(0, inkRect.y + inkRect.height - baselinePx);
926
- const fontDesc = layout.get_font_description() || this._parseFontToDescription(this._state.font);
927
- const metrics = layout.get_context().get_metrics(fontDesc, null);
928
- const fontAscent = metrics.get_ascent() / Pango.SCALE;
929
- const fontDescent = metrics.get_descent() / Pango.SCALE;
930
- return {
931
- width: logicalRect.width,
932
- actualBoundingBoxAscent: actualAscent,
933
- actualBoundingBoxDescent: actualDescent,
934
- actualBoundingBoxLeft: Math.max(0, -inkRect.x),
935
- actualBoundingBoxRight: inkRect.x + inkRect.width,
936
- fontBoundingBoxAscent: fontAscent,
937
- fontBoundingBoxDescent: fontDescent,
938
- alphabeticBaseline: 0,
939
- emHeightAscent: fontAscent,
940
- emHeightDescent: fontDescent,
941
- hangingBaseline: fontAscent * .8,
942
- ideographicBaseline: -fontDescent
943
- };
944
- }
945
- /**
946
- * Write the canvas surface to a PNG file and return as data URL.
947
- * Used by HTMLCanvasElement.toDataURL() when a '2d' context is active.
948
- */
949
- _toDataURL(type, _quality) {
950
- if (type && type !== "image/png") {}
951
- this._surface.flush();
952
- const Gio = imports.gi.Gio;
953
- const GLib = imports.gi.GLib;
954
- const [, tempPath] = GLib.file_open_tmp("canvas-XXXXXX.png");
955
- try {
956
- this._surface.writeToPNG(tempPath);
957
- const file = Gio.File.new_for_path(tempPath);
958
- const [, contents] = file.load_contents(null);
959
- const base64 = GLib.base64_encode(contents);
960
- return `data:image/png;base64,${base64}`;
961
- } finally {
962
- try {
963
- GLib.unlink(tempPath);
964
- } catch (_e) {}
965
- }
966
- }
967
- /** Release native Cairo resources. Call when the canvas is discarded. */
968
- _dispose() {
969
- this._ctx.$dispose();
970
- this._surface.finish();
971
- }
972
- };
973
-
974
- //#endregion
975
- export { CanvasRenderingContext2D };
1
+ import{COMPOSITE_OP_MAP as e,LINE_CAP_MAP as t,LINE_JOIN_MAP as n,cairoArcTo as r,cairoEllipse as i,cairoRoundRect as a,quadraticToCubic as o}from"./cairo-utils.js";import{parseColor as s}from"./color.js";import{CanvasGradient as c}from"./canvas-gradient.js";import{Path2D as l}from"./canvas-path.js";import{CanvasPattern as u}from"./canvas-pattern.js";import{cloneState as d,createDefaultState as f}from"./canvas-state.js";import{OurImageData as p}from"./image-data.js";import m from"cairo";import h from"gi://Gdk?version=4.0";import g from"gi://GdkPixbuf";import _ from"gi://Pango";import v from"gi://PangoCairo";var y=class{constructor(e,t){this._stateStack=[],this.canvas=e,this._surfaceWidth=e.width||300,this._surfaceHeight=e.height||150,this._surface=new m.ImageSurface(m.Format.ARGB32,this._surfaceWidth,this._surfaceHeight),this._ctx=new m.Context(this._surface),this._state=f()}_ensureSurface(){let e=this.canvas.width||300,t=this.canvas.height||150;(e!==this._surfaceWidth||t!==this._surfaceHeight)&&(this._ctx.$dispose(),this._surface.finish(),this._surfaceWidth=e,this._surfaceHeight=t,this._surface=new m.ImageSurface(m.Format.ARGB32,e,t),this._ctx=new m.Context(this._surface),this._stateStack=[])}_resetState(){this._state=f(),this._stateStack=[]}_applyFillStyle(){let e=this._state.fillStyle;if(typeof e==`string`){let e=this._state.fillColor,t=e.a*this._state.globalAlpha;this._ctx.setSourceRGBA(e.r,e.g,e.b,t)}else e instanceof c?this._ctx.setSource(e._getCairoPattern()):e instanceof u&&(this._ctx.setSource(e._getCairoPattern()),this._applyPatternFilter())}_applyStrokeStyle(){let e=this._state.strokeStyle;if(typeof e==`string`){let e=this._state.strokeColor,t=e.a*this._state.globalAlpha;this._ctx.setSourceRGBA(e.r,e.g,e.b,t)}else e instanceof c?this._ctx.setSource(e._getCairoPattern()):e instanceof u&&(this._ctx.setSource(e._getCairoPattern()),this._applyPatternFilter())}_applyPatternFilter(){let e=this._ctx.getSource?.();if(e&&typeof e.setFilter==`function`){let t;t=this._state.imageSmoothingEnabled?this._state.imageSmoothingQuality===`high`?m.Filter.BEST:m.Filter.BILINEAR:m.Filter.NEAREST,e.setFilter(t)}}_applyLineStyle(){this._ctx.setLineWidth(this._state.lineWidth),this._ctx.setLineCap(t[this._state.lineCap]),this._ctx.setLineJoin(n[this._state.lineJoin]),this._ctx.setMiterLimit(this._state.miterLimit),this._ctx.setDash(this._state.lineDash,this._state.lineDashOffset)}_applyCompositing(){let t=e[this._state.globalCompositeOperation];t!==void 0&&this._ctx.setOperator(t)}_getSurface(){return this._surface}_hasShadow(){if(this._state.shadowBlur===0&&this._state.shadowOffsetX===0&&this._state.shadowOffsetY===0)return!1;let e=s(this._state.shadowColor);return e!==null&&e.a>0}_deviceToUserDistance(e,t){let n=this._ctx.userToDevice(0,0),r=this._ctx.userToDevice(1,0),i=this._ctx.userToDevice(0,1),a=(r[0]??0)-(n[0]??0),o=(r[1]??0)-(n[1]??0),s=(i[0]??0)-(n[0]??0),c=(i[1]??0)-(n[1]??0),l=a*c-o*s;return Math.abs(l)<1e-10?[e,t]:[(c*e-s*t)/l,(-o*e+a*t)/l]}_renderShadow(e){}save(){this._ensureSurface(),this._stateStack.push(d(this._state)),this._ctx.save()}restore(){this._ensureSurface();let e=this._stateStack.pop();e&&(this._state=e,this._ctx.restore())}translate(e,t){this._ensureSurface(),this._ctx.translate(e,t)}rotate(e){this._ensureSurface(),this._ctx.rotate(e)}scale(e,t){this._ensureSurface(),this._ctx.scale(e,t)}transform(e,t,n,r,i,a){if(this._ensureSurface(),!Number.isFinite(e)||!Number.isFinite(t)||!Number.isFinite(n)||!Number.isFinite(r)||!Number.isFinite(i)||!Number.isFinite(a))return;let o=i,s=a,c=Math.hypot(e,t),l=Math.hypot(n,r),u=Math.atan2(t,e);this._ctx.translate(o,s),u!==0&&this._ctx.rotate(u),(c!==1||l!==1)&&this._ctx.scale(c,l)}setTransform(e,t,n,r,i,a){if(this._ensureSurface(),typeof e==`object`&&e){let t=e;this._ctx.identityMatrix(),this.transform(t.a??t.m11??1,t.b??t.m12??0,t.c??t.m21??0,t.d??t.m22??1,t.e??t.m41??0,t.f??t.m42??0)}else typeof e==`number`?(this._ctx.identityMatrix(),this.transform(e,t,n,r,i,a)):this._ctx.identityMatrix()}getTransform(){let e=this._ctx.userToDevice(0,0),t=this._ctx.userToDevice(1,0),n=this._ctx.userToDevice(0,1),r=e[0]??0,i=e[1]??0,a=(t[0]??0)-r,o=(t[1]??0)-i,s=(n[0]??0)-r,c=(n[1]??0)-i,l=globalThis.DOMMatrix;return typeof l==`function`?new l([a,o,s,c,r,i]):{a,b:o,c:s,d:c,e:r,f:i,m11:a,m12:o,m13:0,m14:0,m21:s,m22:c,m23:0,m24:0,m31:0,m32:0,m33:1,m34:0,m41:r,m42:i,m43:0,m44:1,is2D:!0,isIdentity:a===1&&o===0&&s===0&&c===1&&r===0&&i===0}}resetTransform(){this._ensureSurface(),this._ctx.identityMatrix()}get fillStyle(){return this._state.fillStyle}set fillStyle(e){if(typeof e==`string`){let t=s(e);t&&(this._state.fillStyle=e,this._state.fillColor=t)}else this._state.fillStyle=e}get strokeStyle(){return this._state.strokeStyle}set strokeStyle(e){if(typeof e==`string`){let t=s(e);t&&(this._state.strokeStyle=e,this._state.strokeColor=t)}else this._state.strokeStyle=e}get lineWidth(){return this._state.lineWidth}set lineWidth(e){e>0&&isFinite(e)&&(this._state.lineWidth=e)}get lineCap(){return this._state.lineCap}set lineCap(e){(e===`butt`||e===`round`||e===`square`)&&(this._state.lineCap=e)}get lineJoin(){return this._state.lineJoin}set lineJoin(e){(e===`miter`||e===`round`||e===`bevel`)&&(this._state.lineJoin=e)}get miterLimit(){return this._state.miterLimit}set miterLimit(e){e>0&&isFinite(e)&&(this._state.miterLimit=e)}get globalAlpha(){return this._state.globalAlpha}set globalAlpha(e){e>=0&&e<=1&&isFinite(e)&&(this._state.globalAlpha=e)}get globalCompositeOperation(){return this._state.globalCompositeOperation}set globalCompositeOperation(t){e[t]!==void 0&&(this._state.globalCompositeOperation=t)}get imageSmoothingEnabled(){return this._state.imageSmoothingEnabled}set imageSmoothingEnabled(e){this._state.imageSmoothingEnabled=!!e}get imageSmoothingQuality(){return this._state.imageSmoothingQuality}set imageSmoothingQuality(e){(e===`low`||e===`medium`||e===`high`)&&(this._state.imageSmoothingQuality=e)}setLineDash(e){e.some(e=>e<0||!isFinite(e))||(this._state.lineDash=[...e])}getLineDash(){return[...this._state.lineDash]}get lineDashOffset(){return this._state.lineDashOffset}set lineDashOffset(e){isFinite(e)&&(this._state.lineDashOffset=e)}get shadowColor(){return this._state.shadowColor}set shadowColor(e){this._state.shadowColor=e}get shadowBlur(){return this._state.shadowBlur}set shadowBlur(e){e>=0&&isFinite(e)&&(this._state.shadowBlur=e)}get shadowOffsetX(){return this._state.shadowOffsetX}set shadowOffsetX(e){isFinite(e)&&(this._state.shadowOffsetX=e)}get shadowOffsetY(){return this._state.shadowOffsetY}set shadowOffsetY(e){isFinite(e)&&(this._state.shadowOffsetY=e)}get font(){return this._state.font}set font(e){this._state.font=e}get textAlign(){return this._state.textAlign}set textAlign(e){this._state.textAlign=e}get textBaseline(){return this._state.textBaseline}set textBaseline(e){this._state.textBaseline=e}get direction(){return this._state.direction}set direction(e){this._state.direction=e}beginPath(){this._ensureSurface(),this._ctx.newPath()}moveTo(e,t){this._ensureSurface(),this._ctx.moveTo(e,t)}lineTo(e,t){this._ensureSurface(),this._ctx.lineTo(e,t)}closePath(){this._ensureSurface(),this._ctx.closePath()}bezierCurveTo(e,t,n,r,i,a){this._ensureSurface(),this._ctx.curveTo(e,t,n,r,i,a)}quadraticCurveTo(e,t,n,r){this._ensureSurface();let i,a;this._ctx.hasCurrentPoint()?[i,a]=this._ctx.getCurrentPoint():(i=e,a=t);let{cp1x:s,cp1y:c,cp2x:l,cp2y:u}=o(i,a,e,t,n,r);this._ctx.curveTo(s,c,l,u,n,r)}arc(e,t,n,r,i,a=!1){if(this._ensureSurface(),Math.abs(i-r)>=2*Math.PI){this._ctx.arc(e,t,n,0,2*Math.PI);return}a?this._ctx.arcNegative(e,t,n,r,i):this._ctx.arc(e,t,n,r,i)}arcTo(e,t,n,i,a){this._ensureSurface();let o,s;this._ctx.hasCurrentPoint()?[o,s]=this._ctx.getCurrentPoint():(o=e,s=t,this._ctx.moveTo(e,t)),r(this._ctx,o,s,e,t,n,i,a)}ellipse(e,t,n,r,a,o,s,c=!1){if(this._ensureSurface(),n<0||r<0)throw RangeError(`The radii provided are negative`);i(this._ctx,e,t,n,r,a,o,s,c)}rect(e,t,n,r){this._ensureSurface(),this._ctx.rectangle(e,t,n,r)}roundRect(e,t,n,r,i=0){this._ensureSurface(),a(this._ctx,e,t,n,r,i)}fill(e,t){this._ensureSurface(),this._applyCompositing(),this._applyFillStyle();let n;e instanceof l?(this._ctx.newPath(),e._replayOnCairo(this._ctx),n=t):n=e,this._ctx.setFillRule(n===`evenodd`?m.FillRule.EVEN_ODD:m.FillRule.WINDING),this._ctx.fillPreserve()}stroke(e){this._ensureSurface(),this._applyCompositing(),this._applyStrokeStyle(),this._applyLineStyle(),e instanceof l&&(this._ctx.newPath(),e._replayOnCairo(this._ctx)),this._ctx.strokePreserve()}fillRect(e,t,n,r){this._ensureSurface(),this._applyCompositing();let i=this._ctx.copyPath();this._hasShadow()&&this._renderShadow(()=>{this._ctx.newPath(),this._ctx.rectangle(e,t,n,r),this._ctx.fill()}),this._applyFillStyle(),this._ctx.newPath(),this._ctx.rectangle(e,t,n,r),this._ctx.fill(),this._ctx.newPath(),this._ctx.appendPath(i)}strokeRect(e,t,n,r){this._ensureSurface(),this._applyCompositing();let i=this._ctx.copyPath();this._hasShadow()&&this._renderShadow(()=>{this._ctx.newPath(),this._ctx.rectangle(e,t,n,r),this._ctx.stroke()}),this._applyStrokeStyle(),this._applyLineStyle(),this._ctx.newPath(),this._ctx.rectangle(e,t,n,r),this._ctx.stroke(),this._ctx.newPath(),this._ctx.appendPath(i)}clearRect(e,t,n,r){this._ensureSurface();let i=this._ctx.copyPath();this._ctx.save(),this._ctx.setOperator(m.Operator.CLEAR),this._ctx.newPath(),this._ctx.rectangle(e,t,n,r),this._ctx.fill(),this._ctx.restore(),this._ctx.newPath(),this._ctx.appendPath(i)}clip(e,t){this._ensureSurface();let n;e instanceof l?(this._ctx.newPath(),e._replayOnCairo(this._ctx),n=t):n=e,this._ctx.setFillRule(n===`evenodd`?m.FillRule.EVEN_ODD:m.FillRule.WINDING),this._ctx.clip()}isPointInPath(e,t,n,r){this._ensureSurface();let i,a,o;return e instanceof l?(this._ctx.newPath(),e._replayOnCairo(this._ctx),i=t,a=n,o=r):(i=e,a=t,o=n),this._ctx.setFillRule(o===`evenodd`?m.FillRule.EVEN_ODD:m.FillRule.WINDING),this._ctx.inFill(i,a)}isPointInStroke(e,t,n){return this._ensureSurface(),this._applyLineStyle(),e instanceof l?(this._ctx.newPath(),e._replayOnCairo(this._ctx),this._ctx.inStroke(t,n)):this._ctx.inStroke(e,t)}createLinearGradient(e,t,n,r){return new c(`linear`,e,t,n,r)}createRadialGradient(e,t,n,r,i,a){return new c(`radial`,e,t,r,i,n,a)}createPattern(e,t){return u.create(e,t)}createImageData(e,t){return typeof e==`number`?new p(Math.abs(e),Math.abs(t)):new p(e.width,e.height)}getImageData(e,t,n,r){this._ensureSurface(),this._surface.flush();let i=h.pixbuf_get_from_surface(this._surface,e,t,n,r);if(!i)return new p(n,r);let a=i.get_pixels(),o=i.get_has_alpha(),s=i.get_rowstride(),c=i.get_n_channels(),l=new Uint8ClampedArray(n*r*4);for(let e=0;e<r;e++)for(let t=0;t<n;t++){let r=e*s+t*c,i=(e*n+t)*4;l[i]=a[r],l[i+1]=a[r+1],l[i+2]=a[r+2],l[i+3]=o?a[r+3]:255}return new p(l,n,r)}putImageData(e,t,n,r,i,a,o){this._ensureSurface();let s=r??0,c=i??0,l=a??e.width,u=o??e.height,d=e.data,f=e.width,p=new Uint8Array(l*u*4);for(let e=0;e<u;e++)for(let t=0;t<l;t++){let n=((c+e)*f+(s+t))*4,r=(e*l+t)*4;p[r]=d[n],p[r+1]=d[n+1],p[r+2]=d[n+2],p[r+3]=d[n+3]}let _=g.Pixbuf.new_from_bytes(p,g.Colorspace.RGB,!0,8,l,u,l*4);this._ctx.save(),this._ctx.setOperator(m.Operator.SOURCE),h.cairo_set_source_pixbuf(this._ctx,_,t+s,n+c),this._ctx.rectangle(t+s,n+c,l,u),this._ctx.fill(),this._ctx.restore()}drawImage(e,t,n,r,i,a,o,s,c){this._ensureSurface(),this._applyCompositing();let l,u,d,f,p,g,_,v,y=this._getDrawImageSource(e);if(!y)return;let{pixbuf:b,imgWidth:x,imgHeight:S}=y;if(r===void 0?(l=0,u=0,d=x,f=S,p=t,g=n,_=x,v=S):a===void 0?(l=0,u=0,d=x,f=S,p=t,g=n,_=r,v=i):(l=t,u=n,d=r,f=i,p=a,g=o,_=s,v=c),d===0||f===0||_===0||v===0)return;this._ctx.save(),this._ctx.rectangle(p,g,_,v),this._ctx.clip(),this._ctx.translate(p,g),this._ctx.scale(_/d,v/f),this._ctx.translate(-l,-u),h.cairo_set_source_pixbuf(this._ctx,b,0,0);let C=this._ctx.getSource?.();if(C&&typeof C.setFilter==`function`){let e;e=this._state.imageSmoothingEnabled?this._state.imageSmoothingQuality===`high`?m.Filter.BEST:m.Filter.BILINEAR:m.Filter.NEAREST,C.setFilter(e)}this._state.globalAlpha<1?this._ctx.paintWithAlpha(this._state.globalAlpha):this._ctx.paint(),this._ctx.restore()}_getDrawImageSource(e){if(typeof e?.isPixbuf==`function`&&e.isPixbuf()){let t=e._pixbuf;return{pixbuf:t,imgWidth:t.get_width(),imgHeight:t.get_height()}}if(typeof e?.getContext==`function`){let t=e.getContext(`2d`);if(t&&typeof t._getSurface==`function`){let n=t._getSurface();n.flush();let r=h.pixbuf_get_from_surface(n,0,0,e.width,e.height);if(r)return{pixbuf:r,imgWidth:e.width,imgHeight:e.height}}}return null}_createTextLayout(e){let t=v.create_layout(this._ctx);t.set_text(e,-1),t.get_context().set_base_dir(_.Direction.LTR),t.context_changed();let n=this._parseFontToDescription(this._state.font);return t.set_font_description(n),t}_parseFontToDescription(e){let t=e.match(/^\s*(italic|oblique|normal)?\s*(small-caps|normal)?\s*(bold|bolder|lighter|[1-9]00|normal)?\s*(\d+(?:\.\d+)?)(px|pt|em|rem|%)?\s*(?:\/\S+)?\s*(.+)?$/i);if(!t)return _.font_description_from_string(e);let n=t[1]||``,r=t[3]||``,i=parseFloat(t[4])||10,a=(t[5]||`px`).toLowerCase(),o=(t[6]||`sans-serif`).replace(/['"]/g,``).trim();a===`pt`?i=i*96/72:a===`em`||a===`rem`?i*=16:a===`%`&&(i=i/100*16);let s=o;n===`italic`?s+=` Italic`:n===`oblique`&&(s+=` Oblique`),r===`bold`||r===`bolder`||parseInt(r)>=600?s+=` Bold`:(r===`lighter`||parseInt(r)>0&&parseInt(r)<=300)&&(s+=` Light`);let c=_.font_description_from_string(s);return c.set_absolute_size(i*_.SCALE),c}_getTextAlignOffset(e){let[,t]=e.get_pixel_extents(),n=t.width;switch(this._state.textAlign){case`center`:return-n/2;case`right`:case`end`:return-n;default:return 0}}_getTextBaselineOffset(e){let t=e.get_font_description()||this._parseFontToDescription(this._state.font),n=e.get_context().get_metrics(t,null),r=n.get_ascent()/_.SCALE,i=n.get_descent()/_.SCALE,a=r+i;switch(this._state.textBaseline){case`top`:return 0;case`hanging`:return-(r*.2);case`middle`:return-(a/2);case`alphabetic`:return-r;case`ideographic`:return-(r+i*.5);case`bottom`:return-a;default:return-r}}fillText(e,t,n,r){this._ensureSurface(),this._applyCompositing();let i=this._createTextLayout(e),a=this._getTextAlignOffset(i),o=this._getTextBaselineOffset(i);if(this._hasShadow()){let e=s(this._state.shadowColor);if(e){let[r,s]=this._deviceToUserDistance(this._state.shadowOffsetX,this._state.shadowOffsetY),c=this._state.shadowBlur,l;if(c>0){let[t]=this._deviceToUserDistance(c,0),[,n]=this._deviceToUserDistance(0,c);l=[[r,s,e.a],[r+t,s,e.a*.5],[r-t,s,e.a*.5],[r,s+n,e.a*.5],[r,s-n,e.a*.5]]}else l=[[r,s,e.a]];let u=this._state.imageSmoothingEnabled?m.Antialias.DEFAULT:m.Antialias.NONE;for(let[r,s,c]of l)this._ctx.save(),this._ctx.setAntialias(u),this._ctx.setSourceRGBA(e.r,e.g,e.b,c),this._ctx.moveTo(t+a+r,n+o+s),v.show_layout(this._ctx,i),this._ctx.restore()}}this._applyFillStyle(),this._ctx.save(),this._ctx.setAntialias(this._state.imageSmoothingEnabled?m.Antialias.DEFAULT:m.Antialias.NONE),this._ctx.moveTo(t+a,n+o),v.show_layout(this._ctx,i),this._ctx.restore()}strokeText(e,t,n,r){this._ensureSurface(),this._applyCompositing(),this._applyStrokeStyle(),this._applyLineStyle();let i=this._createTextLayout(e),a=this._getTextAlignOffset(i),o=this._getTextBaselineOffset(i);this._ctx.save(),this._ctx.setAntialias(this._state.imageSmoothingEnabled?m.Antialias.DEFAULT:m.Antialias.NONE),this._ctx.moveTo(t+a,n+o),v.layout_path(this._ctx,i),this._ctx.stroke(),this._ctx.restore()}measureText(e){this._ensureSurface();let t=this._createTextLayout(e),[n,r]=t.get_pixel_extents(),i=t.get_baseline()/_.SCALE,a=Math.max(0,i-n.y),o=Math.max(0,n.y+n.height-i),s=t.get_font_description()||this._parseFontToDescription(this._state.font),c=t.get_context().get_metrics(s,null),l=c.get_ascent()/_.SCALE,u=c.get_descent()/_.SCALE;return{width:r.width,actualBoundingBoxAscent:a,actualBoundingBoxDescent:o,actualBoundingBoxLeft:Math.max(0,-n.x),actualBoundingBoxRight:n.x+n.width,fontBoundingBoxAscent:l,fontBoundingBoxDescent:u,alphabeticBaseline:0,emHeightAscent:l,emHeightDescent:u,hangingBaseline:l*.8,ideographicBaseline:-u}}_toDataURL(e,t){this._surface.flush();let n=imports.gi.Gio,r=imports.gi.GLib,[,i]=r.file_open_tmp(`canvas-XXXXXX.png`);try{this._surface.writeToPNG(i);let[,e]=n.File.new_for_path(i).load_contents(null);return`data:image/png;base64,${r.base64_encode(e)}`}finally{try{r.unlink(i)}catch{}}}_dispose(){this._ctx.$dispose(),this._surface.finish()}};export{y as CanvasRenderingContext2D};