@gjsify/node-gi 0.64.1 → 0.64.4

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 CHANGED
@@ -148,6 +148,15 @@ export halves, the legacy `imports.*` modules, cairo, and the measured
148
148
  divergences from GJS — is
149
149
  [docs/node-gi-gjs-surface.md](../../../docs/node-gi-gjs-surface.md).
150
150
 
151
+ One divergence is deliberate rather than measured, and it only exists under the
152
+ `nativescript` export condition: there `registerClass` accepts a parent class
153
+ that is not a GObject class, so an `@nativescript/core` widget can BE a real
154
+ GObject subclass (properties, signals, `bind_property`, `Gio.ListStore`
155
+ identity) while staying the widget its layout demands. Every other host throws
156
+ GJS's invalid-base-class error, unchanged — see
157
+ [§ `registerClass` over a FOREIGN parent class](../../../docs/node-gi-gjs-surface.md)
158
+ and [ADR 0105](../../../docs/adr/0105-real-gobject-backs-the-nativescript-subset-on-android.md).
159
+
151
160
  ## Runtimes (Node / Bun / Deno)
152
161
 
153
162
  The engine is a **Node-API** addon, so the same binary loads and runs on **Node,
package/cairo.d.ts CHANGED
@@ -36,6 +36,16 @@ export interface FontExtents {
36
36
  maxYAdvance: number;
37
37
  }
38
38
 
39
+ /** `cairo_matrix_t` as the plain object GJS passes around (no matrix wrapper). */
40
+ export interface Matrix {
41
+ xx: number;
42
+ yx: number;
43
+ xy: number;
44
+ yy: number;
45
+ x0: number;
46
+ y0: number;
47
+ }
48
+
39
49
  /** cairo drawing context (`cairo_t`). */
