@gjsify/utils 0.53.0 → 0.55.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/font-map.js +1 -0
- package/lib/types/font-map.d.ts +164 -0
- package/package.json +16 -3
|
@@ -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.
|
|
3
|
+
"version": "0.55.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.
|
|
52
|
+
"@gjsify/cli": "^0.55.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",
|