@gjsify/canvas2d-core 0.3.20 → 0.4.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/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/cairo-types.js +1 -0
- package/lib/esm/cairo-utils.js +1 -1
- package/lib/esm/canvas-gradient.js +1 -1
- package/lib/esm/canvas-path.js +1 -1
- package/lib/esm/canvas-pattern.js +1 -1
- package/lib/esm/canvas-rendering-context-2d.js +1 -1
- package/lib/esm/canvas-state.js +1 -1
- package/lib/esm/color.js +1 -1
- package/lib/esm/dom-types.js +1 -0
- package/lib/esm/image-data.js +1 -1
- package/lib/types/cairo-types.d.ts +20 -0
- package/lib/types/canvas-pattern.d.ts +1 -1
- package/lib/types/canvas-rendering-context-2d.d.ts +18 -6
- package/lib/types/dom-types.d.ts +88 -0
- package/package.json +10 -10
- package/src/cairo-types.ts +44 -0
- package/src/canvas-drawimage.spec.ts +47 -0
- package/src/canvas-pattern.ts +20 -11
- package/src/canvas-rendering-context-2d.ts +101 -59
- package/src/dom-types.ts +96 -0
- package/tmp/.tsbuildinfo +1 -1
|
@@ -10,6 +10,14 @@ import PangoCairo from 'gi://PangoCairo';
|
|
|
10
10
|
// HTMLCanvasElement type is provided by the DOM lib.
|
|
11
11
|
// Our @gjsify/dom-elements HTMLCanvasElement satisfies this interface.
|
|
12
12
|
|
|
13
|
+
import { asCairoPattern } from './cairo-types.js';
|
|
14
|
+
import {
|
|
15
|
+
type CanvasLike,
|
|
16
|
+
type CanvasGlobalThis,
|
|
17
|
+
type DOMMatrix2DLike,
|
|
18
|
+
isPixbufImageSource,
|
|
19
|
+
isCanvasImageSource,
|
|
20
|
+
} from './dom-types.js';
|
|
13
21
|
import { parseColor } from './color.js';
|
|
14
22
|
import {
|
|
15
23
|
quadraticToCubic,
|
|
@@ -26,12 +34,24 @@ import { CanvasGradient as OurCanvasGradient } from './canvas-gradient.js';
|
|
|
26
34
|
import { CanvasPattern as OurCanvasPattern } from './canvas-pattern.js';
|
|
27
35
|
import { Path2D } from './canvas-path.js';
|
|
28
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Options bag passed through the `getContext('2d', options)` factory. Mirrors
|
|
39
|
+
* the WHATWG `CanvasRenderingContext2DSettings` dictionary; fields are
|
|
40
|
+
* accepted but not yet honored by this implementation.
|
|
41
|
+
*/
|
|
42
|
+
export interface CanvasRenderingContext2DInit {
|
|
43
|
+
alpha?: boolean;
|
|
44
|
+
desynchronized?: boolean;
|
|
45
|
+
colorSpace?: PredefinedColorSpace;
|
|
46
|
+
willReadFrequently?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
29
49
|
/**
|
|
30
50
|
* CanvasRenderingContext2D backed by Cairo.ImageSurface.
|
|
31
51
|
* Implements the Canvas 2D API for GJS.
|
|
32
52
|
*/
|
|
33
53
|
export class CanvasRenderingContext2D {
|
|
34
|
-
readonly canvas:
|
|
54
|
+
readonly canvas: CanvasLike;
|
|
35
55
|
|
|
36
56
|
private _surface: Cairo.ImageSurface;
|
|
37
57
|
private _ctx: Cairo.Context;
|
|
@@ -40,7 +60,7 @@ export class CanvasRenderingContext2D {
|
|
|
40
60
|
private _surfaceWidth: number;
|
|
41
61
|
private _surfaceHeight: number;
|
|
42
62
|
|
|
43
|
-
constructor(canvas:
|
|
63
|
+
constructor(canvas: CanvasLike, _options?: CanvasRenderingContext2DInit) {
|
|
44
64
|
this.canvas = canvas;
|
|
45
65
|
this._surfaceWidth = canvas.width || 300;
|
|
46
66
|
this._surfaceHeight = canvas.height || 150;
|
|
@@ -115,18 +135,17 @@ export class CanvasRenderingContext2D {
|
|
|
115
135
|
* creation — so we re-apply it on every fill/stroke.
|
|
116
136
|
*/
|
|
117
137
|
private _applyPatternFilter(): void {
|
|
118
|
-
const pat = (this._ctx
|
|
119
|
-
if (pat
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
pat.setFilter(filter);
|
|
138
|
+
const pat = asCairoPattern(this._ctx.getSource?.());
|
|
139
|
+
if (!pat) return;
|
|
140
|
+
let filter: Cairo.Filter;
|
|
141
|
+
if (!this._state.imageSmoothingEnabled) {
|
|
142
|
+
filter = Cairo.Filter.NEAREST;
|
|
143
|
+
} else if (this._state.imageSmoothingQuality === 'high') {
|
|
144
|
+
filter = Cairo.Filter.BEST;
|
|
145
|
+
} else {
|
|
146
|
+
filter = Cairo.Filter.BILINEAR;
|
|
129
147
|
}
|
|
148
|
+
pat.setFilter(filter);
|
|
130
149
|
}
|
|
131
150
|
|
|
132
151
|
/** Apply line properties to the Cairo context. */
|
|
@@ -170,9 +189,9 @@ export class CanvasRenderingContext2D {
|
|
|
170
189
|
* regardless of any ctx.scale() or ctx.rotate() in effect.
|
|
171
190
|
*/
|
|
172
191
|
private _deviceToUserDistance(dx: number, dy: number): [number, number] {
|
|
173
|
-
const origin =
|
|
174
|
-
const xAxis =
|
|
175
|
-
const yAxis =
|
|
192
|
+
const origin = this._ctx.userToDevice(0, 0);
|
|
193
|
+
const xAxis = this._ctx.userToDevice(1, 0);
|
|
194
|
+
const yAxis = this._ctx.userToDevice(0, 1);
|
|
176
195
|
const a = (xAxis[0] ?? 0) - (origin[0] ?? 0);
|
|
177
196
|
const b = (xAxis[1] ?? 0) - (origin[1] ?? 0);
|
|
178
197
|
const c = (yAxis[0] ?? 0) - (origin[0] ?? 0);
|
|
@@ -304,9 +323,9 @@ export class CanvasRenderingContext2D {
|
|
|
304
323
|
// userToDevice(0, 0) = (e, f) — translation
|
|
305
324
|
// userToDevice(1, 0) = (a + e, b + f) — first basis vector
|
|
306
325
|
// userToDevice(0, 1) = (c + e, d + f) — second basis vector
|
|
307
|
-
const origin =
|
|
308
|
-
const xAxis =
|
|
309
|
-
const yAxis =
|
|
326
|
+
const origin = this._ctx.userToDevice(0, 0);
|
|
327
|
+
const xAxis = this._ctx.userToDevice(1, 0);
|
|
328
|
+
const yAxis = this._ctx.userToDevice(0, 1);
|
|
310
329
|
const e = origin[0] ?? 0;
|
|
311
330
|
const f = origin[1] ?? 0;
|
|
312
331
|
const a = (xAxis[0] ?? 0) - e;
|
|
@@ -314,11 +333,11 @@ export class CanvasRenderingContext2D {
|
|
|
314
333
|
const c = (yAxis[0] ?? 0) - e;
|
|
315
334
|
const d = (yAxis[1] ?? 0) - f;
|
|
316
335
|
|
|
317
|
-
const DOMMatrixCtor = (globalThis as
|
|
336
|
+
const DOMMatrixCtor = (globalThis as CanvasGlobalThis).DOMMatrix;
|
|
318
337
|
if (typeof DOMMatrixCtor === 'function') {
|
|
319
338
|
return new DOMMatrixCtor([a, b, c, d, e, f]);
|
|
320
339
|
}
|
|
321
|
-
|
|
340
|
+
const fallback: DOMMatrix2DLike = {
|
|
322
341
|
a, b, c, d, e, f,
|
|
323
342
|
m11: a, m12: b, m13: 0, m14: 0,
|
|
324
343
|
m21: c, m22: d, m23: 0, m24: 0,
|
|
@@ -326,7 +345,8 @@ export class CanvasRenderingContext2D {
|
|
|
326
345
|
m41: e, m42: f, m43: 0, m44: 1,
|
|
327
346
|
is2D: true,
|
|
328
347
|
isIdentity: (a === 1 && b === 0 && c === 0 && d === 1 && e === 0 && f === 0),
|
|
329
|
-
}
|
|
348
|
+
};
|
|
349
|
+
return fallback as unknown as DOMMatrix;
|
|
330
350
|
}
|
|
331
351
|
|
|
332
352
|
resetTransform(): void {
|
|
@@ -693,15 +713,15 @@ export class CanvasRenderingContext2D {
|
|
|
693
713
|
// ---- Gradient / Pattern factories ----
|
|
694
714
|
|
|
695
715
|
createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient {
|
|
696
|
-
return new OurCanvasGradient('linear', x0, y0, x1, y1) as
|
|
716
|
+
return new OurCanvasGradient('linear', x0, y0, x1, y1) as unknown as CanvasGradient;
|
|
697
717
|
}
|
|
698
718
|
|
|
699
719
|
createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient {
|
|
700
|
-
return new OurCanvasGradient('radial', x0, y0, x1, y1, r0, r1) as
|
|
720
|
+
return new OurCanvasGradient('radial', x0, y0, x1, y1, r0, r1) as unknown as CanvasGradient;
|
|
701
721
|
}
|
|
702
722
|
|
|
703
|
-
createPattern(image:
|
|
704
|
-
return OurCanvasPattern.create(image, repetition) as
|
|
723
|
+
createPattern(image: unknown, repetition: string | null): CanvasPattern | null {
|
|
724
|
+
return OurCanvasPattern.create(image, repetition) as unknown as CanvasPattern | null;
|
|
705
725
|
}
|
|
706
726
|
|
|
707
727
|
// ---- Image data methods ----
|
|
@@ -710,9 +730,9 @@ export class CanvasRenderingContext2D {
|
|
|
710
730
|
createImageData(imagedata: ImageData): ImageData;
|
|
711
731
|
createImageData(swOrImageData: number | ImageData, sh?: number): ImageData {
|
|
712
732
|
if (typeof swOrImageData === 'number') {
|
|
713
|
-
return new OurImageData(Math.abs(swOrImageData), Math.abs(sh!)) as
|
|
733
|
+
return new OurImageData(Math.abs(swOrImageData), Math.abs(sh!)) as unknown as ImageData;
|
|
714
734
|
}
|
|
715
|
-
return new OurImageData(swOrImageData.width, swOrImageData.height) as
|
|
735
|
+
return new OurImageData(swOrImageData.width, swOrImageData.height) as unknown as ImageData;
|
|
716
736
|
}
|
|
717
737
|
|
|
718
738
|
getImageData(sx: number, sy: number, sw: number, sh: number): ImageData {
|
|
@@ -722,7 +742,7 @@ export class CanvasRenderingContext2D {
|
|
|
722
742
|
// Use Gdk.pixbuf_get_from_surface to read pixels
|
|
723
743
|
const pixbuf = Gdk.pixbuf_get_from_surface(this._surface, sx, sy, sw, sh);
|
|
724
744
|
if (!pixbuf) {
|
|
725
|
-
return new OurImageData(sw, sh) as
|
|
745
|
+
return new OurImageData(sw, sh) as unknown as ImageData;
|
|
726
746
|
}
|
|
727
747
|
|
|
728
748
|
const pixels = pixbuf.get_pixels();
|
|
@@ -742,7 +762,7 @@ export class CanvasRenderingContext2D {
|
|
|
742
762
|
}
|
|
743
763
|
}
|
|
744
764
|
|
|
745
|
-
return new OurImageData(out, sw, sh) as
|
|
765
|
+
return new OurImageData(out, sw, sh) as unknown as ImageData;
|
|
746
766
|
}
|
|
747
767
|
|
|
748
768
|
putImageData(imageData: ImageData, dx: number, dy: number, dirtyX?: number, dirtyY?: number, dirtyWidth?: number, dirtyHeight?: number): void {
|
|
@@ -784,7 +804,7 @@ export class CanvasRenderingContext2D {
|
|
|
784
804
|
// putImageData per spec ignores compositing — always uses SOURCE operator
|
|
785
805
|
this._ctx.save();
|
|
786
806
|
this._ctx.setOperator(Cairo.Operator.SOURCE);
|
|
787
|
-
Gdk.cairo_set_source_pixbuf(this._ctx
|
|
807
|
+
Gdk.cairo_set_source_pixbuf(this._ctx, pixbuf, dx + sx, dy + sy);
|
|
788
808
|
this._ctx.rectangle(dx + sx, dy + sy, sw, sh);
|
|
789
809
|
this._ctx.fill();
|
|
790
810
|
this._ctx.restore();
|
|
@@ -792,11 +812,11 @@ export class CanvasRenderingContext2D {
|
|
|
792
812
|
|
|
793
813
|
// ---- drawImage ----
|
|
794
814
|
|
|
795
|
-
drawImage(image:
|
|
796
|
-
drawImage(image:
|
|
797
|
-
drawImage(image:
|
|
815
|
+
drawImage(image: unknown, dx: number, dy: number): void;
|
|
816
|
+
drawImage(image: unknown, dx: number, dy: number, dw: number, dh: number): void;
|
|
817
|
+
drawImage(image: unknown, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
|
|
798
818
|
drawImage(
|
|
799
|
-
image:
|
|
819
|
+
image: unknown,
|
|
800
820
|
a1: number, a2: number,
|
|
801
821
|
a3?: number, a4?: number,
|
|
802
822
|
a5?: number, a6?: number,
|
|
@@ -831,7 +851,20 @@ export class CanvasRenderingContext2D {
|
|
|
831
851
|
// rectangle is a no-op (and MUST NOT throw). Without this guard,
|
|
832
852
|
// `scale(dw / sw, dh / sh)` produces 0 or Infinity which Cairo
|
|
833
853
|
// rejects with "invalid matrix (not invertible)".
|
|
834
|
-
|
|
854
|
+
//
|
|
855
|
+
// Non-finite (NaN / Infinity / -Infinity) inputs reach us when the
|
|
856
|
+
// caller derives a dimension from a not-yet-resized canvas (e.g.
|
|
857
|
+
// Excalibur's logo overlay computes `Math.min(logoWidth, n * 0.75)`
|
|
858
|
+
// before the engine's pixelRatio / canvas size are known). Treat
|
|
859
|
+
// them the same as 0: spec-correct, and avoids cascading Cairo
|
|
860
|
+
// matrix failures that abort frames mid-paint.
|
|
861
|
+
if (
|
|
862
|
+
!Number.isFinite(sx) || !Number.isFinite(sy) ||
|
|
863
|
+
!Number.isFinite(sw) || !Number.isFinite(sh) ||
|
|
864
|
+
!Number.isFinite(dx) || !Number.isFinite(dy) ||
|
|
865
|
+
!Number.isFinite(dw) || !Number.isFinite(dh) ||
|
|
866
|
+
sw === 0 || sh === 0 || dw === 0 || dh === 0
|
|
867
|
+
) {
|
|
835
868
|
return;
|
|
836
869
|
}
|
|
837
870
|
|
|
@@ -847,7 +880,7 @@ export class CanvasRenderingContext2D {
|
|
|
847
880
|
this._ctx.scale(dw / sw, dh / sh);
|
|
848
881
|
this._ctx.translate(-sx, -sy);
|
|
849
882
|
|
|
850
|
-
Gdk.cairo_set_source_pixbuf(this._ctx
|
|
883
|
+
Gdk.cairo_set_source_pixbuf(this._ctx, pixbuf, 0, 0);
|
|
851
884
|
|
|
852
885
|
// Apply Cairo interpolation filter based on imageSmoothingEnabled +
|
|
853
886
|
// imageSmoothingQuality. setSource installs a fresh SurfacePattern and
|
|
@@ -858,16 +891,17 @@ export class CanvasRenderingContext2D {
|
|
|
858
891
|
//
|
|
859
892
|
// Cairo.Filter values (verified runtime in GJS 1.86):
|
|
860
893
|
// FAST=0 GOOD=1 BEST=2 NEAREST=3 BILINEAR=4 GAUSSIAN=5
|
|
861
|
-
// GIR typings are
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
894
|
+
// GIR typings are missing setFilter on Pattern — `asCairoPattern`
|
|
895
|
+
// narrows to the augmented shape (see cairo-types.ts).
|
|
896
|
+
const pat = asCairoPattern(this._ctx.getSource?.());
|
|
897
|
+
if (pat) {
|
|
898
|
+
let filter: Cairo.Filter;
|
|
865
899
|
if (!this._state.imageSmoothingEnabled) {
|
|
866
|
-
filter = Cairo.Filter.NEAREST
|
|
900
|
+
filter = Cairo.Filter.NEAREST;
|
|
867
901
|
} else if (this._state.imageSmoothingQuality === 'high') {
|
|
868
|
-
filter = Cairo.Filter.BEST
|
|
902
|
+
filter = Cairo.Filter.BEST;
|
|
869
903
|
} else {
|
|
870
|
-
filter = Cairo.Filter.BILINEAR
|
|
904
|
+
filter = Cairo.Filter.BILINEAR;
|
|
871
905
|
}
|
|
872
906
|
pat.setFilter(filter);
|
|
873
907
|
}
|
|
@@ -878,29 +912,37 @@ export class CanvasRenderingContext2D {
|
|
|
878
912
|
// doesn't support per-draw alpha, so paint() is the spec-correct
|
|
879
913
|
// choice for drawImage. The clip above confines the paint to dx,dy,dw,dh.
|
|
880
914
|
if (this._state.globalAlpha < 1) {
|
|
881
|
-
|
|
915
|
+
this._ctx.paintWithAlpha(this._state.globalAlpha);
|
|
882
916
|
} else {
|
|
883
917
|
this._ctx.paint();
|
|
884
918
|
}
|
|
885
919
|
this._ctx.restore();
|
|
886
920
|
}
|
|
887
921
|
|
|
888
|
-
private _getDrawImageSource(image:
|
|
922
|
+
private _getDrawImageSource(image: unknown): { pixbuf: GdkPixbuf.Pixbuf; imgWidth: number; imgHeight: number } | null {
|
|
889
923
|
// HTMLImageElement (GdkPixbuf-backed)
|
|
890
|
-
if (
|
|
891
|
-
const pixbuf = image._pixbuf
|
|
924
|
+
if (isPixbufImageSource(image)) {
|
|
925
|
+
const pixbuf = image._pixbuf;
|
|
892
926
|
return { pixbuf, imgWidth: pixbuf.get_width(), imgHeight: pixbuf.get_height() };
|
|
893
927
|
}
|
|
894
928
|
|
|
895
929
|
// HTMLCanvasElement with a 2D context
|
|
896
|
-
if (
|
|
930
|
+
if (isCanvasImageSource(image)) {
|
|
931
|
+
const w = image.width ?? 0;
|
|
932
|
+
const h = image.height ?? 0;
|
|
933
|
+
// Reject non-positive / non-finite dimensions before they reach
|
|
934
|
+
// GdkPixbuf — `pixbuf_get_from_surface` logs a GLib-CRITICAL on
|
|
935
|
+
// `width > 0 && height > 0` assertion failure for NaN/0 inputs.
|
|
936
|
+
if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
897
939
|
const ctx2d = image.getContext('2d');
|
|
898
940
|
if (ctx2d && typeof ctx2d._getSurface === 'function') {
|
|
899
|
-
const surface = ctx2d._getSurface()
|
|
941
|
+
const surface = ctx2d._getSurface();
|
|
900
942
|
surface.flush();
|
|
901
|
-
const pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0,
|
|
943
|
+
const pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0, w, h);
|
|
902
944
|
if (pixbuf) {
|
|
903
|
-
return { pixbuf, imgWidth:
|
|
945
|
+
return { pixbuf, imgWidth: w, imgHeight: h };
|
|
904
946
|
}
|
|
905
947
|
}
|
|
906
948
|
}
|
|
@@ -912,7 +954,7 @@ export class CanvasRenderingContext2D {
|
|
|
912
954
|
|
|
913
955
|
/** Create a PangoCairo layout configured with current font/text settings. */
|
|
914
956
|
private _createTextLayout(text: string): Pango.Layout {
|
|
915
|
-
const layout = PangoCairo.create_layout(this._ctx
|
|
957
|
+
const layout = PangoCairo.create_layout(this._ctx);
|
|
916
958
|
layout.set_text(text, -1);
|
|
917
959
|
|
|
918
960
|
// Force LTR base direction so text is never rendered mirrored
|
|
@@ -1056,10 +1098,10 @@ export class CanvasRenderingContext2D {
|
|
|
1056
1098
|
const aa = this._state.imageSmoothingEnabled ? Cairo.Antialias.DEFAULT : Cairo.Antialias.NONE;
|
|
1057
1099
|
for (const [tx, ty, ta] of taps) {
|
|
1058
1100
|
this._ctx.save();
|
|
1059
|
-
|
|
1101
|
+
this._ctx.setAntialias(aa);
|
|
1060
1102
|
this._ctx.setSourceRGBA(sc.r, sc.g, sc.b, ta);
|
|
1061
1103
|
this._ctx.moveTo(x + xOff + tx, y + yOff + ty);
|
|
1062
|
-
PangoCairo.show_layout(this._ctx
|
|
1104
|
+
PangoCairo.show_layout(this._ctx, layout);
|
|
1063
1105
|
this._ctx.restore();
|
|
1064
1106
|
}
|
|
1065
1107
|
}
|
|
@@ -1069,9 +1111,9 @@ export class CanvasRenderingContext2D {
|
|
|
1069
1111
|
this._ctx.save();
|
|
1070
1112
|
// Disable anti-aliasing so pixel/bitmap fonts render crisp (matching browser
|
|
1071
1113
|
// behaviour for fonts with no outline hints). cairo_save/restore covers antialias.
|
|
1072
|
-
|
|
1114
|
+
this._ctx.setAntialias(this._state.imageSmoothingEnabled ? Cairo.Antialias.DEFAULT : Cairo.Antialias.NONE);
|
|
1073
1115
|
this._ctx.moveTo(x + xOff, y + yOff);
|
|
1074
|
-
PangoCairo.show_layout(this._ctx
|
|
1116
|
+
PangoCairo.show_layout(this._ctx, layout);
|
|
1075
1117
|
this._ctx.restore();
|
|
1076
1118
|
}
|
|
1077
1119
|
|
|
@@ -1086,9 +1128,9 @@ export class CanvasRenderingContext2D {
|
|
|
1086
1128
|
const yOff = this._getTextBaselineOffset(layout);
|
|
1087
1129
|
|
|
1088
1130
|
this._ctx.save();
|
|
1089
|
-
|
|
1131
|
+
this._ctx.setAntialias(this._state.imageSmoothingEnabled ? Cairo.Antialias.DEFAULT : Cairo.Antialias.NONE);
|
|
1090
1132
|
this._ctx.moveTo(x + xOff, y + yOff);
|
|
1091
|
-
PangoCairo.layout_path(this._ctx
|
|
1133
|
+
PangoCairo.layout_path(this._ctx, layout);
|
|
1092
1134
|
this._ctx.stroke();
|
|
1093
1135
|
this._ctx.restore();
|
|
1094
1136
|
}
|
package/src/dom-types.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Internal interface declarations for the DOM-shaped objects that
|
|
2
|
+
// CanvasRenderingContext2D consumes.
|
|
3
|
+
//
|
|
4
|
+
// canvas2d-core deliberately has *no* dependency on @gjsify/dom-elements (that
|
|
5
|
+
// would create a cycle: dom-elements → canvas2d-core → dom-elements). Instead
|
|
6
|
+
// it accepts duck-typed inputs that match the relevant slice of the WHATWG
|
|
7
|
+
// Canvas 2D API. These interfaces document those slices and let the rest of
|
|
8
|
+
// the package work against concrete types instead of `any`.
|
|
9
|
+
|
|
10
|
+
import type Cairo from 'cairo';
|
|
11
|
+
import type GdkPixbuf from 'gi://GdkPixbuf';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The HTMLCanvasElement-shaped object passed into the
|
|
15
|
+
* `CanvasRenderingContext2D` constructor by the registered context factory.
|
|
16
|
+
*
|
|
17
|
+
* `@gjsify/dom-elements`' `HTMLCanvasElement` satisfies this — but so does any
|
|
18
|
+
* lightweight `{ width, height }` mock used by unit tests. Width/height
|
|
19
|
+
* default to the WHATWG canvas defaults (300×150) when missing.
|
|
20
|
+
*/
|
|
21
|
+
export interface CanvasLike {
|
|
22
|
+
width?: number;
|
|
23
|
+
height?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* GdkPixbuf-backed image source produced by `@gjsify/dom-elements`'
|
|
28
|
+
* `HTMLImageElement` (and other pixbuf-bearing wrappers).
|
|
29
|
+
*
|
|
30
|
+
* The `isPixbuf()` brand keeps us decoupled from the concrete class while
|
|
31
|
+
* preventing accidental matches against unrelated objects.
|
|
32
|
+
*/
|
|
33
|
+
export interface PixbufImageSource {
|
|
34
|
+
isPixbuf(): boolean;
|
|
35
|
+
/** @internal — populated by HTMLImageElement once decoding completes. */
|
|
36
|
+
_pixbuf: GdkPixbuf.Pixbuf;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Canvas-like image source carrying a 2D context whose backing surface can be
|
|
41
|
+
* sampled (used for `drawImage(canvas, …)` and `createPattern(canvas, …)`).
|
|
42
|
+
*/
|
|
43
|
+
export interface CanvasImageSource extends CanvasLike {
|
|
44
|
+
getContext(contextId: '2d', options?: unknown): CanvasContext2DLike | null;
|
|
45
|
+
getContext(contextId: string, options?: unknown): unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The minimal slice of `CanvasRenderingContext2D` required to extract pixel
|
|
50
|
+
* data for `drawImage` / `createPattern`. Our own context naturally satisfies
|
|
51
|
+
* this through `_getSurface()`.
|
|
52
|
+
*/
|
|
53
|
+
export interface CanvasContext2DLike {
|
|
54
|
+
/** @internal — exposes the Cairo backing surface. */
|
|
55
|
+
_getSurface?(): Cairo.ImageSurface;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Type guard for {@link PixbufImageSource}. */
|
|
59
|
+
export function isPixbufImageSource(value: unknown): value is PixbufImageSource {
|
|
60
|
+
if (value === null || typeof value !== 'object') return false;
|
|
61
|
+
const candidate = value as { isPixbuf?: unknown };
|
|
62
|
+
return typeof candidate.isPixbuf === 'function' && (value as PixbufImageSource).isPixbuf();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Type guard for {@link CanvasImageSource}. */
|
|
66
|
+
export function isCanvasImageSource(value: unknown): value is CanvasImageSource {
|
|
67
|
+
if (value === null || typeof value !== 'object') return false;
|
|
68
|
+
return typeof (value as { getContext?: unknown }).getContext === 'function';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Minimal `DOMMatrix` shape returned by `CanvasRenderingContext2D.getTransform()`
|
|
73
|
+
* when no native `DOMMatrix` constructor is registered. Mirrors the
|
|
74
|
+
* `is2D`-only subset of the WHATWG matrix interface.
|
|
75
|
+
*/
|
|
76
|
+
export interface DOMMatrix2DLike {
|
|
77
|
+
a: number; b: number; c: number; d: number; e: number; f: number;
|
|
78
|
+
m11: number; m12: number; m13: number; m14: number;
|
|
79
|
+
m21: number; m22: number; m23: number; m24: number;
|
|
80
|
+
m31: number; m32: number; m33: number; m34: number;
|
|
81
|
+
m41: number; m42: number; m43: number; m44: number;
|
|
82
|
+
is2D: boolean;
|
|
83
|
+
isIdentity: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Constructor signature for the platform `DOMMatrix`. Lets us reach the
|
|
88
|
+
* runtime constructor through `globalThis` without an `any` cast when an
|
|
89
|
+
* embedder (e.g. `@gjsify/dom-elements`) has registered one.
|
|
90
|
+
*/
|
|
91
|
+
export type DOMMatrixConstructor = new (init?: number[] | string) => DOMMatrix;
|
|
92
|
+
|
|
93
|
+
/** Subset of `globalThis` we touch inside this package. */
|
|
94
|
+
export interface CanvasGlobalThis {
|
|
95
|
+
DOMMatrix?: DOMMatrixConstructor;
|
|
96
|
+
}
|