40
50
  export class Context {
41
51
  constructor(surface: Surface);
@@ -53,10 +63,17 @@ export class Context {
53
63
  textPath(utf8: string): void;
54
64
  textExtents(utf8: string): TextExtents;
55
65
  fontExtents(): FontExtents;
66
+ /**
67
+ * The current font matrix. GJS has no cairo matrix wrapper
68
+ * (`getMatrix`/`setMatrix` stay unbound), so it travels as a plain object.
69
+ */
70
+ getFontMatrix(): Matrix;
71
+ setFontMatrix(matrix: Matrix): void;
56
72
  rectangle(x: number, y: number, width: number, height: number): void;
57
73
  arc(xc: number, yc: number, radius: number, angle1: number, angle2: number): void;
58
74
  arcNegative(xc: number, yc: number, radius: number, angle1: number, angle2: number): void;
59
75
  curveTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): void;
76
+ relCurveTo(dx1: number, dy1: number, dx2: number, dy2: number, dx3: number, dy3: number): void;
60
77
  fill(): void;
61
78
  fillPreserve(): void;
62
79
  stroke(): void;
@@ -65,6 +82,19 @@ export class Context {
65
82
  paintWithAlpha(alpha: number): void;
66
83
  clip(): void;
67
84
  clipPreserve(): void;
85
+ resetClip(): void;
86
+ /** Redirect drawing to an intermediate group surface. */
87
+ pushGroup(): void;
88
+ /** Like {@link pushGroup} but with an explicit {@link Content} for the group surface. */
89
+ pushGroupWithContent(content: number): void;
90
+ /** End the current group and return it as a pattern (concrete subclass). */
91
+ popGroup(): Pattern;
92
+ /** End the current group and install it as this context's source. */
93
+ popGroupToSource(): void;
94
+ /** Paint the current source through `pattern`'s alpha. */
95
+ mask(pattern: Pattern): void;
96
+ /** Paint the current source through `surface`'s alpha, offset to `(x, y)`. */
97
+ maskSurface(surface: Surface, x: number, y: number): void;
68
98
  translate(tx: number, ty: number): void;
69
99
  scale(sx: number, sy: number): void;
70
100
  rotate(angle: number): void;
@@ -74,6 +104,10 @@ export class Context {
74
104
  setSourceSurface(surface: Surface, x: number, y: number): void;
75
105
  /** The context's current source pattern (own reference; concrete subclass). */
76
106
  getSource(): Pattern;
107
+ /** The surface this context draws to (own reference; concrete subclass). */
108
+ getTarget(): Surface;
109
+ /** The target of the current group, or {@link getTarget} outside a group. */
110
+ getGroupTarget(): Surface;
77
111
  identityMatrix(): void;
78
112
  newSubPath(): void;
79
113
  /** User → device coordinates as `[x, y]` (GJS return shape). */
@@ -111,6 +145,10 @@ export class Context {
111
145
  setOperator(op: number): void;
112
146
  setMiterLimit(limit: number): void;
113
147
  setTolerance(tolerance: number): void;
148
+ /** Emit the current page and clear it (paginated surfaces; a no-op elsewhere). */
149
+ copyPage(): void;
150
+ /** Like {@link copyPage} but also clears the current page's contents. */
151
+ showPage(): void;
114
152
  status(): number;
115
153
  $dispose(): void;
116
154
  }
package/cairo.js CHANGED
@@ -23,16 +23,25 @@
23
23
  // (the `Object.assign({}, imports._cairo, cairoNative)` merge shape).
24
24
  // Copyright (c) 2010 litl, LLC / GJS contributors. MIT OR LGPL-2.0-or-later.
25
25
 
26
- import native from './index.js';
26
+ import native, { ensureNative, onNativeLoaded } from './index.js';
27
27
 
28
- const c = native.__cairo;
29
- if (c === undefined) {
30
- throw new Error(
28
+ // Bound when the addon loads: at import on desktop, on first use on a lazy host (NativeScript).
29
+ // Every call that can run before any cairo object exists goes through `engine()`.
30
+ let c;
31
+
32
+ function missingBinding() {
33
+ return new Error(
31
34
  '@gjsify/node-gi/cairo: the native cairo binding is missing from the addon. ' +
32
35
  'Rebuild @gjsify/node-gi (node-gyp rebuild) — the addon must link libcairo.',
33
36
  );
34
37
  }
35
38
 
39
+ function engine() {
40
+ ensureNative();
41
+ if (c === undefined) throw missingBinding();
42
+ return c;
43
+ }
44
+
36
45
  // ---- enums (ported verbatim from refs/gjs/modules/core/_cairo.js) -----------
37
46
 
38
47
  export const Antialias = { DEFAULT: 0, NONE: 1, GRAY: 2, SUBPIXEL: 3 };
@@ -141,7 +150,7 @@ function handleOf(x, what) {
141
150
  /** cairo drawing context. `new cairo.Context(surface)` wraps `cairo_create`. */
142
151
  export class Context {
143
152
  constructor(surface) {
144
- setHandle(this, c.contextCreate(handleOf(surface, 'cairo.Surface')));
153
+ setHandle(this, engine().contextCreate(handleOf(surface, 'cairo.Surface')));
145
154
  }
146
155
 
147
156
  save() {
@@ -181,6 +190,9 @@ export class Context {
181
190
  curveTo(x1, y1, x2, y2, x3, y3) {
182
191
  c.curveTo(this[H], x1, y1, x2, y2, x3, y3);
183
192
  }
193
+ relCurveTo(dx1, dy1, dx2, dy2, dx3, dy3) {
194
+ c.relCurveTo(this[H], dx1, dy1, dx2, dy2, dx3, dy3);
195
+ }
184
196
 
185
197
  fill() {
186
198
  c.fill(this[H]);
@@ -206,6 +218,33 @@ export class Context {
206
218
  clipPreserve() {
207
219
  c.clipPreserve(this[H]);
208
220
  }
221
+ resetClip() {
222
+ c.resetClip(this[H]);
223
+ }
224
+ /** Redirect drawing to an intermediate group surface. */
225
+ pushGroup() {
226
+ c.pushGroup(this[H]);
227
+ }
228
+ /** Like {@link pushGroup} but with an explicit {@link Content} for the group surface. */
229
+ pushGroupWithContent(content) {
230
+ c.pushGroupWithContent(this[H], content);
231
+ }
232
+ /** End the current group and return it as a pattern (concrete subclass). */
233
+ popGroup() {
234
+ return wrapPatternHandle(c.popGroup(this[H]));
235
+ }
236
+ /** End the current group and install it as this context's source. */
237
+ popGroupToSource() {
238
+ c.popGroupToSource(this[H]);
239
+ }
240
+ /** Paint the current source through `pattern`'s alpha. */
241
+ mask(pattern) {
242
+ c.mask(this[H], handleOf(pattern, 'cairo.Pattern'));
243
+ }
244
+ /** Paint the current source through `surface`'s alpha, offset to `(x, y)`. */
245
+ maskSurface(surface, x, y) {
246
+ c.maskSurface(this[H], handleOf(surface, 'cairo.Surface'), x, y);
247
+ }
209
248
 
210
249
  translate(tx, ty) {
211
250
  c.translate(this[H], tx, ty);
@@ -289,6 +328,14 @@ export class Context {
289
328
  getSource() {
290
329
  return wrapPatternHandle(c.getSource(this[H]));
291
330
  }
331
+ /** The surface this context draws to (a ref of its own — safe to keep). */
332
+ getTarget() {
333
+ return wrapSurfaceHandle(c.getTarget(this[H]));
334
+ }
335
+ /** The target of the current group, or {@link getTarget} outside a group. */
336
+ getGroupTarget() {
337
+ return wrapSurfaceHandle(c.getGroupTarget(this[H]));
338
+ }
292
339
 
293
340
  setLineWidth(width) {
294
341
  c.setLineWidth(this[H], width);
@@ -355,6 +402,18 @@ export class Context {
355
402
  fontExtents() {
356
403
  return c.fontExtents(this[H]);
357
404
  }
405
+ /**
406
+ * The current font matrix as `{ xx, yx, xy, yy, x0, y0}`. GJS has no cairo
407
+ * matrix wrapper (`getMatrix`/`setMatrix` stay unbound), so it travels as
408
+ * a plain object of the six doubles.
409
+ */
410
+ getFontMatrix() {
411
+ return c.getFontMatrix(this[H]);
412
+ }
413
+ /** Set the font matrix from `{ xx, yx, xy, yy, x0, y0}`. */
414
+ setFontMatrix(matrix) {
415
+ c.setFontMatrix(this[H], matrix);
416
+ }
358
417
  /** Stroke bounding box as `[x1, y1, x2, y2]`. */
359
418
  strokeExtents() {
360
419
  return c.strokeExtents(this[H]);
@@ -385,6 +444,15 @@ export class Context {
385
444
  c.setTolerance(this[H], tolerance);
386
445
  }
387
446
 
447
+ /** Emit the current page and clear it (paginated surfaces; a no-op elsewhere). */
448
+ copyPage() {
449
+ c.copyPage(this[H]);
450
+ }
451
+ /** Like {@link copyPage} but also clears the current page's contents. */
452
+ showPage() {
453
+ c.showPage(this[H]);
454
+ }
455
+
388
456
  status() {
389
457
  return c.status(this[H]);
390
458
  }
@@ -422,14 +490,14 @@ export class Surface {
422
490
  export class ImageSurface extends Surface {
423
491
  constructor(format, width, height) {
424
492
  super();
425
- setHandle(this, c.imageSurfaceCreate(format, width, height));
493
+ setHandle(this, engine().imageSurfaceCreate(format, width, height));
426
494
  }
427
495
 
428
496
  static create(format, width, height) {
429
497
  return new ImageSurface(format, width, height);
430
498
  }
431
499
  static createFromPNG(filename) {
432
- return wrapRaw(ImageSurface, c.imageSurfaceCreateFromPNG(filename));
500
+ return wrapRaw(ImageSurface, engine().imageSurfaceCreateFromPNG(filename));
433
501
  }
434
502
 
435
503
  getWidth() {
@@ -471,10 +539,10 @@ export class Pattern {
471
539
  /** A solid-color pattern (`cairo_pattern_create_rgb[a]`). */
472
540
  export class SolidPattern extends Pattern {
473
541
  static createRGB(red, green, blue) {
474
- return wrapRaw(SolidPattern, c.solidPatternCreateRGB(red, green, blue));
542
+ return wrapRaw(SolidPattern, engine().solidPatternCreateRGB(red, green, blue));
475
543
  }
476
544
  static createRGBA(red, green, blue, alpha) {
477
- return wrapRaw(SolidPattern, c.solidPatternCreateRGBA(red, green, blue, alpha));
545
+ return wrapRaw(SolidPattern, engine().solidPatternCreateRGBA(red, green, blue, alpha));
478
546
  }
479
547
  }
480
548
 
@@ -492,7 +560,7 @@ export class Gradient extends Pattern {
492
560
  export class LinearGradient extends Gradient {
493
561
  constructor(x0, y0, x1, y1) {
494
562
  super();
495
- setHandle(this, c.linearGradientCreate(x0, y0, x1, y1));
563
+ setHandle(this, engine().linearGradientCreate(x0, y0, x1, y1));
496
564
  }
497
565
  }
498
566
 
@@ -500,7 +568,7 @@ export class LinearGradient extends Gradient {
500
568
  export class RadialGradient extends Gradient {
501
569
  constructor(cx0, cy0, radius0, cx1, cy1, radius1) {
502
570
  super();
503
- setHandle(this, c.radialGradientCreate(cx0, cy0, radius0, cx1, cy1, radius1));
571
+ setHandle(this, engine().radialGradientCreate(cx0, cy0, radius0, cx1, cy1, radius1));
504
572
  }
505
573
  }
506
574
 
@@ -508,7 +576,7 @@ export class RadialGradient extends Gradient {
508
576
  export class SurfacePattern extends Pattern {
509
577
  constructor(surface) {
510
578
  super();
511
- setHandle(this, c.surfacePatternCreate(handleOf(surface, 'cairo.Surface')));
579
+ setHandle(this, engine().surfacePatternCreate(handleOf(surface, 'cairo.Surface')));
512
580
  }
513
581
 
514
582
  setExtend(extend) {
@@ -532,6 +600,12 @@ export class SurfacePattern extends Pattern {
532
600
  */
533
601
  export class Path {}
534
602
 
603
+ // Fan a surface handle out to the concrete subclass by cairo runtime type —
604
+ // mirrors CairoSurface::from_c_ptr (only the image surface has a subclass here).
605
+ function wrapSurfaceHandle(handle) {
606
+ return wrapRaw(c.surfaceGetType(handle) === SurfaceType.IMAGE ? ImageSurface : Surface, handle);
607
+ }
608
+
535
609
  // Fan a pattern handle out to the concrete subclass by cairo runtime type —
536
610
  // mirrors gjs_cairo_pattern_from_pattern (MESH/RASTER_SOURCE fall back to the
537
611
  // base Pattern; gjs throws for those, but they are unreachable from this module).
@@ -554,11 +628,15 @@ function wrapPatternHandle(handle) {
554
628
  // a returned/callback cairo pointer into the right JS instance. Surface + Pattern
555
629
  // fan out to the concrete subclass by cairo runtime type (mirrors
556
630
  // CairoSurface::from_c_ptr / gjs_cairo_pattern_from_pattern).
557
- c.setup({
558
- context: (handle) => wrapRaw(Context, handle),
559
- surface: (handle) => wrapRaw(c.surfaceGetType(handle) === SurfaceType.IMAGE ? ImageSurface : Surface, handle),
560
- pattern: (handle) => wrapPatternHandle(handle),
561
- path: (handle) => wrapRaw(Path, handle),
631
+ onNativeLoaded(() => {
632
+ if (native.__cairo === undefined) throw missingBinding();
633
+ c = native.__cairo;
634
+ c.setup({
635
+ context: (handle) => wrapRaw(Context, handle),
636
+ surface: (handle) => wrapSurfaceHandle(handle),
637
+ pattern: (handle) => wrapPatternHandle(handle),
638
+ path: (handle) => wrapRaw(Path, handle),
639
+ });
562
640
  });
563
641
 
564
642
  // The default export mirrors GJS's ESM `cairo` object (enums + constructors), the
package/gettext.js CHANGED
@@ -23,6 +23,7 @@
23
23
  // default export. The gjsify `--app node` build aliases the bare `gettext`
24
24
  // specifier to this module (kept external — `ALIASES_GJS_FOR_NODE`).
25
25
  import * as native from './index.js';
26
+ import { ensureNative } from './index.js';
26
27
  import { requireGi } from './gi.js';
27
28
 
28
29
  // GLib is resolved on first lookup, not at import: loading a namespace walks the
@@ -48,7 +49,10 @@ function GLib() {
48
49
  // lookups still work (they go through GLib, which every addon version has), and
49
50
  // the binders are the no-ops they used to be. It is announced once, because a
50
51
  // silently untranslated UI is the defect this file exists to close.
51
- const haveLocaleBinders = typeof native.localeCategories === 'function';
52
+ function haveLocaleBinders() {
53
+ ensureNative();
54
+ return typeof native.localeCategories === 'function';
55
+ }
52
56
 
53
57
  let announcedStaleAddon = false;
54
58
  function noLocaleBinders(fn) {
@@ -78,6 +82,18 @@ const FALLBACK_CATEGORIES = {
78
82
  win32: { ALL: 0, COLLATE: 1, CTYPE: 2, MONETARY: 3, NUMERIC: 4, TIME: 5, MESSAGES: 1729 },
79
83
  };
80
84
 
85
+ // Resolved on first read, not at import, so a lazy host (NativeScript) that only imports this
86
+ // module has not loaded the addon yet; a failed load throws here, there is no fallback table.
87
+ let categories = null;
88
+ function localeCategories() {
89
+ if (categories === null) {
90
+ categories = haveLocaleBinders()
91
+ ? native.localeCategories()
92
+ : (FALLBACK_CATEGORIES[globalThis.process?.platform] ?? FALLBACK_CATEGORIES.linux);
93
+ }
94
+ return categories;
95
+ }
96
+
81
97
  /** Look up `msgid` in the default domain (the one `textdomain()` last named). */
82
98
  export function gettext(msgid) {
83
99
  return GLib().dgettext(null, msgid);
@@ -145,13 +161,13 @@ export function domain(domainName) {
145
161
  * @returns {string|null} the prior locale name, or null if it could not be set
146
162
  */
147
163
  export function setlocale(category, locale) {
148
- if (!haveLocaleBinders) return noLocaleBinders('setlocale');
164
+ if (!haveLocaleBinders()) return noLocaleBinders('setlocale');
149
165
  return native.setThreadLocale(category, locale ?? null);
150
166
  }
151
167
 
152
168
  /** Set the default text domain, or report it when `domainName` is null. */
153
169
  export function textdomain(domainName) {
154
- if (!haveLocaleBinders) return noLocaleBinders('textdomain');
170
+ if (!haveLocaleBinders()) return noLocaleBinders('textdomain');
155
171
  return native.textdomain(domainName ?? null);
156
172
  }
157
173
 
@@ -162,7 +178,7 @@ export function textdomain(domainName) {
162
178
  * `gjs_bindtextdomain` does — see the note in private.cc.
163
179
  */
164
180
  export function bindtextdomain(domainName, dirName) {
165
- if (!haveLocaleBinders) return noLocaleBinders('bindtextdomain');
181
+ if (!haveLocaleBinders()) return noLocaleBinders('bindtextdomain');
166
182
  return native.bindtextdomain(domainName, dirName ?? null);
167
183
  }
168
184
 
@@ -188,9 +204,15 @@ export function bindtextdomainCodeset(_domainName, _codeset) {
188
204
  * claiming to address LC_MESSAGES — on two of the three platforms the translation
189
205
  * blocker was reported from.
190
206
  */
191
- export const LocaleCategory = haveLocaleBinders
192
- ? native.localeCategories()
193
- : (FALLBACK_CATEGORIES[globalThis.process?.platform] ?? FALLBACK_CATEGORIES.linux);
207
+ export const LocaleCategory = Object.defineProperties(
208
+ {},
209
+ Object.fromEntries(
210
+ Object.keys(FALLBACK_CATEGORIES.linux).map((name) => [
211
+ name,
212
+ { enumerable: true, get: () => localeCategories()[name] },
213
+ ]),
214
+ ),
215
+ );
194
216
 
195
217
  /**
196
218
  * The GJS `Gettext` module as a default export — the object shape