@gjsify/utils 0.53.0 → 0.54.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.
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import e from"gi://GLib?version=2.0";import t from"gi://Gio?version=2.0";import n from"gi://PangoCairo?version=1.0";import r from"gi://Pango?version=1.0";const i=1;function fontMapNamespace(){let e=n.FontMap;if(e!=null&&typeof e.get_default==`function`&&typeof e.new_for_font_type==`function`)return e}function isUnsupportedByFontMap(n){let r=e.Error,i=t.io_error_quark,a=t.IOErrorEnum?.NOT_SUPPORTED;return typeof r!=`function`||typeof i!=`function`||a===void 0||!(n instanceof r)?!1:n.matches(i(),a)}function fontErrorMessage(t){let n=e.Error;return typeof n==`function`&&t instanceof n?t.message??String(t):t instanceof Error?t.message:String(t)}function fontMapFamilies(e){return e.list_families().map(e=>e.get_name())}function fontMapHasFamily(e,t){return e.get_family(t)!==null}function layoutSize(e,t){let n=new r.FontDescription;n.set_family(t),n.set_size(40*r.SCALE);let i=r.Layout.new(e.create_context());return i.set_font_description(n),i.set_text(`Wg`,-1),i.get_pixel_size().join(`x`)}function fontMapServesFamily(e,t,n=layoutSize){return n(e,t)!==n(e,`ZzzGjsifySubstitutionControlQx`)}const a={map:null,before:[],failed:new Map,adopted:!1};function probeFontconfigMap(t,n,r,i=[]){if(r.length===0||e.getenv(`PANGOCAIRO_BACKEND`)!==null)return a;let o=t.new_for_font_type(1);if(o==null)return a;let s=fontMapFamilies(o);if(s.length===0)return a;let c=new Map;for(let e of r)try{o.add_font_file(e)}catch(t){c.set(e,fontErrorMessage(t))}let l=fontMapFamilies(o).filter(e=>!s.includes(e));if(l.length===0)return{map:o,before:s,failed:c,adopted:!1};let alreadyReached=e=>!i.includes(e)&&n.get_family(e)!==null;if(l.every(alreadyReached))return{map:o,before:s,failed:c,adopted:!1};let u=o.set_default;return typeof u==`function`?(u.call(o),{map:o,before:s,failed:c,adopted:!0}):{map:o,before:s,failed:c,adopted:!1}}function noRegistration(){return{available:!1,map:void 0,before:[],registered:[],declined:[],unreachable:[],failed:[],fontconfigFallback:!1}}function registerFontFaces(e,t){let n=fontMapNamespace();if(n===void 0)return noRegistration();let r=t??n.get_default();if(e.length===0)return{...noRegistration(),available:!0,map:r,before:fontMapFamilies(r)};let i=fontMapFamilies(r),a=[],o=[],s=[],c=[];for(let t of e)try{r.add_font_file(t),a.push(t)}catch(e){if(isUnsupportedByFontMap(e)){o.push(t);continue}c.push({path:t,message:fontErrorMessage(e)})}let l=[];a.length>0&&(l=fontMapFamilies(r).filter(e=>!i.includes(e)).filter(e=>!fontMapServesFamily(r,e)),l.length>0&&s.push(...a));let u=[...o,...s];if(u.length>0){let e=probeFontconfigMap(n,r,u,l),t=[];for(let n of o){let r=e.failed.get(n);r===void 0?e.adopted?a.push(n):t.push(n):c.push({path:n,message:r})}if(o.length=0,o.push(...t),e.adopted)s.length=0;else{let t=s.filter(t=>!e.failed.has(t));for(let t of s){let n=e.failed.get(t);n!==void 0&&c.push({path:t,message:n})}s.length=0,s.push(...t)}if(e.adopted&&e.map!==null)return{available:!0,map:e.map,before:e.before,registered:a,declined:o,unreachable:s,failed:c,fontconfigFallback:!0}}return{available:!0,map:r,before:i,registered:a,declined:o,unreachable:s,failed:c,fontconfigFallback:!1}}export{i as CAIRO_FONT_TYPE_FT,fontErrorMessage,fontMapFamilies,fontMapHasFamily,fontMapServesFamily,isUnsupportedByFontMap,registerFontFaces};
@@ -0,0 +1,164 @@
1
+ import Pango from 'gi://Pango?version=1.0';
2
+ /**
3
+ * `CAIRO_FONT_TYPE_FT` from `cairo.h` — the value `pango_cairo_font_map_new_for_font_type()` maps to
4
+ * its fontconfig backend.
5
+ *
6
+ * A literal rather than `gi://cairo`'s `FontType.FT`: the enum is part of cairo's ABI, so importing a
7
+ * fourth namespace to spell one stable integer would add a load-time dependency for nothing.
8
+ */
9
+ export declare const CAIRO_FONT_TYPE_FT = 1;
10
+ /** A face the font map would not take, and why. */
11
+ export interface FontFaceFailure {
12
+ readonly path: string;
13
+ readonly message: string;
14
+ }
15
+ /** What one {@link registerFontFaces} call did. */
16
+ export interface FontFaceRegistration {
17
+ /**
18
+ * `false` where there is no Pango font map to register anything on at all — a `--app browser`
19
+ * bundle empties `gi://` (`plugins/gjs-imports-empty.ts`), and there a browser's OWN font
20
+ * machinery is what resolves a `FontFace`.
21
+ *
22
+ * Reported rather than left to be inferred from an empty outcome, because the two must not be
23
+ * reported alike: nothing was DECLINED on a browser, and a caller warning about a decline there
24
+ * would print a font warning per face in every web app.
25
+ */
26
+ readonly available: boolean;
27
+ /**
28
+ * The map in force afterwards — the adopted fontconfig one where the fallback ran, the `map` that
29
+ * was passed in otherwise. `undefined` when {@link available} is `false`.
30
+ *
31
+ * A caller registering more than one batch MUST thread this forward: after a swap the process
32
+ * default is the fontconfig map, and only this value knows.
33
+ */
34
+ readonly map: Pango.FontMap | undefined;
35
+ /**
36
+ * The family names the registering map held before any face was added — the BEFORE of a family
37
+ * diff. On the fallback path this is the CANDIDATE's list, because the diff has to be taken on
38
+ * the map that ends up in force or every family the two backends merely SPELL differently would
39
+ * be credited to this call.
40
+ */
41
+ readonly before: readonly string[];
42
+ /** Faces now on the map. */
43
+ readonly registered: readonly string[];
44
+ /**
45
+ * Faces no map here would take — see {@link isUnsupportedByFontMap}. A face in a shipped `.app`
46
+ * lands here and is CORRECT: the OS activated the directory before the process started.
47
+ */
48
+ readonly declined: readonly string[];
49
+ /**
50
+ * Faces this call put on the map that no layout can be served by — registered, `get_family`
51
+ * answers the family, and text asking for it still measures the fallback. A SUBSET of
52
+ * {@link registered}, and disjoint from {@link declined}.
53
+ *
54
+ * ITS OWN FIELD rather than a third bucket beside the two above, and the difference is what
55
+ * happened to the map. `declined` is a map that would not TAKE the face, which for a shipped
56
+ * `.app` is the correct outcome; a face here was TAKEN and then could not SERVE it. A caller
57
+ * reading `declined` tells its user that no font map would take this file and points at
58
+ * `ATSApplicationFontsPath` and `initFonts()` — advice that is wrong here, because the face is
59
+ * already on the map and re-running a registration cannot help. That is why the distinction is
60
+ * data rather than a comment: one is silent and correct, the other is loud and necessary, and
61
+ * a caller cannot tell them apart without laying text out itself.
62
+ *
63
+ * EMPTY where the fontconfig fallback took over, which is the whole point of it: then the faces
64
+ * render and there is nothing left to report.
65
+ */
66
+ readonly unreachable: readonly string[];
67
+ /** Faces that failed for any other reason, plus any the fontconfig map could not open either. */
68
+ readonly failed: readonly FontFaceFailure[];
69
+ /**
70
+ * `true` when this call REPLACED the process's default font map with a fontconfig-backed one.
71
+ *
72
+ * A whole-application consequence of one call — every widget created afterwards renders through
73
+ * FreeType instead of the platform rasteriser — so it is reported rather than inferred, and
74
+ * `PANGOCAIRO_BACKEND=coretext` gives the platform map back (ADR 0038 § Amendment 5).
75
+ */
76
+ readonly fontconfigFallback: boolean;
77
+ }
78
+ /**
79
+ * Did the font map decline runtime registration outright?
80
+ *
81
+ * `pango_font_map_add_font_file()` is a vfunc, and the CoreText map implements none — so on macOS the
82
+ * call falls through to Pango's base implementation, which answers `G_IO_ERROR_NOT_SUPPORTED`. That is
83
+ * not a failure to report: a shipped `.app` is already correct declaratively, because
84
+ * `ATSApplicationFontsPath` had the OS activate the staged directory before the process started, and
85
+ * the ordering makes the runtime call the wrong tool there rather than merely a redundant one —
86
+ * `pango_core_text_font_map_changed()` only bumps a serial, there is no
87
+ * `kCTFontManagerRegisteredFontsChangedNotification` observer and no re-scan path in
88
+ * `pangocoretext-fontmap.c`, so a face registered after the map initialises could not be recovered by
89
+ * poking it anyway.
90
+ *
91
+ * KEYED ON THE ERROR rather than on `process.platform`, which is the difference between a capability
92
+ * test and a guess about who is asking. It costs no `gjsify.os` declaration, and it stays right for any
93
+ * other map that declines — which stopped being hypothetical: the backend is whichever is selected
94
+ * rather than one per platform, and `@gjsify/node-gi`'s loader selects `PANGOCAIRO_BACKEND=fc` on the
95
+ * bundled windowing runtime (ADR 0038 § Amendment 3), so a darwin process on it reaches this arm's
96
+ * other side and REGISTERS the faces. A `process.platform` branch would have had to be found and
97
+ * reversed instead.
98
+ */
99
+ export declare function isUnsupportedByFontMap(error: unknown): boolean;
100
+ /**
101
+ * The reported reason as a GError's `message`, never a stringified GError.
102
+ *
103
+ * `GLib.Error` is NOT `instanceof Error` under GJS — measured on gjs 1.88.1, where
104
+ * `GLib.Error.new_literal(…) instanceof Error` is `false`. A plain `instanceof Error` narrowing
105
+ * therefore misses EXACTLY the errors this module sees, and every diagnostic printed over them would
106
+ * silently degrade to `String(error)` — which is not obviously broken, only domain-prefixed.
107
+ */
108
+ export declare function fontErrorMessage(error: unknown): string;
109
+ /**
110
+ * The family names a font map currently holds.
111
+ *
112
+ * Typed `Pango.FontMap` and not `PangoCairo.FontMap`, which is what the call sites have:
113
+ * `pango_cairo_font_map_get_default()` is declared to RETURN the base type.
114
+ */
115
+ export declare function fontMapFamilies(fontMap: Pango.FontMap): string[];
116
+ /** Is `family` one this map can resolve? Pango's own answer, asked directly rather than inferred. */
117
+ export declare function fontMapHasFamily(fontMap: Pango.FontMap, family: string): boolean;
118
+ /**
119
+ * How {@link fontMapServesFamily} takes its reading: a value that is EQUAL for a family the map
120
+ * serves and for one it substitutes.
121
+ *
122
+ * A seam, so the decision can be held from a host that cannot produce the condition — the same
123
+ * discipline {@link isUnsupportedByFontMap} exists for, and the reason `font-map.spec.ts` can assert
124
+ * the decision on every leg instead of only where a poisoned map can be built.
125
+ */
126
+ export type FontFamilyProbe = (fontMap: Pango.FontMap, family: string) => string;
127
+ /**
128
+ * Does this map actually SERVE `family`, or does asking for it get the fallback?
129
+ *
130
+ * A LAYOUT, because that is the only reading that does not lie, and the readings that look cheaper
131
+ * were measured first (pango 1.58.2 / gjs 1.88.1, on the fc map Fedora has, reached here with
132
+ * `PANGOCAIRO_BACKEND=fontconfig`) on a map that had resolved the family to the fallback and then
133
+ * had the face registered on it:
134
+ *
135
+ * `get_family(name)` answers it
136
+ * `list_families()` contains it
137
+ * `load_font(desc).describe()` the requested family — from a FRESH context
138
+ * `get_serial()` 1, exactly as on an unpoisoned map
139
+ * `Pango.Layout` pixel size THE FALLBACK — 86x66, identical to an invented family
140
+ *
141
+ * So the map holds the face, hands the right font to a caller that asks for it by name, reports the
142
+ * same serial as a healthy map, and lays every glyph out in Verdana: a per-map-instance cache of the
143
+ * negative resolution, which `add_font_file` does not invalidate and which nothing in JS reaches.
144
+ * Warming a context with `load_font` first does not help (measured — still 86x66), and neither does a
145
+ * context created after the registration; only a map that never saw the question serves it, which is
146
+ * the fresh map {@link probeFontconfigMap} builds.
147
+ *
148
+ * Which is also why the equality is against a control family rather than against a fixed number: the
149
+ * fallback's metrics are the host's business (Verdana 86x66 here, 87x63 on the Fedora 44 leg), and a
150
+ * number pinned to one host's default sans would red everywhere else while proving nothing extra.
151
+ */
152
+ export declare function fontMapServesFamily(fontMap: Pango.FontMap, family: string, probe?: FontFamilyProbe): boolean;
153
+ /**
154
+ * Register faces on a font map, falling back to a fontconfig-backed one when that map DECLINES.
155
+ *
156
+ * `map` defaults to the process default and exists so a caller can keep a batch on the map a previous
157
+ * batch adopted — see {@link FontFaceRegistration.map}.
158
+ *
159
+ * Total: a face that will not open, or that no map here will take, is reported and never thrown.
160
+ * Taking an application down over a decorative face would be worse than rendering it in a fallback —
161
+ * but doing so SILENTLY is the defect this exists against (ADR 0038), so both are reported and the
162
+ * caller decides what to say about them.
163
+ */
164
+ export declare function registerFontFaces(faces: readonly string[], map?: Pango.FontMap): FontFaceRegistration;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/utils",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "Utils module for gjsify",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/types/index.d.ts",
@@ -18,6 +18,10 @@
18
18
  "types": "./lib/types/core.d.ts",
