@promptctl/rich-js 0.26.0 → 0.28.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 +1 -0
- package/THIRD-PARTY-NOTICES +42 -0
- package/dist/core/animation.d.ts +52 -15
- package/dist/core/animation.js +82 -37
- package/dist/core/ansi.d.ts +15 -8
- package/dist/core/ansi.js +106 -28
- package/dist/core/bidi-data.d.ts +14 -0
- package/dist/core/bidi-data.js +385 -0
- package/dist/core/bidi.d.ts +89 -0
- package/dist/core/bidi.js +615 -0
- package/dist/core/box.d.ts +9 -1
- package/dist/core/box.js +11 -0
- package/dist/core/cell-widths.d.ts +3 -2
- package/dist/core/cell-widths.js +93 -93
- package/dist/core/cells.d.ts +20 -0
- package/dist/core/cells.js +93 -5
- package/dist/core/color.d.ts +26 -18
- package/dist/core/color.js +37 -34
- package/dist/core/console.d.ts +26 -1
- package/dist/core/console.js +26 -6
- package/dist/core/control-codes.d.ts +61 -0
- package/dist/core/control-codes.js +157 -0
- package/dist/core/easing.js +6 -1
- package/dist/core/env.d.ts +6 -0
- package/dist/core/env.js +9 -1
- package/dist/core/export-html.js +4 -3
- package/dist/core/export-lines.d.ts +2 -0
- package/dist/core/export-lines.js +7 -3
- package/dist/core/export-svg.js +6 -5
- package/dist/core/json.js +9 -2
- package/dist/core/markup.js +4 -3
- package/dist/core/oklch.d.ts +2 -0
- package/dist/core/oklch.js +3 -3
- package/dist/core/orient.d.ts +68 -0
- package/dist/core/orient.js +224 -0
- package/dist/core/osc8.d.ts +12 -5
- package/dist/core/osc8.js +14 -7
- package/dist/core/pretty.js +22 -18
- package/dist/core/protocol.d.ts +53 -22
- package/dist/core/protocol.js +14 -6
- package/dist/core/render.d.ts +8 -3
- package/dist/core/render.js +19 -5
- package/dist/core/seal.d.ts +26 -0
- package/dist/core/seal.js +75 -0
- package/dist/core/segment.d.ts +9 -1
- package/dist/core/segment.js +11 -1
- package/dist/core/style.d.ts +29 -1
- package/dist/core/style.js +41 -5
- package/dist/core/text-order.d.ts +51 -0
- package/dist/core/text-order.js +50 -0
- package/dist/core/text.d.ts +10 -0
- package/dist/core/text.js +133 -87
- package/dist/core/timeline.js +6 -6
- package/dist/core/view.d.ts +23 -9
- package/dist/core/view.js +37 -12
- package/dist/core/wrap.d.ts +24 -6
- package/dist/core/wrap.js +82 -16
- package/dist/host/app.d.ts +48 -7
- package/dist/host/app.js +116 -11
- package/dist/host/index.d.ts +3 -2
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +35 -2
- package/dist/host/input.js +88 -4
- package/dist/host/terminal-colors.d.ts +18 -0
- package/dist/host/terminal-colors.js +46 -0
- package/dist/host/terminal-host.d.ts +2 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +2 -2
- package/dist/renderables/change.d.ts +1 -3
- package/dist/renderables/change.js +4 -6
- package/dist/renderables/effect.d.ts +61 -60
- package/dist/renderables/effect.js +91 -81
- package/dist/renderables/effects.d.ts +37 -8
- package/dist/renderables/effects.js +193 -55
- package/dist/renderables/frames.d.ts +1 -10
- package/dist/renderables/frames.js +1 -16
- package/dist/renderables/glyph-effect.d.ts +12 -13
- package/dist/renderables/glyph-effect.js +34 -32
- package/dist/renderables/glyph-effects.d.ts +27 -8
- package/dist/renderables/glyph-effects.js +75 -7
- package/dist/renderables/lifecycle.d.ts +40 -32
- package/dist/renderables/lifecycle.js +87 -48
- package/dist/renderables/live.d.ts +3 -3
- package/dist/renderables/live.js +4 -4
- package/dist/renderables/markdown.js +4 -2
- package/dist/renderables/panel.js +6 -4
- package/dist/renderables/progress.d.ts +11 -4
- package/dist/renderables/progress.js +29 -17
- package/dist/renderables/progressBar.d.ts +3 -3
- package/dist/renderables/progressBar.js +16 -13
- package/dist/renderables/syntax.js +7 -2
- package/dist/renderables/table.js +2 -1
- package/dist/template-bindings/animation-funcs.d.ts +49 -18
- package/dist/template-bindings/animation-funcs.js +115 -32
- package/dist/template-bindings/index.d.ts +7 -5
- package/dist/template-bindings/index.js +8 -5
- package/dist/template-bindings/palette-funcs.d.ts +1 -1
- package/dist/template-bindings/palette-funcs.js +1 -1
- package/dist/themes/buildPalette.d.ts +15 -39
- package/dist/themes/buildPalette.js +196 -67
- package/dist/themes/colorMath.d.ts +10 -4
- package/dist/themes/colorMath.js +33 -1
- package/dist/themes/data/index.js +1 -4
- package/dist/themes/data/textual-ansi.js +0 -143
- package/dist/themes/data/types.d.ts +3 -1
- package/dist/themes/palette.d.ts +22 -6
- package/dist/themes/palette.js +13 -21
- package/dist/themes/ramp.d.ts +1 -1
- package/dist/themes/ramp.js +1 -1
- package/dist/themes/registry.d.ts +7 -10
- package/dist/themes/registry.js +9 -30
- package/dist/themes/terminalThemes.d.ts +4 -10
- package/dist/themes/terminalThemes.js +31 -46
- package/dist/themes/transpose.d.ts +5 -10
- package/dist/themes/transpose.js +13 -24
- package/dist/widgets/button.d.ts +1 -1
- package/dist/widgets/button.js +6 -6
- package/dist/widgets/checkbox.js +4 -4
- package/dist/widgets/dropdown.d.ts +1 -1
- package/dist/widgets/dropdown.js +7 -5
- package/dist/widgets/event-router.js +2 -2
- package/dist/widgets/ink.d.ts +5 -4
- package/dist/widgets/ink.js +6 -9
- package/dist/widgets/text-input.d.ts +1 -1
- package/dist/widgets/text-input.js +41 -32
- package/dist/widgets/themed-widget.js +3 -3
- package/dist/widgets/toggle.d.ts +1 -1
- package/dist/widgets/toggle.js +5 -5
- package/dist/widgets/types.d.ts +2 -2
- package/dist/widgets/widget-app.d.ts +3 -3
- package/dist/widgets/widget-app.js +5 -5
- package/package.json +5 -4
package/dist/core/color.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Terminal color representation, parsing, downgrading, and ANSI code generation.
|
|
3
3
|
*/
|
|
4
|
+
import { isDumbTerminal } from "./env.js";
|
|
4
5
|
import { Memo, WeakMemo } from "./memo.js";
|
|
5
6
|
import { pattern } from "./pattern.js";
|
|
6
|
-
|
|
7
|
-
// only `import type`s from `core/color.ts`, so there is no runtime cycle.
|
|
8
|
-
import { Palette } from "../themes/palette.js";
|
|
7
|
+
import { detectTextOrder } from "./text-order.js";
|
|
9
8
|
// --- ColorRgba ---
|
|
10
9
|
function hex2(byte) {
|
|
11
10
|
return byte.toString(16).padStart(2, "0");
|
|
@@ -320,23 +319,15 @@ function forcedColorSystem(env) {
|
|
|
320
319
|
function envOf(options) {
|
|
321
320
|
return options.env ?? (typeof process !== "undefined" ? process.env : {});
|
|
322
321
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
* as Emacs' shell mode sets it. It takes no colour and nothing that moves the
|
|
326
|
-
* cursor.
|
|
327
|
-
*/
|
|
328
|
-
export function isDumbTerminal(env) {
|
|
329
|
-
const term = env["TERM"];
|
|
330
|
-
return term === "dumb" || term === "unknown";
|
|
322
|
+
function isTTYOf(options) {
|
|
323
|
+
return options.isTTY ?? (typeof process !== "undefined" ? (process.stdout?.isTTY ?? false) : false);
|
|
331
324
|
}
|
|
332
325
|
// What the destination can draw, preferences aside: TTY presence,
|
|
333
326
|
// TERM=dumb/unknown, COLORTERM, known terminal names, TERM patterns. `null`
|
|
334
327
|
// here means the destination takes no escapes at all.
|
|
335
328
|
function terminalColorSystem(options) {
|
|
336
329
|
const env = envOf(options);
|
|
337
|
-
|
|
338
|
-
(typeof process !== "undefined" ? (process.stdout?.isTTY ?? false) : false);
|
|
339
|
-
if (!isTTY)
|
|
330
|
+
if (!isTTYOf(options))
|
|
340
331
|
return null;
|
|
341
332
|
if (isDumbTerminal(env))
|
|
342
333
|
return null;
|
|
@@ -379,11 +370,14 @@ function terminalColorSystem(options) {
|
|
|
379
370
|
*/
|
|
380
371
|
export function resolveDestination(spec, options = {}) {
|
|
381
372
|
const reducedMotion = options.reducedMotion ?? false;
|
|
373
|
+
// Ahead of both arms: a colour spec says nothing about order.
|
|
374
|
+
const textOrder = options.textOrder ?? detectTextOrder(envOf(options), isTTYOf(options));
|
|
382
375
|
if (spec !== "auto") {
|
|
383
376
|
return {
|
|
384
377
|
colorSystem: typeof spec === "string" ? resolveColorSystem(spec) : spec,
|
|
385
378
|
hyperlinks: true,
|
|
386
379
|
reducedMotion,
|
|
380
|
+
textOrder,
|
|
387
381
|
};
|
|
388
382
|
}
|
|
389
383
|
return {
|
|
@@ -391,6 +385,7 @@ export function resolveDestination(spec, options = {}) {
|
|
|
391
385
|
hyperlinks: (forcedColorSystem(envOf(options)) ?? null) !== null ||
|
|
392
386
|
terminalColorSystem(options) !== null,
|
|
393
387
|
reducedMotion,
|
|
388
|
+
textOrder,
|
|
394
389
|
};
|
|
395
390
|
}
|
|
396
391
|
/**
|
|
@@ -844,24 +839,17 @@ export function blendRgb(color1, color2, crossFade = 0.5) {
|
|
|
844
839
|
}
|
|
845
840
|
// --- TerminalTheme ---
|
|
846
841
|
/**
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
* **`ansiColors` is the theme's own sixteen colours**, the shades a terminal
|
|
851
|
-
* showing this theme draws `red`, `blue` and the rest in. `ColorSpec.parse("red")`
|
|
852
|
-
* is ANSI colour 1 under every theme; which red that is, is the theme's to say,
|
|
853
|
-
* as it is in Rich, where each `TerminalTheme` carries its own table.
|
|
842
|
+
* What a terminal draws with: the part of a `TerminalTheme` a colour resolves
|
|
843
|
+
* against.
|
|
854
844
|
*/
|
|
855
|
-
export class
|
|
845
|
+
export class TerminalColors {
|
|
856
846
|
backgroundColor;
|
|
857
847
|
foregroundColor;
|
|
858
848
|
ansiColors;
|
|
859
|
-
|
|
860
|
-
constructor(backgroundColor, foregroundColor, ansiColors, palette) {
|
|
849
|
+
constructor(backgroundColor, foregroundColor, ansiColors) {
|
|
861
850
|
this.backgroundColor = backgroundColor;
|
|
862
851
|
this.foregroundColor = foregroundColor;
|
|
863
852
|
this.ansiColors = ansiColors;
|
|
864
|
-
this.palette = palette;
|
|
865
853
|
// [LAW:parse-dont-validate] Every reader indexes `ansiColors` 0–15 as the
|
|
866
854
|
// terminal's sixteen, so a theme is the proof it holds exactly those.
|
|
867
855
|
if (ansiColors.firstIndex !== 0 || ansiColors.size !== 16) {
|
|
@@ -869,6 +857,22 @@ export class TerminalTheme {
|
|
|
869
857
|
}
|
|
870
858
|
}
|
|
871
859
|
}
|
|
860
|
+
/**
|
|
861
|
+
* A terminal theme — surface/foreground baseline, the sixteen ANSI colours,
|
|
862
|
+
* and a semantic palette.
|
|
863
|
+
*
|
|
864
|
+
* **`ansiColors` is the theme's own sixteen colours**, the shades a terminal
|
|
865
|
+
* showing this theme draws `red`, `blue` and the rest in. `ColorSpec.parse("red")`
|
|
866
|
+
* is ANSI colour 1 under every theme; which red that is, is the theme's to say,
|
|
867
|
+
* as it is in Rich, where each `TerminalTheme` carries its own table.
|
|
868
|
+
*/
|
|
869
|
+
export class TerminalTheme extends TerminalColors {
|
|
870
|
+
palette;
|
|
871
|
+
constructor(backgroundColor, foregroundColor, ansiColors, palette) {
|
|
872
|
+
super(backgroundColor, foregroundColor, ansiColors);
|
|
873
|
+
this.palette = palette;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
872
876
|
// --- ColorTable data ---
|
|
873
877
|
function buildStandard16() {
|
|
874
878
|
return [
|
|
@@ -943,20 +947,19 @@ export const EIGHT_BIT_TABLE = new ColorTable(build256Table());
|
|
|
943
947
|
*/
|
|
944
948
|
export const EIGHT_BIT_DOWNGRADE_TABLE = new ColorTable(build256Table().slice(16), 16);
|
|
945
949
|
export const WINDOWS_TABLE = new ColorTable(buildWindowsTable());
|
|
946
|
-
// --- Internal fallback
|
|
950
|
+
// --- Internal fallback terminals ---
|
|
947
951
|
//
|
|
948
952
|
// [LAW:one-way-deps] Preset themes (DEFAULT_TERMINAL_THEME, MONOKAI, NORD, ...)
|
|
949
|
-
// live in `src/themes/terminalThemes.ts` because they
|
|
950
|
-
//
|
|
953
|
+
// live in `src/themes/terminalThemes.ts` because they hold the registry's
|
|
954
|
+
// palettes. Keeping them out of `core/color.ts` makes the dependency strictly
|
|
951
955
|
// `themes/* -> core/color`, with no runtime back-edge.
|
|
952
956
|
//
|
|
953
957
|
// `getTruecolor()` retains a fallback for callers that omit `theme`: the
|
|
954
|
-
// terminal a colour's depth assumes,
|
|
955
|
-
//
|
|
956
|
-
|
|
957
|
-
const INTERNAL_DEFAULT_THEME = new TerminalTheme(new ColorRgba(0, 0, 0), new ColorRgba(255, 255, 255), STANDARD_TABLE, new Palette("default", true, new Map()));
|
|
958
|
+
// terminal a colour's depth assumes, its own sixteen and no palette, which the
|
|
959
|
+
// lookups it serves never read.
|
|
960
|
+
const INTERNAL_DEFAULT_TERMINAL = new TerminalColors(new ColorRgba(0, 0, 0), new ColorRgba(255, 255, 255), STANDARD_TABLE);
|
|
958
961
|
/** The Windows console in its default Campbell scheme: a WINDOWS colour's terminal. */
|
|
959
|
-
const
|
|
962
|
+
const WINDOWS_CONSOLE_TERMINAL = new TerminalColors(WINDOWS_TABLE.get(0), WINDOWS_TABLE.get(7), WINDOWS_TABLE);
|
|
960
963
|
/**
|
|
961
964
|
* The terminal a colour at `depth` is drawn by: `theme`, or when a caller names
|
|
962
965
|
* none, the one the depth assumes — the Windows console at WINDOWS, the VGA
|
|
@@ -965,7 +968,7 @@ const WINDOWS_CONSOLE_THEME = new TerminalTheme(WINDOWS_TABLE.get(0), WINDOWS_TA
|
|
|
965
968
|
* palette this does not.)
|
|
966
969
|
*/
|
|
967
970
|
export function resolveTerminal(theme, depth) {
|
|
968
|
-
return theme ?? (depth === ColorDepth.WINDOWS ?
|
|
971
|
+
return theme ?? (depth === ColorDepth.WINDOWS ? WINDOWS_CONSOLE_TERMINAL : INTERNAL_DEFAULT_TERMINAL);
|
|
969
972
|
}
|
|
970
973
|
/**
|
|
971
974
|
* The table a downgrade to `depth` rounds a fixed colour against.
|
package/dist/core/console.d.ts
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Style, Theme } from "./style.js";
|
|
5
5
|
import { ColorDepth } from "./color.js";
|
|
6
|
+
import type { TextOrder } from "./text-order.js";
|
|
6
7
|
import type { ColorSystemName, Destination } from "./color.js";
|
|
7
8
|
import type { Env } from "./env.js";
|
|
8
|
-
import type { TerminalTheme } from "./color.js";
|
|
9
|
+
import type { TerminalColors, TerminalTheme } from "./color.js";
|
|
9
10
|
import { type JSONOptions } from "./json.js";
|
|
10
11
|
import type { Highlighter } from "./highlighter.js";
|
|
11
12
|
import { type RuleOptions } from "../renderables/rule.js";
|
|
12
13
|
import type { Height, OverflowMethod, RenderOptions, StyleErrorHandler } from "./protocol.js";
|
|
14
|
+
import { type HostFrame } from "./protocol.js";
|
|
13
15
|
import type { Unsubscribe } from "./subscription.js";
|
|
14
16
|
export interface ConsoleSink {
|
|
15
17
|
write(chunk: string | Uint8Array): unknown;
|
|
@@ -52,6 +54,8 @@ export interface ConsoleOptions {
|
|
|
52
54
|
* TERM=dumb: false). `false` with `colorSystem: null` writes plain text.
|
|
53
55
|
*/
|
|
54
56
|
hyperlinks?: boolean;
|
|
57
|
+
/** What the output's destination does with direction. Default: what `detectTextOrder` finds. */
|
|
58
|
+
textOrder?: TextOrder;
|
|
55
59
|
/**
|
|
56
60
|
* The terminal can draw only ASCII: boxes, rules, tree guides and widget
|
|
57
61
|
* marks draw with ASCII characters instead of line-drawing glyphs. A
|
|
@@ -68,6 +72,12 @@ export interface ConsoleOptions {
|
|
|
68
72
|
* it in; nothing here reads the environment for it. Default false.
|
|
69
73
|
*/
|
|
70
74
|
reducedMotion?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* The terminal this console draws on (`RenderOptions.terminal`), which
|
|
77
|
+
* every effect it draws resolves its colours on. Default: the one the
|
|
78
|
+
* colour depth assumes.
|
|
79
|
+
*/
|
|
80
|
+
terminal?: TerminalColors;
|
|
71
81
|
/**
|
|
72
82
|
* Static width (cells). Ignored when `getSize` is provided. Falls back to
|
|
73
83
|
* the `COLUMNS` env var / the bound stream's `columns` / 80.
|
|
@@ -125,6 +135,13 @@ export interface ConsoleOptions {
|
|
|
125
135
|
}
|
|
126
136
|
export interface PrintOptions {
|
|
127
137
|
style?: string | Style;
|
|
138
|
+
/**
|
|
139
|
+
* The frame this print is drawn in (`RenderOptions.frame`): the moment
|
|
140
|
+
* everything in it that moves is drawn for, named by the caller — a host's
|
|
141
|
+
* `frameNow()`. [LAW:single-enforcer] The one place a print's time is
|
|
142
|
+
* decided; absent, the print is drawn for no moment, 0 (`frameTimeOf`).
|
|
143
|
+
*/
|
|
144
|
+
frame?: HostFrame;
|
|
128
145
|
justify?: "default" | "left" | "center" | "right" | "full";
|
|
129
146
|
overflow?: OverflowMethod;
|
|
130
147
|
highlight?: boolean;
|
|
@@ -141,6 +158,7 @@ export declare class Console {
|
|
|
141
158
|
private readonly _lastLogTime;
|
|
142
159
|
private readonly _style;
|
|
143
160
|
private readonly _isTerminal;
|
|
161
|
+
private readonly _attended;
|
|
144
162
|
private readonly _interactive;
|
|
145
163
|
private readonly _file;
|
|
146
164
|
private readonly _stream;
|
|
@@ -148,6 +166,7 @@ export declare class Console {
|
|
|
148
166
|
private readonly _markup;
|
|
149
167
|
private readonly _highlight;
|
|
150
168
|
private readonly _theme;
|
|
169
|
+
private readonly _terminal;
|
|
151
170
|
private readonly _onStyleError;
|
|
152
171
|
private readonly _asciiOnly;
|
|
153
172
|
private readonly _highlighter;
|
|
@@ -180,6 +199,12 @@ export declare class Console {
|
|
|
180
199
|
print(...args: unknown[]): void;
|
|
181
200
|
private _draw;
|
|
182
201
|
log(...args: unknown[]): void;
|
|
202
|
+
/**
|
|
203
|
+
* Sounds the terminal's bell, Rich's `Console.bell`, for whoever is at it: a
|
|
204
|
+
* pipe, a dumb terminal and a capture get nothing. It goes straight to the sink
|
|
205
|
+
* because a bell moves no cursor, and a live region would repaint its frame.
|
|
206
|
+
*/
|
|
207
|
+
bell(): void;
|
|
183
208
|
rule(title?: string, options?: RuleOptions): void;
|
|
184
209
|
printJson(json: string | object, options?: JSONOptions): void;
|
|
185
210
|
/**
|
package/dist/core/console.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Segment } from "./segment.js";
|
|
5
5
|
import { NULL_STYLE, DEFAULT_THEME } from "./style.js";
|
|
6
|
-
import {
|
|
6
|
+
import { resolveDestination } from "./color.js";
|
|
7
|
+
import { isDumbTerminal } from "./env.js";
|
|
7
8
|
import { encodeHtml } from "./export-html.js";
|
|
8
9
|
import { encodeSvg } from "./export-svg.js";
|
|
9
10
|
import { RichText } from "./text.js";
|
|
@@ -17,6 +18,7 @@ import { ReprHighlighter, NullHighlighter } from "./highlighter.js";
|
|
|
17
18
|
// rather than being restated here; see `rule()`.
|
|
18
19
|
import { Rule } from "../renderables/rule.js";
|
|
19
20
|
import { segmentsToString, shownOn } from "./render.js";
|
|
21
|
+
import { sealed } from "./seal.js";
|
|
20
22
|
import { placeBlock } from "./place.js";
|
|
21
23
|
import { activePass, isRenderable, measure, render } from "./protocol.js";
|
|
22
24
|
import { ListState, State, refuseRenderWrite } from "./state.js";
|
|
@@ -181,6 +183,7 @@ export class Console {
|
|
|
181
183
|
_lastLogTime = new State(null);
|
|
182
184
|
_style;
|
|
183
185
|
_isTerminal;
|
|
186
|
+
_attended;
|
|
184
187
|
_interactive;
|
|
185
188
|
_file;
|
|
186
189
|
// [LAW:one-source-of-truth] The resolved output stream, not the `stderr`
|
|
@@ -191,6 +194,7 @@ export class Console {
|
|
|
191
194
|
_markup;
|
|
192
195
|
_highlight;
|
|
193
196
|
_theme;
|
|
197
|
+
_terminal;
|
|
194
198
|
_onStyleError;
|
|
195
199
|
_asciiOnly;
|
|
196
200
|
_highlighter;
|
|
@@ -211,7 +215,7 @@ export class Console {
|
|
|
211
215
|
// [LAW:dataflow-not-control-flow] `isTTY` and `env` are forwarded
|
|
212
216
|
// unconditionally, so detection stays inside the console's injected
|
|
213
217
|
// environment rather than the ambient process's.
|
|
214
|
-
const resolved = resolveDestination(options?.colorSystem === undefined ? "auto" : options.colorSystem, { isTTY: this._isTerminal, env: environment.env, reducedMotion: options?.reducedMotion });
|
|
218
|
+
const resolved = resolveDestination(options?.colorSystem === undefined ? "auto" : options.colorSystem, { isTTY: this._isTerminal, env: environment.env, reducedMotion: options?.reducedMotion, ...(options?.textOrder === undefined ? {} : { textOrder: options.textOrder }) });
|
|
215
219
|
// [LAW:single-enforcer] The console's one override, an explicit
|
|
216
220
|
// `hyperlinks`, is applied here and nowhere else; every write encodes for
|
|
217
221
|
// this value whole. [LAW:one-source-of-truth] The reader's motion
|
|
@@ -223,9 +227,12 @@ export class Console {
|
|
|
223
227
|
// [LAW:no-ambient-temporal-coupling] The theme is assigned before the
|
|
224
228
|
// style, because the console's own style may be one of the theme's names.
|
|
225
229
|
this._theme = options?.theme ?? DEFAULT_THEME;
|
|
230
|
+
this._terminal = options?.terminal;
|
|
226
231
|
this._style = this._theme.resolve(options?.style ?? NULL_STYLE);
|
|
227
|
-
// Rich's rule: a terminal is interactive unless it is a dumb one.
|
|
228
|
-
|
|
232
|
+
// Rich's rule: a terminal is interactive unless it is a dumb one. Forcing that
|
|
233
|
+
// is animation policy, and says nothing of who is at the terminal.
|
|
234
|
+
this._attended = this._isTerminal && !isDumbTerminal(environment.env);
|
|
235
|
+
this._interactive = options?.forceInteractive ?? this._attended;
|
|
229
236
|
this._file = options?.file;
|
|
230
237
|
this._record = options?.record ?? false;
|
|
231
238
|
this._markup = options?.markup !== false;
|
|
@@ -290,6 +297,8 @@ export class Console {
|
|
|
290
297
|
theme: this._theme,
|
|
291
298
|
onStyleError: this._onStyleError,
|
|
292
299
|
colorSystem: this._destination.colorSystem,
|
|
300
|
+
textOrder: this._destination.textOrder,
|
|
301
|
+
terminal: this._terminal,
|
|
293
302
|
markup: this._markup,
|
|
294
303
|
highlight: this._highlight,
|
|
295
304
|
highlighter: this._highlighter,
|
|
@@ -317,7 +326,7 @@ export class Console {
|
|
|
317
326
|
!isRenderable(lastArg) &&
|
|
318
327
|
("style" in lastArg || "justify" in lastArg || "markup" in lastArg ||
|
|
319
328
|
"highlight" in lastArg || "overflow" in lastArg || "end" in lastArg ||
|
|
320
|
-
"softWrap" in lastArg || "crop" in lastArg || "sep" in lastArg)) {
|
|
329
|
+
"softWrap" in lastArg || "crop" in lastArg || "sep" in lastArg || "frame" in lastArg)) {
|
|
321
330
|
opts = lastArg;
|
|
322
331
|
items = args.slice(0, -1);
|
|
323
332
|
}
|
|
@@ -407,6 +416,7 @@ export class Console {
|
|
|
407
416
|
const renderOpts = {
|
|
408
417
|
...options,
|
|
409
418
|
...strings,
|
|
419
|
+
frame: opts.frame,
|
|
410
420
|
justify: opts.justify === "default" ? undefined : opts.justify,
|
|
411
421
|
overflow: opts.overflow ?? (softWrap ? "ignore" : undefined),
|
|
412
422
|
noWrap: softWrap,
|
|
@@ -494,6 +504,16 @@ export class Console {
|
|
|
494
504
|
// stamp, so the next row's must show.
|
|
495
505
|
this._lastLogTime.set(stamped);
|
|
496
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* Sounds the terminal's bell, Rich's `Console.bell`, for whoever is at it: a
|
|
509
|
+
* pipe, a dumb terminal and a capture get nothing. It goes straight to the sink
|
|
510
|
+
* because a bell moves no cursor, and a live region would repaint its frame.
|
|
511
|
+
*/
|
|
512
|
+
bell() {
|
|
513
|
+
refuseRenderWrite(this._madeIn);
|
|
514
|
+
if (this._attended && this._capture.get() === null)
|
|
515
|
+
this.file.write("\x07");
|
|
516
|
+
}
|
|
497
517
|
// [LAW:one-source-of-truth] `RuleOptions` is `Rule`'s, not a restatement of
|
|
498
518
|
// it. `console.ts` used to declare its own copy and field-by-field forward
|
|
499
519
|
// into this constructor, which made a second map of one shape — identical
|
|
@@ -546,7 +566,7 @@ export class Console {
|
|
|
546
566
|
}
|
|
547
567
|
// --- Export (when record:true) ---
|
|
548
568
|
exportText({ clear = true } = {}) {
|
|
549
|
-
const text = this._recorded.get().map((s) => s.text).join("");
|
|
569
|
+
const text = [...sealed(this._recorded.get(), "plain")].map((s) => s.text).join("");
|
|
550
570
|
if (clear)
|
|
551
571
|
this._recorded.set([]);
|
|
552
572
|
return text;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The census of code points that do not print: every one in Unicode's general
|
|
3
|
+
* categories Cc, Cf, Zl and Zp, and the variation selectors, with what `RichText`
|
|
4
|
+
* does when content carries it. [LAW:one-source-of-truth] The sanitizer
|
|
5
|
+
* (`sanitized`), the wire (`wireText`) and the cell-width measure
|
|
6
|
+
* (`cellLen`) all read this table, so whether content keeps a code point has one
|
|
7
|
+
* statement. `docs/control-codes.md` is each family's use and harm, and what real
|
|
8
|
+
* terminals were measured doing with it.
|
|
9
|
+
*
|
|
10
|
+
* A row does not carry a width. A kept code point takes the cells Rich's table
|
|
11
|
+
* gives it (`cell-widths.ts`), and a second statement of them here would be one
|
|
12
|
+
* that could drift.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* What `RichText` does with a code point. `strip` deletes it from content. `keep`
|
|
16
|
+
* holds it as a character. `line-break` is a line ending, so content holds an LF
|
|
17
|
+
* in its place. `pictured` means nothing inside a span, so content holds the
|
|
18
|
+
* stand-in `pictureOf` gives in its place and the reader sees something was there.
|
|
19
|
+
* `withheld` is held in content and measured as nothing, and `wireText` decides
|
|
20
|
+
* from its neighbours whether it is written.
|
|
21
|
+
*
|
|
22
|
+
* Only `keep`, and a `withheld` code point `wireText` lets by, reaches the wire. A
|
|
23
|
+
* raw `Segment` is not content, so what it holds of the others is deleted there.
|
|
24
|
+
*/
|
|
25
|
+
export type Disposition = "strip" | "keep" | "line-break" | "pictured" | "withheld";
|
|
26
|
+
/**
|
|
27
|
+
* The code points `first` to `last`, inclusive, and what is done with each. A
|
|
28
|
+
* `pictured` row carries what content shows in place of each of its code points.
|
|
29
|
+
*/
|
|
30
|
+
export type ControlCodeRow = {
|
|
31
|
+
readonly first: number;
|
|
32
|
+
readonly last: number;
|
|
33
|
+
readonly disposition: Exclude<Disposition, "pictured">;
|
|
34
|
+
} | {
|
|
35
|
+
readonly first: number;
|
|
36
|
+
readonly last: number;
|
|
37
|
+
readonly disposition: "pictured";
|
|
38
|
+
readonly picture: (codePoint: number) => string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Ascending and disjoint, every Cc, Cf, Zl, Zp and Variation_Selector code point
|
|
42
|
+
* in exactly one row (`test/core/control-codes.test.ts`).
|
|
43
|
+
*
|
|
44
|
+
* Rich's `Text` strips only `STRIP_CONTROL_CODES` (BEL, BS, VT, FF, CR) and writes
|
|
45
|
+
* every other code point to the wire. Here a directive reaches the wire only
|
|
46
|
+
* through the library's own constructs: `Style`, a link, `Console.bell`, the paint
|
|
47
|
+
* layer.
|
|
48
|
+
*/
|
|
49
|
+
export declare const CONTROL_CODES: readonly ControlCodeRow[];
|
|
50
|
+
/** What content shows in place of `codePoint`, whose disposition is `pictured`. */
|
|
51
|
+
export declare function pictureOf(codePoint: number): string;
|
|
52
|
+
/** A regular expression class of the code points in rows `chosen` picks, for `u`. */
|
|
53
|
+
export declare function controlClass(chosen: (disposition: Disposition) => boolean): string;
|
|
54
|
+
/**
|
|
55
|
+
* UTS #51's subdivision flag, as a `u` source: the black flag, a CLDR subdivision
|
|
56
|
+
* spelled in tag letters and digits, and CANCEL TAG. The one sequence tag
|
|
57
|
+
* characters have a reader for.
|
|
58
|
+
*/
|
|
59
|
+
export declare const SUBDIVISION_FLAG = "\uD83C\uDFF4[\uDB40\uDC30-\uDB40\uDC39\uDB40\uDC61-\uDB40\uDC7A]{3,6}\uDB40\uDC7F";
|
|
60
|
+
/** What happens to `codePoint`. One in no row is kept. */
|
|
61
|
+
export declare function dispositionOf(codePoint: number): Disposition;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The census of code points that do not print: every one in Unicode's general
|
|
3
|
+
* categories Cc, Cf, Zl and Zp, and the variation selectors, with what `RichText`
|
|
4
|
+
* does when content carries it. [LAW:one-source-of-truth] The sanitizer
|
|
5
|
+
* (`sanitized`), the wire (`wireText`) and the cell-width measure
|
|
6
|
+
* (`cellLen`) all read this table, so whether content keeps a code point has one
|
|
7
|
+
* statement. `docs/control-codes.md` is each family's use and harm, and what real
|
|
8
|
+
* terminals were measured doing with it.
|
|
9
|
+
*
|
|
10
|
+
* A row does not carry a width. A kept code point takes the cells Rich's table
|
|
11
|
+
* gives it (`cell-widths.ts`), and a second statement of them here would be one
|
|
12
|
+
* that could drift.
|
|
13
|
+
*/
|
|
14
|
+
const strip = (first, last) => ({ first, last, disposition: "strip" });
|
|
15
|
+
const keep = (first, last) => ({ first, last, disposition: "keep" });
|
|
16
|
+
const lineBreak = (first, last) => ({ first, last, disposition: "line-break" });
|
|
17
|
+
const pictured = (first, last, picture) => ({ first, last, disposition: "pictured", picture });
|
|
18
|
+
/**
|
|
19
|
+
* The stand-ins. A C0 control's Control Pictures symbol is U+2400 up, DEL's is
|
|
20
|
+
* U+2421. Unicode gives C1 no pictures, so each shows U+2426 SYMBOL FOR SUBSTITUTE:
|
|
21
|
+
* one cell under every East Asian width setting, which U+FFFD is not, and apart
|
|
22
|
+
* from a real decoding error. Each is one code unit, so offsets hold.
|
|
23
|
+
*/
|
|
24
|
+
const controlPicture = (codePoint) => String.fromCharCode(0x2400 + codePoint);
|
|
25
|
+
const deletePicture = () => "\u2421";
|
|
26
|
+
const substitute = () => "\u2426";
|
|
27
|
+
const withheld = (first, last) => ({ first, last, disposition: "withheld" });
|
|
28
|
+
/**
|
|
29
|
+
* Ascending and disjoint, every Cc, Cf, Zl, Zp and Variation_Selector code point
|
|
30
|
+
* in exactly one row (`test/core/control-codes.test.ts`).
|
|
31
|
+
*
|
|
32
|
+
* Rich's `Text` strips only `STRIP_CONTROL_CODES` (BEL, BS, VT, FF, CR) and writes
|
|
33
|
+
* every other code point to the wire. Here a directive reaches the wire only
|
|
34
|
+
* through the library's own constructs: `Style`, a link, `Console.bell`, the paint
|
|
35
|
+
* layer.
|
|
36
|
+
*/
|
|
37
|
+
export const CONTROL_CODES = [
|
|
38
|
+
// NUL to BS. BEL's alert is `Console.bell`, and BS's overstrike, bold and
|
|
39
|
+
// underline in nroff output, is `decodeAnsi`: taken a code point at a time, text
|
|
40
|
+
// can turn neither into what it means.
|
|
41
|
+
pictured(0x00, 0x08, controlPicture),
|
|
42
|
+
keep(0x09, 0x0a),
|
|
43
|
+
// VT and FF, mandatory breaks in UAX #14 as LS and PS are.
|
|
44
|
+
lineBreak(0x0b, 0x0c),
|
|
45
|
+
// CR. A CRLF's LF ends its line however the two were chunked, and a CR's
|
|
46
|
+
// overwrite is `decodeAnsi`'s. What is given up is a classic Mac file's lines.
|
|
47
|
+
strip(0x0d, 0x0d),
|
|
48
|
+
// SO and SI switch the terminal's character set; DLE to SUB are transmission
|
|
49
|
+
// controls, and CAN and SUB abort a sequence; ESC begins one, and content that
|
|
50
|
+
// means styling says so with `decodeAnsi`; FS to US are data separators.
|
|
51
|
+
pictured(0x0e, 0x1f, controlPicture),
|
|
52
|
+
// DEL.
|
|
53
|
+
pictured(0x7f, 0x7f, deletePicture),
|
|
54
|
+
// The C1 controls. VTE, WezTerm and xterm.js read U+009B as CSI and U+009D as
|
|
55
|
+
// OSC; xterm, kitty and others ignore them. None has a meaning in text but NEL,
|
|
56
|
+
// which Unicode treats as CR, LF and CRLF are treated (§5.8). A stray one is
|
|
57
|
+
// often windows-1252 decoded as Latin-1, but which, a code point alone cannot
|
|
58
|
+
// say, so mojibake is the caller's to repair. `TextInput` refuses them as keys.
|
|
59
|
+
pictured(0x80, 0x84, substitute),
|
|
60
|
+
lineBreak(0x85, 0x85),
|
|
61
|
+
pictured(0x86, 0x9f, substitute),
|
|
62
|
+
// SOFT HYPHEN: a place a word may break, drawn as a hyphen only where one does.
|
|
63
|
+
// Terminals disagree on its cell, so it never reaches them.
|
|
64
|
+
withheld(0xad, 0xad),
|
|
65
|
+
// The Arabic and Syriac number signs and other prepended concatenation marks,
|
|
66
|
+
// which draw a glyph.
|
|
67
|
+
keep(0x600, 0x605),
|
|
68
|
+
// ARABIC LETTER MARK, a bidi mark like LRM and RLM below: it holds no state,
|
|
69
|
+
// so right-to-left text can use it.
|
|
70
|
+
keep(0x61c, 0x61c),
|
|
71
|
+
// More prepended concatenation marks.
|
|
72
|
+
keep(0x6dd, 0x6dd),
|
|
73
|
+
keep(0x70f, 0x70f),
|
|
74
|
+
keep(0x890, 0x891),
|
|
75
|
+
keep(0x8e2, 0x8e2),
|
|
76
|
+
// The Mongolian free variation selectors.
|
|
77
|
+
withheld(0x180b, 0x180d),
|
|
78
|
+
// MONGOLIAN VOWEL SEPARATOR, which shapes the vowel after it.
|
|
79
|
+
keep(0x180e, 0x180e),
|
|
80
|
+
withheld(0x180f, 0x180f),
|
|
81
|
+
// ZWSP, a place a line may break (`wrap.ts`); ZWNJ, which Persian and Urdu spell
|
|
82
|
+
// with; ZWJ, for emoji and conjuncts; and the bidi marks LRM and RLM, which hold
|
|
83
|
+
// no state, so right-to-left text can use them. ZWJ's joining is Rich's width
|
|
84
|
+
// rule, which `cells.ts` holds apart from this row.
|
|
85
|
+
keep(0x200b, 0x200f),
|
|
86
|
+
// LINE SEPARATOR and PARAGRAPH SEPARATOR.
|
|
87
|
+
lineBreak(0x2028, 0x2029),
|
|
88
|
+
// The bidi embeddings and overrides with their closer PDF, and the isolates
|
|
89
|
+
// below. Content holds them; what reaches the wire is `sealed`'s to decide.
|
|
90
|
+
keep(0x202a, 0x202e),
|
|
91
|
+
// WORD JOINER, which only asks that nothing break here, and the invisible
|
|
92
|
+
// mathematical operators.
|
|
93
|
+
keep(0x2060, 0x2064),
|
|
94
|
+
// The bidi isolates with their closer PDI.
|
|
95
|
+
keep(0x2066, 0x2069),
|
|
96
|
+
// The deprecated shaping and swapping switches. Each holds state to the end of
|
|
97
|
+
// the text, Unicode deprecates them, and no terminal was found honouring one.
|
|
98
|
+
strip(0x206a, 0x206f),
|
|
99
|
+
// VARIATION SELECTOR-1 to 16: glyph variants, and text or emoji presentation.
|
|
100
|
+
withheld(0xfe00, 0xfe0f),
|
|
101
|
+
// ZERO WIDTH NO-BREAK SPACE, the byte order mark, as a word joiner.
|
|
102
|
+
keep(0xfeff, 0xfeff),
|
|
103
|
+
// Interlinear annotation. No terminal draws ruby, the annotating text stays
|
|
104
|
+
// visible, and the anchors hold no state, so there is nothing to contain.
|
|
105
|
+
keep(0xfff9, 0xfffb),
|
|
106
|
+
// The Kaithi number signs, which draw a glyph.
|
|
107
|
+
keep(0x110bd, 0x110bd),
|
|
108
|
+
keep(0x110cd, 0x110cd),
|
|
109
|
+
// Egyptian hieroglyph, shorthand and musical format controls.
|
|
110
|
+
keep(0x13430, 0x1343f),
|
|
111
|
+
keep(0x1bca0, 0x1bca3),
|
|
112
|
+
keep(0x1d173, 0x1d17a),
|
|
113
|
+
// LANGUAGE TAG, deprecated.
|
|
114
|
+
strip(0xe0001, 0xe0001),
|
|
115
|
+
// The tag characters: an emoji subdivision flag, or text no reader can see.
|
|
116
|
+
withheld(0xe0020, 0xe007f),
|
|
117
|
+
// The ideographic variation selectors: Japanese and Chinese glyph variants.
|
|
118
|
+
withheld(0xe0100, 0xe01ef),
|
|
119
|
+
];
|
|
120
|
+
/** What content shows in place of `codePoint`, whose disposition is `pictured`. */
|
|
121
|
+
export function pictureOf(codePoint) {
|
|
122
|
+
const row = rowOf(codePoint);
|
|
123
|
+
if (row?.disposition !== "pictured")
|
|
124
|
+
throw new RangeError(`U+${codePoint.toString(16).padStart(4, "0")} is not pictured`);
|
|
125
|
+
return row.picture(codePoint);
|
|
126
|
+
}
|
|
127
|
+
/** A regular expression class of the code points in rows `chosen` picks, for `u`. */
|
|
128
|
+
export function controlClass(chosen) {
|
|
129
|
+
const hex = (codePoint) => `\\u{${codePoint.toString(16)}}`;
|
|
130
|
+
return `[${CONTROL_CODES.filter((row) => chosen(row.disposition)).map((row) => `${hex(row.first)}-${hex(row.last)}`).join("")}]`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* UTS #51's subdivision flag, as a `u` source: the black flag, a CLDR subdivision
|
|
134
|
+
* spelled in tag letters and digits, and CANCEL TAG. The one sequence tag
|
|
135
|
+
* characters have a reader for.
|
|
136
|
+
*/
|
|
137
|
+
export const SUBDIVISION_FLAG = "\u{1f3f4}[\u{e0030}-\u{e0039}\u{e0061}-\u{e007a}]{3,6}\u{e007f}";
|
|
138
|
+
/** What happens to `codePoint`. One in no row is kept. */
|
|
139
|
+
export function dispositionOf(codePoint) {
|
|
140
|
+
return rowOf(codePoint)?.disposition ?? "keep";
|
|
141
|
+
}
|
|
142
|
+
/** The row holding `codePoint`, if any. */
|
|
143
|
+
function rowOf(codePoint) {
|
|
144
|
+
let low = 0;
|
|
145
|
+
let high = CONTROL_CODES.length - 1;
|
|
146
|
+
while (low <= high) {
|
|
147
|
+
const mid = (low + high) >> 1;
|
|
148
|
+
const row = CONTROL_CODES[mid];
|
|
149
|
+
if (codePoint < row.first)
|
|
150
|
+
high = mid - 1;
|
|
151
|
+
else if (codePoint > row.last)
|
|
152
|
+
low = mid + 1;
|
|
153
|
+
else
|
|
154
|
+
return row;
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
package/dist/core/easing.js
CHANGED
|
@@ -379,7 +379,12 @@ export function passAt(what, t, period) {
|
|
|
379
379
|
// takes off; carrying a pass's start out of `t` itself would leave each
|
|
380
380
|
// sample its own rounding. `+ 0` turns the `-0` a small negative `t` rounds
|
|
381
381
|
// to into pass 0, so the pass is a plain integer whoever reads it.
|
|
382
|
-
|
|
382
|
+
const pass = Math.round((t - into) / period) + 0;
|
|
383
|
+
// [LAW:no-silent-failure] Past where a pass is a float, `period` is finer than time resolves: the refusal a
|
|
384
|
+
// boundary walked there gives, said of `t` and not of the infinite sum a pass that overflowed would build.
|
|
385
|
+
if (!Number.isFinite(pass))
|
|
386
|
+
throw new RangeError(`${what}: a period of ${period} s is finer than time resolves near t = ${t}`);
|
|
387
|
+
return { pass, into };
|
|
383
388
|
}
|
|
384
389
|
/** The least normal double, 2^-1022: below it every double is a multiple of `Number.MIN_VALUE`. */
|
|
385
390
|
const MIN_NORMAL = 2 ** -1022;
|
package/dist/core/env.d.ts
CHANGED
|
@@ -14,3 +14,9 @@
|
|
|
14
14
|
* `NodeJS` namespace. `test/seam/browser-types.test.ts` holds that line.
|
|
15
15
|
*/
|
|
16
16
|
export type Env = Readonly<Record<string, string | undefined>>;
|
|
17
|
+
/**
|
|
18
|
+
* A terminal that says it cannot be drawn on: `TERM` is `dumb` or `unknown`,
|
|
19
|
+
* as Emacs' shell mode sets it. It takes no colour and nothing that moves the
|
|
20
|
+
* cursor.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isDumbTerminal(env: Env): boolean;
|
package/dist/core/env.js
CHANGED
|
@@ -13,4 +13,12 @@
|
|
|
13
13
|
* without `@types/node` — the ordinary browser project — cannot resolve the
|
|
14
14
|
* `NodeJS` namespace. `test/seam/browser-types.test.ts` holds that line.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* A terminal that says it cannot be drawn on: `TERM` is `dumb` or `unknown`,
|
|
18
|
+
* as Emacs' shell mode sets it. It takes no colour and nothing that moves the
|
|
19
|
+
* cursor.
|
|
20
|
+
*/
|
|
21
|
+
export function isDumbTerminal(env) {
|
|
22
|
+
const term = env["TERM"];
|
|
23
|
+
return term === "dumb" || term === "unknown";
|
|
24
|
+
}
|
package/dist/core/export-html.js
CHANGED
|
@@ -121,13 +121,14 @@ function onGrid(text, draw) {
|
|
|
121
121
|
* blinks with the glyph. A wide glyph's box sits between the two: it inherits
|
|
122
122
|
* the double underline, and the inner span inside it draws the other lines.
|
|
123
123
|
*/
|
|
124
|
-
function runHtml({ text, look }) {
|
|
124
|
+
function runHtml({ text, look, inVisualOrder }) {
|
|
125
125
|
const drawn = look.underline === "double"
|
|
126
126
|
? span([...paintCss(look), "text-decoration-line:underline", "text-decoration-style:double", ...BLINK[look.blink]], onGrid(text, (glyph) => span([`color:${look.foreground.hex}`, ...glyphCss(look)], glyph)))
|
|
127
127
|
: span([...paintCss(look), ...glyphCss(look)], onGrid(text, (glyph) => glyph));
|
|
128
|
+
const ordered = inVisualOrder ? span(["unicode-bidi:bidi-override"], drawn) : drawn;
|
|
128
129
|
return look.href === null
|
|
129
|
-
?
|
|
130
|
-
: `<a href="${escapeAttribute(look.href)}" style="${ANCHOR_CSS}">${
|
|
130
|
+
? ordered
|
|
131
|
+
: `<a href="${escapeAttribute(look.href)}" style="${ANCHOR_CSS}">${ordered}</a>`;
|
|
131
132
|
}
|
|
132
133
|
/**
|
|
133
134
|
* The CSS a page includes once, wherever fragments appear: what an inline
|
|
@@ -65,6 +65,8 @@ export interface ExportLook {
|
|
|
65
65
|
export interface ExportRun {
|
|
66
66
|
readonly text: string;
|
|
67
67
|
readonly look: ExportLook;
|
|
68
|
+
/** Its cells are in display order already, so a browser must not reorder them. */
|
|
69
|
+
readonly inVisualOrder: boolean;
|
|
68
70
|
}
|
|
69
71
|
/** One terminal row, left to right. An empty array is a blank row. */
|
|
70
72
|
export type ExportLine = readonly ExportRun[];
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import { blendRgb, ColorSpec } from "./color.js";
|
|
33
33
|
import { NULL_STYLE } from "./style.js";
|
|
34
|
+
import { sealed } from "./seal.js";
|
|
34
35
|
/**
|
|
35
36
|
* The URL schemes an export will link.
|
|
36
37
|
*
|
|
@@ -146,13 +147,16 @@ export function resolveLook(style, theme) {
|
|
|
146
147
|
*/
|
|
147
148
|
export function exportLines(segments, theme) {
|
|
148
149
|
const rows = [[]];
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
// A browser is a display that reorders and holds the controls at zero width.
|
|
151
|
+
for (const segment of sealed(segments, "logical")) {
|
|
152
|
+
const style = segment.style ?? NULL_STYLE;
|
|
153
|
+
const look = resolveLook(style, theme);
|
|
154
|
+
const inVisualOrder = style.bidi?.order === "visual";
|
|
151
155
|
segment.text.split("\n").forEach((piece, index) => {
|
|
152
156
|
if (index > 0)
|
|
153
157
|
rows.push([]);
|
|
154
158
|
if (piece.length > 0)
|
|
155
|
-
rows[rows.length - 1].push({ text: piece, look });
|
|
159
|
+
rows[rows.length - 1].push({ text: piece, look, inVisualOrder });
|
|
156
160
|
});
|
|
157
161
|
}
|
|
158
162
|
return rows[rows.length - 1].length === 0 ? rows.slice(0, -1) : rows;
|