@react-x11/components 0.3.0 → 0.5.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/README.md +131 -4
- package/dist/charts/components.d.ts +3 -1
- package/dist/charts/components.d.ts.map +1 -1
- package/dist/charts/components.js +8 -4
- package/dist/charts/components.js.map +1 -1
- package/dist/charts/node.d.ts +13 -6
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +75 -24
- package/dist/charts/node.js.map +1 -1
- package/dist/charts/render.d.ts +4 -0
- package/dist/charts/render.d.ts.map +1 -1
- package/dist/charts/render.js +11 -6
- package/dist/charts/render.js.map +1 -1
- package/dist/code-editor/node.d.ts +47 -16
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +121 -51
- package/dist/code-editor/node.js.map +1 -1
- package/dist/flow/draw.d.ts +19 -1
- package/dist/flow/draw.d.ts.map +1 -1
- package/dist/flow/draw.js +64 -35
- package/dist/flow/draw.js.map +1 -1
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +24 -8
- package/dist/flow/index.js.map +1 -1
- package/dist/flow/model.d.ts +4 -2
- package/dist/flow/model.d.ts.map +1 -1
- package/dist/flow/model.js +4 -2
- package/dist/flow/model.js.map +1 -1
- package/dist/flow/node.d.ts +40 -9
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +142 -53
- package/dist/flow/node.js.map +1 -1
- package/dist/flow/types.d.ts +46 -21
- package/dist/flow/types.d.ts.map +1 -1
- package/dist/formula/index.d.ts +4 -2
- package/dist/formula/index.d.ts.map +1 -1
- package/dist/formula/index.js.map +1 -1
- package/dist/formula/node.d.ts +5 -1
- package/dist/formula/node.d.ts.map +1 -1
- package/dist/formula/node.js +19 -3
- package/dist/formula/node.js.map +1 -1
- package/dist/html/controls.d.ts +4 -1
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +11 -5
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +18 -8
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/parse.d.ts +15 -10
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +41 -16
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +3 -0
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +3 -1
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/values.d.ts +14 -1
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +11 -8
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +1 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +12 -5
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +7 -4
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +22 -1
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts +22 -8
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +97 -15
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +3 -0
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +2 -2
- package/dist/html/paint.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/maps/clip.d.ts +43 -0
- package/dist/maps/clip.d.ts.map +1 -0
- package/dist/maps/clip.js +171 -0
- package/dist/maps/clip.js.map +1 -0
- package/dist/maps/gzip.d.ts +15 -0
- package/dist/maps/gzip.d.ts.map +1 -0
- package/dist/maps/gzip.js +56 -0
- package/dist/maps/gzip.js.map +1 -0
- package/dist/maps/index.d.ts +55 -0
- package/dist/maps/index.d.ts.map +1 -0
- package/dist/maps/index.js +141 -0
- package/dist/maps/index.js.map +1 -0
- package/dist/maps/labels.d.ts +123 -0
- package/dist/maps/labels.d.ts.map +1 -0
- package/dist/maps/labels.js +323 -0
- package/dist/maps/labels.js.map +1 -0
- package/dist/maps/mvt.d.ts +244 -0
- package/dist/maps/mvt.d.ts.map +1 -0
- package/dist/maps/mvt.js +820 -0
- package/dist/maps/mvt.js.map +1 -0
- package/dist/maps/node.d.ts +278 -0
- package/dist/maps/node.d.ts.map +1 -0
- package/dist/maps/node.js +1459 -0
- package/dist/maps/node.js.map +1 -0
- package/dist/maps/overlay.d.ts +157 -0
- package/dist/maps/overlay.d.ts.map +1 -0
- package/dist/maps/overlay.js +472 -0
- package/dist/maps/overlay.js.map +1 -0
- package/dist/maps/paint.d.ts +226 -0
- package/dist/maps/paint.d.ts.map +1 -0
- package/dist/maps/paint.js +705 -0
- package/dist/maps/paint.js.map +1 -0
- package/dist/maps/proj.d.ts +281 -0
- package/dist/maps/proj.d.ts.map +1 -0
- package/dist/maps/proj.js +451 -0
- package/dist/maps/proj.js.map +1 -0
- package/dist/maps/sources.d.ts +256 -0
- package/dist/maps/sources.d.ts.map +1 -0
- package/dist/maps/sources.js +210 -0
- package/dist/maps/sources.js.map +1 -0
- package/dist/maps/style.d.ts +141 -0
- package/dist/maps/style.d.ts.map +1 -0
- package/dist/maps/style.js +147 -0
- package/dist/maps/style.js.map +1 -0
- package/dist/maps/styles.d.ts +82 -0
- package/dist/maps/styles.d.ts.map +1 -0
- package/dist/maps/styles.js +898 -0
- package/dist/maps/styles.js.map +1 -0
- package/dist/maps/tiles.d.ts +283 -0
- package/dist/maps/tiles.d.ts.map +1 -0
- package/dist/maps/tiles.js +469 -0
- package/dist/maps/tiles.js.map +1 -0
- package/dist/maps/types.d.ts +244 -0
- package/dist/maps/types.d.ts.map +1 -0
- package/dist/maps/types.js +2 -0
- package/dist/maps/types.js.map +1 -0
- package/dist/qml/containers.d.ts +3 -0
- package/dist/qml/containers.d.ts.map +1 -0
- package/dist/qml/containers.js +331 -0
- package/dist/qml/containers.js.map +1 -0
- package/dist/qml/controls.d.ts +12 -0
- package/dist/qml/controls.d.ts.map +1 -0
- package/dist/qml/controls.js +21 -0
- package/dist/qml/controls.js.map +1 -0
- package/dist/qml/globals.d.ts +6 -0
- package/dist/qml/globals.d.ts.map +1 -0
- package/dist/qml/globals.js +25 -0
- package/dist/qml/globals.js.map +1 -0
- package/dist/qml/index.d.ts +15 -0
- package/dist/qml/index.d.ts.map +1 -0
- package/dist/qml/index.js +25 -0
- package/dist/qml/index.js.map +1 -0
- package/dist/qml/interaction.d.ts +12 -0
- package/dist/qml/interaction.d.ts.map +1 -0
- package/dist/qml/interaction.js +246 -0
- package/dist/qml/interaction.js.map +1 -0
- package/dist/qml/ir.d.ts +106 -0
- package/dist/qml/ir.d.ts.map +1 -0
- package/dist/qml/ir.js +7 -0
- package/dist/qml/ir.js.map +1 -0
- package/dist/qml/layouts.d.ts +3 -0
- package/dist/qml/layouts.d.ts.map +1 -0
- package/dist/qml/layouts.js +122 -0
- package/dist/qml/layouts.js.map +1 -0
- package/dist/qml/models.d.ts +14 -0
- package/dist/qml/models.d.ts.map +1 -0
- package/dist/qml/models.js +80 -0
- package/dist/qml/models.js.map +1 -0
- package/dist/qml/objects.d.ts +228 -0
- package/dist/qml/objects.d.ts.map +1 -0
- package/dist/qml/objects.js +1103 -0
- package/dist/qml/objects.js.map +1 -0
- package/dist/qml/parse.d.ts +10 -0
- package/dist/qml/parse.d.ts.map +1 -0
- package/dist/qml/parse.js +607 -0
- package/dist/qml/parse.js.map +1 -0
- package/dist/qml/qtquick.d.ts +7 -0
- package/dist/qml/qtquick.d.ts.map +1 -0
- package/dist/qml/qtquick.js +347 -0
- package/dist/qml/qtquick.js.map +1 -0
- package/dist/qml/react.d.ts +83 -0
- package/dist/qml/react.d.ts.map +1 -0
- package/dist/qml/react.js +323 -0
- package/dist/qml/react.js.map +1 -0
- package/dist/qml/resolver.d.ts +38 -0
- package/dist/qml/resolver.d.ts.map +1 -0
- package/dist/qml/resolver.js +64 -0
- package/dist/qml/resolver.js.map +1 -0
- package/dist/qml/slots.d.ts +65 -0
- package/dist/qml/slots.d.ts.map +1 -0
- package/dist/qml/slots.js +230 -0
- package/dist/qml/slots.js.map +1 -0
- package/dist/qml/states.d.ts +5 -0
- package/dist/qml/states.d.ts.map +1 -0
- package/dist/qml/states.js +276 -0
- package/dist/qml/states.js.map +1 -0
- package/dist/qml/view-utils.d.ts +42 -0
- package/dist/qml/view-utils.d.ts.map +1 -0
- package/dist/qml/view-utils.js +121 -0
- package/dist/qml/view-utils.js.map +1 -0
- package/dist/reorder/hx.d.ts +18 -0
- package/dist/reorder/hx.d.ts.map +1 -0
- package/dist/reorder/hx.js +10 -0
- package/dist/reorder/hx.js.map +1 -0
- package/dist/reorder/index.d.ts +314 -0
- package/dist/reorder/index.d.ts.map +1 -0
- package/dist/reorder/index.js +1539 -0
- package/dist/reorder/index.js.map +1 -0
- package/dist/reorder/model.d.ts +107 -0
- package/dist/reorder/model.d.ts.map +1 -0
- package/dist/reorder/model.js +162 -0
- package/dist/reorder/model.js.map +1 -0
- package/dist/richtext/node.d.ts +15 -5
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +40 -11
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +35 -13
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +83 -27
- package/dist/richtext/runs.js.map +1 -1
- package/dist/tabs/hx.d.ts +18 -0
- package/dist/tabs/hx.d.ts.map +1 -0
- package/dist/tabs/hx.js +10 -0
- package/dist/tabs/hx.js.map +1 -0
- package/dist/tabs/index.d.ts +160 -0
- package/dist/tabs/index.d.ts.map +1 -0
- package/dist/tabs/index.js +1265 -0
- package/dist/tabs/index.js.map +1 -0
- package/dist/terminal/vt/fonts.d.ts +45 -15
- package/dist/terminal/vt/fonts.d.ts.map +1 -1
- package/dist/terminal/vt/fonts.js +89 -19
- package/dist/terminal/vt/fonts.js.map +1 -1
- package/dist/terminal/vt/index.d.ts +1 -1
- package/dist/terminal/vt/index.d.ts.map +1 -1
- package/dist/terminal/vt/index.js +3 -3
- package/dist/terminal/vt/index.js.map +1 -1
- package/dist/terminal/vt/node.d.ts +42 -0
- package/dist/terminal/vt/node.d.ts.map +1 -1
- package/dist/terminal/vt/node.js +95 -11
- package/dist/terminal/vt/node.js.map +1 -1
- package/dist/terminal/vt/pty.d.ts +16 -0
- package/dist/terminal/vt/pty.d.ts.map +1 -1
- package/dist/terminal/vt/pty.js +186 -0
- package/dist/terminal/vt/pty.js.map +1 -1
- package/dist/terminal/vt/renderer.d.ts.map +1 -1
- package/dist/terminal/vt/renderer.js +13 -3
- package/dist/terminal/vt/renderer.js.map +1 -1
- package/package.json +23 -3
- package/src/charts/components.ts +14 -7
- package/src/charts/node.ts +81 -26
- package/src/charts/render.ts +16 -6
- package/src/code-editor/node.ts +147 -53
- package/src/flow/draw.ts +104 -35
- package/src/flow/index.ts +33 -13
- package/src/flow/model.ts +4 -2
- package/src/flow/node.ts +158 -59
- package/src/flow/types.ts +46 -21
- package/src/formula/index.ts +4 -2
- package/src/formula/node.ts +24 -5
- package/src/html/controls.ts +4 -1
- package/src/html/css/cascade.ts +18 -7
- package/src/html/css/parse.ts +46 -21
- package/src/html/css/style.ts +6 -1
- package/src/html/css/values.ts +25 -9
- package/src/html/index.ts +1 -0
- package/src/html/layout/boxes.ts +19 -9
- package/src/html/layout/inline.ts +23 -1
- package/src/html/node.ts +110 -17
- package/src/html/paint.ts +17 -2
- package/src/index.ts +142 -0
- package/src/maps/clip.ts +184 -0
- package/src/maps/gzip.ts +66 -0
- package/src/maps/index.ts +316 -0
- package/src/maps/labels.ts +442 -0
- package/src/maps/mvt.ts +857 -0
- package/src/maps/node.ts +1819 -0
- package/src/maps/overlay.ts +639 -0
- package/src/maps/paint.ts +978 -0
- package/src/maps/proj.ts +641 -0
- package/src/maps/sources.ts +425 -0
- package/src/maps/style.ts +309 -0
- package/src/maps/styles.ts +1040 -0
- package/src/maps/tiles.ts +676 -0
- package/src/maps/types.ts +260 -0
- package/src/qml/containers.tsx +380 -0
- package/src/qml/controls.tsx +47 -0
- package/src/qml/globals.ts +33 -0
- package/src/qml/index.ts +67 -0
- package/src/qml/interaction.tsx +317 -0
- package/src/qml/ir.ts +80 -0
- package/src/qml/layouts.tsx +141 -0
- package/src/qml/models.ts +101 -0
- package/src/qml/objects.ts +1431 -0
- package/src/qml/parse.ts +624 -0
- package/src/qml/qtquick.tsx +413 -0
- package/src/qml/react.tsx +454 -0
- package/src/qml/resolver.ts +99 -0
- package/src/qml/slots.ts +249 -0
- package/src/qml/states.ts +318 -0
- package/src/qml/view-utils.ts +140 -0
- package/src/reorder/hx.ts +31 -0
- package/src/reorder/index.ts +2223 -0
- package/src/reorder/model.ts +232 -0
- package/src/richtext/node.ts +49 -14
- package/src/richtext/runs.ts +115 -30
- package/src/tabs/hx.ts +31 -0
- package/src/tabs/index.ts +1686 -0
- package/src/terminal/vt/fonts.ts +128 -36
- package/src/terminal/vt/index.ts +4 -2
- package/src/terminal/vt/node.ts +105 -12
- package/src/terminal/vt/pty.ts +243 -0
- package/src/terminal/vt/renderer.ts +13 -3
package/src/terminal/vt/fonts.ts
CHANGED
|
@@ -11,17 +11,36 @@
|
|
|
11
11
|
// combining marks, a text-presentation emoji with a variation selector — and
|
|
12
12
|
// `Font.shape()` handles those one cluster at a time, cached, on a path a
|
|
13
13
|
// normal screen never touches.
|
|
14
|
+
//
|
|
15
|
+
// Not every backend's text engine has those seams. react-x11's Cocoa backend
|
|
16
|
+
// up to 2.3.x answered `fonts.match()` with a face that has `metrics` and
|
|
17
|
+
// `hasGlyph` and nothing else (it grew the rest in 2.4.0,
|
|
18
|
+
// sidorares/react-x11#432), so everything here feature-detects
|
|
19
|
+
// (`hasGlyphRuns`), and a `FontSet` over such an engine is a valid object
|
|
20
|
+
// that builds no runs rather than a `TypeError` thrown from inside paint.
|
|
14
21
|
|
|
15
|
-
/**
|
|
16
|
-
export interface
|
|
17
|
-
metrics(size: number):
|
|
18
|
-
ascent: number;
|
|
19
|
-
descent: number;
|
|
20
|
-
lineGap: number;
|
|
21
|
-
lineHeight: number;
|
|
22
|
-
};
|
|
23
|
-
advanceOf(glyphId: number, size: number): number;
|
|
22
|
+
/** What every backend's face answers: metrics and coverage. */
|
|
23
|
+
export interface EngineFont {
|
|
24
|
+
metrics(size: number): FontMetrics;
|
|
24
25
|
hasGlyph(codepoint: number): boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `metrics(size)` as the two engines report it. ntk gives `lineGap` and
|
|
30
|
+
* `lineHeight`; CoreText's name for the gap is `leading`, and it reports no
|
|
31
|
+
* line height at all — `lineHeightOf` reads whichever is there.
|
|
32
|
+
*/
|
|
33
|
+
export interface FontMetrics {
|
|
34
|
+
ascent: number;
|
|
35
|
+
descent: number;
|
|
36
|
+
lineGap?: number;
|
|
37
|
+
lineHeight?: number;
|
|
38
|
+
leading?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** ntk's `Font`: the glyph-run seams on top of the metrics (ntk#254). */
|
|
42
|
+
export interface NtkFont extends EngineFont {
|
|
43
|
+
advanceOf(glyphId: number, size: number): number;
|
|
25
44
|
/** ntk#254 — the lookup twin of `hasGlyph`, `null` when uncovered. */
|
|
26
45
|
glyphIdFor(codepoint: number): number | null;
|
|
27
46
|
shape(
|
|
@@ -31,17 +50,36 @@ export interface NtkFont {
|
|
|
31
50
|
): { glyphs: { id: number; ax: number; dx: number; dy: number }[] };
|
|
32
51
|
}
|
|
33
52
|
|
|
34
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* ntk's font manager, off `app.fonts` — or another engine's, which may
|
|
55
|
+
* answer plainer faces and have no `fallbackFor` at all.
|
|
56
|
+
*/
|
|
35
57
|
export interface NtkFonts {
|
|
36
58
|
match(
|
|
37
59
|
family: string,
|
|
38
60
|
opts?: { weight?: number | string; style?: string },
|
|
39
|
-
):
|
|
40
|
-
fallbackFor(
|
|
61
|
+
): EngineFont;
|
|
62
|
+
fallbackFor?(
|
|
41
63
|
codepoint: number,
|
|
42
64
|
family?: string,
|
|
43
65
|
opts?: { weight?: number | string; style?: string },
|
|
44
|
-
):
|
|
66
|
+
): EngineFont | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether a face can build glyph runs: `glyphIdFor` to find the glyph and
|
|
71
|
+
* `advanceOf` to measure it. A face without them still answers metrics and
|
|
72
|
+
* coverage, which is all react-x11's Cocoa backend offered before 2.4.0.
|
|
73
|
+
*/
|
|
74
|
+
export function hasGlyphRuns(
|
|
75
|
+
font: EngineFont | null | undefined,
|
|
76
|
+
): font is NtkFont {
|
|
77
|
+
const f = font as Partial<NtkFont> | null | undefined;
|
|
78
|
+
return (
|
|
79
|
+
!!f &&
|
|
80
|
+
typeof f.glyphIdFor === 'function' &&
|
|
81
|
+
typeof f.advanceOf === 'function'
|
|
82
|
+
);
|
|
45
83
|
}
|
|
46
84
|
|
|
47
85
|
/** One glyph run, in the shape `ctx.drawGlyphs` documents. */
|
|
@@ -77,9 +115,20 @@ export class FontSet {
|
|
|
77
115
|
readonly family: string;
|
|
78
116
|
readonly size: number;
|
|
79
117
|
readonly metrics: CellMetrics;
|
|
118
|
+
/**
|
|
119
|
+
* Whether this engine's faces can build glyph runs at all.
|
|
120
|
+
*
|
|
121
|
+
* `false` when `match()` answered a face with metrics and coverage but
|
|
122
|
+
* none of the glyph-run seams — react-x11's Cocoa backend before 2.4.0
|
|
123
|
+
* (sidorares/react-x11#432). `run()` is then always null, and the node
|
|
124
|
+
* paints nothing rather than half a terminal. A box with no fonts at all
|
|
125
|
+
* is a different state and stays `true`: the engine is ntk's, it just
|
|
126
|
+
* matched nothing, and backgrounds still draw.
|
|
127
|
+
*/
|
|
128
|
+
readonly glyphRuns: boolean;
|
|
80
129
|
private _fonts: NtkFonts;
|
|
81
|
-
private _faces = new Map<Variant,
|
|
82
|
-
/** `variant
|
|
130
|
+
private _faces = new Map<Variant, EngineFont | null>();
|
|
131
|
+
/** `variant:chars` → the run to stamp, or null for "nothing to draw". */
|
|
83
132
|
private _glyphs = new Map<string, GlyphRun | null>();
|
|
84
133
|
|
|
85
134
|
constructor(fonts: NtkFonts, family: string, size: number) {
|
|
@@ -88,17 +137,18 @@ export class FontSet {
|
|
|
88
137
|
this.size = size;
|
|
89
138
|
|
|
90
139
|
const regular = this.face('regular');
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
140
|
+
this.glyphRuns = regular === null || hasGlyphRuns(regular);
|
|
141
|
+
const m = safeMetrics(regular, size);
|
|
142
|
+
const ascent = finite(m?.ascent, size * 0.8);
|
|
143
|
+
const descent = finite(m?.descent, size * 0.2);
|
|
144
|
+
const lineHeight = m ? lineHeightOf(m, ascent, descent) : size * 1.2;
|
|
95
145
|
// The advance of a digit is the cell width: in a monospace face every
|
|
96
146
|
// glyph agrees, and in a face that is not monospace the digit is still
|
|
97
147
|
// the least surprising answer.
|
|
98
148
|
let advance = size * 0.6;
|
|
99
|
-
if (regular) {
|
|
100
|
-
const gid = regular
|
|
101
|
-
if (gid !== null) advance = regular
|
|
149
|
+
if (hasGlyphRuns(regular)) {
|
|
150
|
+
const gid = safeGlyphId(regular, 0x30);
|
|
151
|
+
if (gid !== null) advance = safeAdvance(regular, gid, size) || advance;
|
|
102
152
|
}
|
|
103
153
|
const cellWidth = Math.max(1, Math.round(advance));
|
|
104
154
|
const cellHeight = Math.max(1, Math.ceil(lineHeight));
|
|
@@ -123,18 +173,19 @@ export class FontSet {
|
|
|
123
173
|
* regular one — ntk does not synthesise either, and a fake oblique is worse
|
|
124
174
|
* than an honest upright (overstrike emulation is a later, separate call).
|
|
125
175
|
*/
|
|
126
|
-
face(variant: Variant):
|
|
176
|
+
face(variant: Variant): EngineFont | null {
|
|
127
177
|
const cached = this._faces.get(variant);
|
|
128
178
|
if (cached !== undefined) return cached;
|
|
129
|
-
let font:
|
|
179
|
+
let font: EngineFont | null = null;
|
|
130
180
|
try {
|
|
131
|
-
font =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
181
|
+
font =
|
|
182
|
+
this._fonts.match(this.family, {
|
|
183
|
+
weight: variant === 'bold' || variant === 'boldItalic' ? 700 : 400,
|
|
184
|
+
style:
|
|
185
|
+
variant === 'italic' || variant === 'boldItalic'
|
|
186
|
+
? 'italic'
|
|
187
|
+
: 'normal',
|
|
188
|
+
}) ?? null;
|
|
138
189
|
} catch {
|
|
139
190
|
// no fontconfig, no fonts on the box: the caller draws backgrounds and
|
|
140
191
|
// no glyphs rather than failing to paint at all
|
|
@@ -152,8 +203,10 @@ export class FontSet {
|
|
|
152
203
|
* lookups rather than cmap searches.
|
|
153
204
|
*/
|
|
154
205
|
run(chars: string, variant: Variant): GlyphRun | null {
|
|
155
|
-
if (!chars || chars === ' ') return null;
|
|
156
|
-
|
|
206
|
+
if (!this.glyphRuns || !chars || chars === ' ') return null;
|
|
207
|
+
// The variant never contains a colon, so the key cannot be ambiguous
|
|
208
|
+
// whatever the cell holds.
|
|
209
|
+
const key = `${variant}:${chars}`;
|
|
157
210
|
const hit = this._glyphs.get(key);
|
|
158
211
|
if (hit !== undefined) return hit;
|
|
159
212
|
const run = this._build(chars, variant);
|
|
@@ -163,7 +216,7 @@ export class FontSet {
|
|
|
163
216
|
|
|
164
217
|
private _build(chars: string, variant: Variant): GlyphRun | null {
|
|
165
218
|
const font = this.face(variant);
|
|
166
|
-
if (!font) return null;
|
|
219
|
+
if (!hasGlyphRuns(font)) return null;
|
|
167
220
|
const first = chars.codePointAt(0);
|
|
168
221
|
if (first === undefined) return null;
|
|
169
222
|
const single = String.fromCodePoint(first).length === chars.length;
|
|
@@ -192,6 +245,7 @@ export class FontSet {
|
|
|
192
245
|
// A cluster: shape it once and keep the result. Rare enough that the
|
|
193
246
|
// shaping cost never reaches a frame budget, and correct where a
|
|
194
247
|
// codepoint-at-a-time walk would stack the marks on top of each other.
|
|
248
|
+
if (typeof (font as Partial<NtkFont>).shape !== 'function') return null;
|
|
195
249
|
try {
|
|
196
250
|
const shaped = font.shape(chars, this.size);
|
|
197
251
|
if (!shaped.glyphs.length) return null;
|
|
@@ -202,14 +256,17 @@ export class FontSet {
|
|
|
202
256
|
}
|
|
203
257
|
|
|
204
258
|
private _fallback(codepoint: number, variant: Variant): NtkFont | null {
|
|
259
|
+
const fonts = this._fonts;
|
|
260
|
+
if (typeof fonts.fallbackFor !== 'function') return null;
|
|
205
261
|
try {
|
|
206
|
-
|
|
262
|
+
const face = fonts.fallbackFor(codepoint, this.family, {
|
|
207
263
|
weight: variant === 'bold' || variant === 'boldItalic' ? 700 : 400,
|
|
208
264
|
style:
|
|
209
265
|
variant === 'italic' || variant === 'boldItalic'
|
|
210
266
|
? 'italic'
|
|
211
267
|
: 'normal',
|
|
212
268
|
});
|
|
269
|
+
return hasGlyphRuns(face) ? face : null;
|
|
213
270
|
} catch {
|
|
214
271
|
return null;
|
|
215
272
|
}
|
|
@@ -218,12 +275,47 @@ export class FontSet {
|
|
|
218
275
|
|
|
219
276
|
function safeGlyphId(font: NtkFont, codepoint: number): number | null {
|
|
220
277
|
try {
|
|
221
|
-
|
|
278
|
+
const gid = font.glyphIdFor(codepoint);
|
|
279
|
+
return typeof gid === 'number' && Number.isInteger(gid) ? gid : null;
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function safeAdvance(font: NtkFont, glyphId: number, size: number): number {
|
|
286
|
+
try {
|
|
287
|
+
return finite(font.advanceOf(glyphId, size), 0);
|
|
288
|
+
} catch {
|
|
289
|
+
return 0;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function safeMetrics(
|
|
294
|
+
font: EngineFont | null,
|
|
295
|
+
size: number,
|
|
296
|
+
): FontMetrics | null {
|
|
297
|
+
if (!font || typeof font.metrics !== 'function') return null;
|
|
298
|
+
try {
|
|
299
|
+
const m = font.metrics(size);
|
|
300
|
+
return m && typeof m === 'object' ? m : null;
|
|
222
301
|
} catch {
|
|
223
302
|
return null;
|
|
224
303
|
}
|
|
225
304
|
}
|
|
226
305
|
|
|
306
|
+
/**
|
|
307
|
+
* ntk's `lineHeight` when it is there; otherwise ascent + descent + gap, with
|
|
308
|
+
* the gap under either engine's name.
|
|
309
|
+
*/
|
|
310
|
+
function lineHeightOf(m: FontMetrics, ascent: number, descent: number): number {
|
|
311
|
+
const gap = finite(m.lineGap, finite(m.leading, 0));
|
|
312
|
+
return finite(m.lineHeight, ascent + descent + gap);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function finite(n: number | undefined, fallback: number): number {
|
|
316
|
+
return typeof n === 'number' && Number.isFinite(n) ? n : fallback;
|
|
317
|
+
}
|
|
318
|
+
|
|
227
319
|
/** `VARIANT_*` bits from `./colors.ts` to one of the four faces. */
|
|
228
320
|
export function variantOf(bold: boolean, italic: boolean): Variant {
|
|
229
321
|
if (bold && italic) return 'boldItalic';
|
package/src/terminal/vt/index.ts
CHANGED
|
@@ -33,7 +33,7 @@ import type { EmbedStatus, ExitInfo } from '../../embed/index.js';
|
|
|
33
33
|
import type { TerminalColors } from '../backends.js';
|
|
34
34
|
import { ELEMENT, VtTermNode } from './node.js';
|
|
35
35
|
import type { VtTermProps } from './node.js';
|
|
36
|
-
import { PtyUnavailableError,
|
|
36
|
+
import { PtyUnavailableError, defaultPtyHost } from './pty.js';
|
|
37
37
|
import type { PtyHost, PtySession } from './pty.js';
|
|
38
38
|
import { startTimeout, stopTimeout } from '../../embed/timers.js';
|
|
39
39
|
import type { TimerId } from '../../embed/timers.js';
|
|
@@ -45,6 +45,8 @@ export type { VtTermProps };
|
|
|
45
45
|
export {
|
|
46
46
|
PTY_MODULES,
|
|
47
47
|
PtyUnavailableError,
|
|
48
|
+
bunPtyHost,
|
|
49
|
+
defaultPtyHost,
|
|
48
50
|
defaultShell,
|
|
49
51
|
nodePtyHost,
|
|
50
52
|
ptyLoadError,
|
|
@@ -203,7 +205,7 @@ export function VtTerminal(props: VtTerminalProps): ReactElement {
|
|
|
203
205
|
);
|
|
204
206
|
return;
|
|
205
207
|
}
|
|
206
|
-
const host = handlers.current.pty ??
|
|
208
|
+
const host = handlers.current.pty ?? defaultPtyHost();
|
|
207
209
|
if (!(await host.available())) {
|
|
208
210
|
// The message separates "nothing installed" from "installed but it
|
|
209
211
|
// would not load" — a native module built for another Node ABI looks
|
package/src/terminal/vt/node.ts
CHANGED
|
@@ -68,6 +68,31 @@ const PREFERRED_COLS = 80;
|
|
|
68
68
|
const PREFERRED_ROWS = 24;
|
|
69
69
|
/** Wheel notches to lines, the way every terminal counts them. */
|
|
70
70
|
const WHEEL_LINES = 3;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Said once per process: the engine behind `app.fonts` has no glyph-run
|
|
74
|
+
* seams, so this element paints nothing. `process` and `console` come off
|
|
75
|
+
* `globalThis` because `src/` compiles with `types: []` — see `warnVtOnly`
|
|
76
|
+
* in `../index.ts`.
|
|
77
|
+
*/
|
|
78
|
+
let warnedNoGlyphRuns = false;
|
|
79
|
+
function warnNoGlyphRuns(): void {
|
|
80
|
+
if (warnedNoGlyphRuns) return;
|
|
81
|
+
warnedNoGlyphRuns = true;
|
|
82
|
+
const g = globalThis as {
|
|
83
|
+
process?: { env?: Record<string, string | undefined> };
|
|
84
|
+
console?: { warn(message: string): void };
|
|
85
|
+
};
|
|
86
|
+
if (g.process?.env?.NODE_ENV === 'production') return;
|
|
87
|
+
g.console?.warn(
|
|
88
|
+
'@react-x11/components: <Terminal backend="vt"> — the text engine behind ' +
|
|
89
|
+
'app.fonts has no glyph-run seams (glyphIdFor/advanceOf on a face, ' +
|
|
90
|
+
'drawGlyphs on the context), so the terminal paints nothing. ' +
|
|
91
|
+
"react-x11's Cocoa backend before 2.4.0 was the known case " +
|
|
92
|
+
'(sidorares/react-x11#432); every backend of react-x11 2.5.0 and ' +
|
|
93
|
+
'later has them, so check which engine app.fonts is.',
|
|
94
|
+
);
|
|
95
|
+
}
|
|
71
96
|
/** OSC 52 payloads bigger than this are a program misbehaving, not a copy. */
|
|
72
97
|
const MAX_OSC52 = 1_000_000;
|
|
73
98
|
/** What counts as part of a word for double-click selection. */
|
|
@@ -283,6 +308,42 @@ export class VtTermNode extends Node {
|
|
|
283
308
|
|
|
284
309
|
// --- metrics and layout --------------------------------------------------
|
|
285
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Device pixels per logical pixel — the display scale (core's
|
|
313
|
+
* `docs/scale.md`).
|
|
314
|
+
*
|
|
315
|
+
* Everything this node computes with is device pixels: `contentBox()`,
|
|
316
|
+
* `this.style.fontSize` (core multiplies a style length before handing it
|
|
317
|
+
* over) and so the cell metrics shaped from it, the paint context and the
|
|
318
|
+
* renderer's surface. Two things are not, and each is converted once where
|
|
319
|
+
* it enters: a synthetic event's `x`/`y` (`_devicePoint`), and the
|
|
320
|
+
* constants below that never pass through a style — `DEFAULT_SIZE` and the
|
|
321
|
+
* cell guesses `measureContent` makes before there is a font.
|
|
322
|
+
*/
|
|
323
|
+
private get _scale(): number {
|
|
324
|
+
return this.scale > 0 ? this.scale : 1;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* A pointer event in the grid's own pixels.
|
|
329
|
+
*
|
|
330
|
+
* The native X event carries the device position the synthetic `x`/`y`
|
|
331
|
+
* were divided from, so it is the exact answer where there is one — core's
|
|
332
|
+
* own drawn elements read it the same way — and `x * scale` is the answer
|
|
333
|
+
* for an event synthesized without one.
|
|
334
|
+
*/
|
|
335
|
+
private _devicePoint(ev: { x: number; y: number; nativeEvent?: unknown }): {
|
|
336
|
+
x: number;
|
|
337
|
+
y: number;
|
|
338
|
+
} {
|
|
339
|
+
const native = ev.nativeEvent as { x?: unknown; y?: unknown } | null;
|
|
340
|
+
const s = this._scale;
|
|
341
|
+
return {
|
|
342
|
+
x: typeof native?.x === 'number' ? native.x : ev.x * s,
|
|
343
|
+
y: typeof native?.y === 'number' ? native.y : ev.y * s,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
286
347
|
/**
|
|
287
348
|
* The font, off the **style** rather than off a prop of its own.
|
|
288
349
|
*
|
|
@@ -291,25 +352,47 @@ export class VtTermNode extends Node {
|
|
|
291
352
|
* reason: a terminal inheriting the font from the tree above it is what
|
|
292
353
|
* makes `<box style={{ fontSize: 15 }}>` mean what it says. `<Terminal>`'s
|
|
293
354
|
* `fontFamily`/`fontSize` props are folded into this element's style.
|
|
355
|
+
*
|
|
356
|
+
* The size answered is in device pixels — the unit the glyphs are shaped
|
|
357
|
+
* at. A `fontSize` from the style arrives already multiplied; the default
|
|
358
|
+
* is a logical 13 and is multiplied here, so a bare `<Terminal>` on a 2x
|
|
359
|
+
* panel is the size it is on a 1x one, sharper rather than smaller.
|
|
294
360
|
*/
|
|
295
361
|
private _fontStyle(): { family: string; size: number } {
|
|
296
362
|
const style = this.style as { fontFamily?: string; fontSize?: number };
|
|
297
363
|
return {
|
|
298
364
|
family: style.fontFamily ?? DEFAULT_FAMILY,
|
|
299
|
-
size: Number(style.fontSize ?? DEFAULT_SIZE),
|
|
365
|
+
size: Number(style.fontSize ?? DEFAULT_SIZE * this._scale),
|
|
300
366
|
};
|
|
301
367
|
}
|
|
302
368
|
|
|
369
|
+
/**
|
|
370
|
+
* The faces for the current family and size, or null when there is no
|
|
371
|
+
* engine to build them from.
|
|
372
|
+
*
|
|
373
|
+
* Null is cached per font key when the engine answered but cannot build
|
|
374
|
+
* glyph runs — an `app.fonts` that is not ntk's and has not grown its
|
|
375
|
+
* seams, which react-x11's Cocoa backend was before 2.4.0
|
|
376
|
+
* (sidorares/react-x11#432) — so the probe runs once, not on every
|
|
377
|
+
* measure and paint. A box with no `app.fonts` at all (the mock
|
|
378
|
+
* backend) is not cached: it never had an engine to ask.
|
|
379
|
+
*/
|
|
303
380
|
private _fontSet(): FontSet | null {
|
|
304
381
|
const { family, size } = this._fontStyle();
|
|
305
382
|
const key = `${family}|${size}`;
|
|
306
|
-
if (
|
|
383
|
+
if (key === this._fontKey) return this._fonts;
|
|
307
384
|
const fonts = (this.app as { fonts?: NtkFonts } | null | undefined)?.fonts;
|
|
308
385
|
if (!fonts) return null;
|
|
386
|
+
const set = new FontSet(fonts, family, size);
|
|
309
387
|
this._fontKey = key;
|
|
310
|
-
this._fonts = new FontSet(fonts, family, size);
|
|
311
388
|
this._mirror.invalidate();
|
|
312
|
-
|
|
389
|
+
if (!set.glyphRuns) {
|
|
390
|
+
warnNoGlyphRuns();
|
|
391
|
+
this._fonts = null;
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
this._fonts = set;
|
|
395
|
+
return set;
|
|
313
396
|
}
|
|
314
397
|
|
|
315
398
|
/**
|
|
@@ -320,8 +403,11 @@ export class VtTermNode extends Node {
|
|
|
320
403
|
*/
|
|
321
404
|
override measureContent({ width, height }: MeasureConstraints): MeasuredSize {
|
|
322
405
|
const fonts = this._fontSet();
|
|
323
|
-
|
|
324
|
-
|
|
406
|
+
// The constraints and the answer are device pixels, like the metrics;
|
|
407
|
+
// the guesses made without a font (the mock backend) are scaled to match.
|
|
408
|
+
const s = this._scale;
|
|
409
|
+
const cellWidth = fonts?.metrics.cellWidth ?? DEFAULT_SIZE * 0.6 * s;
|
|
410
|
+
const cellHeight = fonts?.metrics.cellHeight ?? DEFAULT_SIZE * 1.2 * s;
|
|
325
411
|
return {
|
|
326
412
|
width: Math.min(PREFERRED_COLS * cellWidth, width),
|
|
327
413
|
height: Math.min(PREFERRED_ROWS * cellHeight, height),
|
|
@@ -763,13 +849,18 @@ export class VtTermNode extends Node {
|
|
|
763
849
|
|
|
764
850
|
// --- input: pointer ------------------------------------------------------
|
|
765
851
|
|
|
766
|
-
|
|
852
|
+
/** The cell under a pointer event. `contentBox()` and the cell metrics are
|
|
853
|
+
* device pixels, so the event is read in the same unit — a synthetic
|
|
854
|
+
* `x`/`y` is logical, and comparing it directly put a 2x click on the
|
|
855
|
+
* cell half as far from the corner as the pointer. */
|
|
856
|
+
private _cellAt(ev: { x: number; y: number; nativeEvent?: unknown }): Grid {
|
|
767
857
|
const fonts = this._fontSet();
|
|
768
858
|
const box = this.contentBox();
|
|
769
859
|
const cellWidth = fonts?.metrics.cellWidth ?? 1;
|
|
770
860
|
const cellHeight = fonts?.metrics.cellHeight ?? 1;
|
|
771
|
-
const
|
|
772
|
-
const
|
|
861
|
+
const p = this._devicePoint(ev);
|
|
862
|
+
const col = Math.floor((p.x - box.x) / cellWidth);
|
|
863
|
+
const row = Math.floor((p.y - box.y) / cellHeight);
|
|
773
864
|
return {
|
|
774
865
|
col: Math.max(0, Math.min(this._cols - 1, col)),
|
|
775
866
|
row: Math.max(0, Math.min(this._rows - 1, row)),
|
|
@@ -875,10 +966,12 @@ export class VtTermNode extends Node {
|
|
|
875
966
|
}
|
|
876
967
|
return;
|
|
877
968
|
}
|
|
878
|
-
// Dragging past an edge scrolls the scrollback under the pointer.
|
|
969
|
+
// Dragging past an edge scrolls the scrollback under the pointer. The
|
|
970
|
+
// box is device pixels; so is the point compared with it.
|
|
879
971
|
const box = this.contentBox();
|
|
880
|
-
|
|
881
|
-
|
|
972
|
+
const { y } = this._devicePoint(ev);
|
|
973
|
+
if (y < box.y) term.scrollLines(-1);
|
|
974
|
+
else if (y > box.y + box.height) term.scrollLines(1);
|
|
882
975
|
this._head = { line: this._absLine(row), col };
|
|
883
976
|
this._updateSelection();
|
|
884
977
|
}
|