19
19
  "default": "./lib/esm/core.js"
20
20
  },
21
+ "./font-map": {
22
+ "types": "./lib/types/font-map.d.ts",
23
+ "default": "./lib/esm/font-map.js"
24
+ },
21
25
  "./main-loop": {
22
26
  "types": "./lib/types/main-loop.d.ts",
23
27
  "default": "./lib/esm/main-loop.js"
@@ -32,6 +36,7 @@
32
36
  "check": "gjsify tsc --noEmit",
33
37
  "build": "gjsify run build:gjsify && gjsify run build:types",
34
38
  "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
39
+ "build:esm": "gjsify tsc -p tsconfig.build.json",
35
40
  "build:types": "gjsify tsc",
36
41
  "build:test": "gjsify run build:test:gjs",
37
42
  "build:test:gjs": "gjsify build src/test.ts --app gjs --outfile test.gjs.js",
@@ -44,14 +49,16 @@
44
49
  "fs"
45
50
  ],
46
51
  "devDependencies": {
47
- "@gjsify/cli": "^0.53.0",
52
+ "@gjsify/cli": "^0.54.0",
48
53
  "typescript": "^6.0.3"
49
54
  },
50
55
  "dependencies": {
51
56
  "@girs/gio-2.0": "5.4.0",
52
57
  "@girs/giounix-2.0": "5.4.0",
53
58
  "@girs/gjs": "5.4.0",
54
- "@girs/glib-2.0": "5.4.0"
59
+ "@girs/glib-2.0": "5.4.0",
60
+ "@girs/pango-1.0": "5.4.0",
61
+ "@girs/pangocairo-1.0": "5.4.0"
55
62
  },
56
63
  "license": "MIT",
57
64
  "repository": {
@@ -78,6 +85,12 @@
78
85
  "browser": "polyfill",
79
86
  "nativescript": "polyfill"
80
87
  },
88
+ "./font-map": {
89
+ "gjs": "polyfill",
90
+ "node": "polyfill",
91
+ "browser": "polyfill",
92
+ "nativescript": "polyfill"
93
+ },
81
94
  "./main-loop": {
82
95
  "gjs": "polyfill",
83
96
  "node": "polyfill",