diff --git a/src/clayterm.c b/src/clayterm.c index 8b8ae69..b52c8f2 100644 --- a/src/clayterm.c +++ b/src/clayterm.c @@ -75,6 +75,11 @@ struct Clayterm { * re-init, so the values survive resize updates. */ uint32_t colors; /* max_colors evidence; §7.1 baseline is 256 */ int truecolor; /* positive truecolor evidence */ + /* Terminal-default colors for compositing destinations (renderer-spec + * §7.9), resolved by the host: reported theme > createTerm + * defaultTheme > black background / white foreground. 24-bit RGB. */ + uint32_t default_bg; + uint32_t default_fg; /* Emission tier, resolved once per reduce. last_tier detects tier * changes, which force a complete redraw (color-encoding-spec CI-5). */ int tier; @@ -857,12 +862,16 @@ int error_message_ptr(struct Clayterm *ct, int index) { } /* Push host-held color capability evidence into the instance - * (color-encoding-spec §4.2). Called once after init and after update() - * folds capability events that change either value; re-pushed by the - * host after any re-init so the values survive resize updates. */ -void set_capabilities(struct Clayterm *ct, uint32_t colors, int truecolor) { + * (color-encoding-spec §4.2; renderer-spec §7.9 defaults). Called once + * after init and after update() folds capability events that change any + * value; re-pushed by the host after any re-init so the values survive + * resize updates. */ +void set_capabilities(struct Clayterm *ct, uint32_t colors, int truecolor, + uint32_t default_bg, uint32_t default_fg) { ct->colors = colors; ct->truecolor = truecolor ? 1 : 0; + ct->default_bg = default_bg & 0x00FFFFFF; + ct->default_fg = default_fg & 0x00FFFFFF; } struct Clayterm *init(void *mem, int w, int h) { @@ -890,7 +899,9 @@ struct Clayterm *init(void *mem, int w, int h) { .lastbg = 0xffffffff, .lastx = -1, .lasty = -1, - .colors = 256, /* §7.1 baseline until the host pushes evidence */ + .colors = 256, /* §7.1 baseline until the host pushes evidence */ + .default_bg = 0x000000, /* §7.9 fallback: black background */ + .default_fg = 0xffffff, /* §7.9 fallback: white foreground */ .last_tier = -1, }; diff --git a/src/clayterm.h b/src/clayterm.h index 531356f..4f1ad5f 100644 --- a/src/clayterm.h +++ b/src/clayterm.h @@ -12,7 +12,8 @@ struct Clayterm; /* WASM exports */ int clayterm_size(int w, int h); struct Clayterm *init(void *mem, int w, int h); -void set_capabilities(struct Clayterm *ct, uint32_t colors, int truecolor); +void set_capabilities(struct Clayterm *ct, uint32_t colors, int truecolor, + uint32_t default_bg, uint32_t default_fg); void reduce(struct Clayterm *ct, uint32_t *buf, int len, int mode, int row, float deltaTime); char *output(struct Clayterm *ct); diff --git a/term-native.ts b/term-native.ts index f4e7b7f..db44015 100644 --- a/term-native.ts +++ b/term-native.ts @@ -26,10 +26,14 @@ const CLAY_DEFAULT_MAX_ELEMENT_COUNT = 8192; const MAX_FIXED_ELEMENT_WIRE_BYTES = 116; /** Color capability evidence the renderer consumes for emission - * (color-encoding-spec §4.2). */ + * (color-encoding-spec §4.2). The default colors are the §7.9 chain's + * resolution (reported theme > defaultTheme > black/white), packed as + * 24-bit RGB. */ export interface ColorCaps { colors: number; trueColor: boolean; + defaultBg: number; + defaultFg: number; } export interface Native { @@ -46,11 +50,16 @@ export interface Native { update(w: number, h: number): void; /** * Push color capability evidence into the renderer. Called by the - * host after update() folds capability events that change either - * value (color-encoding-spec §4.2); re-pushed internally after any - * re-init so the values survive resize updates. + * host after update() folds capability events that change any value + * (color-encoding-spec §4.2); re-pushed internally after any re-init + * so the values survive resize updates. */ - setCapabilities(colors: number, trueColor: boolean): void; + setCapabilities( + colors: number, + trueColor: boolean, + defaultBg: number, + defaultFg: number, + ): void; reduce( ct: number, buf: number, @@ -119,7 +128,13 @@ export async function createTermNative( ): void; output(ct: number): number; length(ct: number): number; - set_capabilities(ct: number, colors: number, truecolor: number): void; + set_capabilities( + ct: number, + colors: number, + truecolor: number, + default_bg: number, + default_fg: number, + ): void; Clay_SetPointerState(vec: number, down: number): void; pointer_over_count(): number; pointer_over_id_string_length(index: number): number; @@ -151,6 +166,8 @@ export async function createTermNative( statePtr, currentCaps.colors, currentCaps.trueColor ? 1 : 0, + currentCaps.defaultBg, + currentCaps.defaultFg, ); } @@ -187,8 +204,13 @@ export async function createTermNative( update(uw: number, uh: number): void { layout(uw, uh); }, - setCapabilities(colors: number, trueColor: boolean): void { - currentCaps = { colors, trueColor }; + setCapabilities( + colors: number, + trueColor: boolean, + defaultBg: number, + defaultFg: number, + ): void { + currentCaps = { colors, trueColor, defaultBg, defaultFg }; pushCaps(); }, reduce: ct.reduce, diff --git a/term.ts b/term.ts index 8d71a77..c14d179 100644 --- a/term.ts +++ b/term.ts @@ -14,6 +14,17 @@ export interface TermOptions { * baseline. */ terminfo?: TerminalInfo; + /** + * Fallback terminal default colors for compositing destinations + * (renderer-spec §7.9). Used when the terminal has not reported its + * own theme; does not change term.capabilities.theme, which holds + * only reported colors. Defaults to a black background and a white + * foreground. + */ + defaultTheme?: { + foreground?: Rgb; + background?: Rgb; + }; } /** @@ -94,6 +105,23 @@ function runtimeFromStatic(caps: Capabilities): RuntimeCapabilities { }); } +/** Resolve the §7.9 terminal-default chain: reported theme > + * defaultTheme > a black background and a white foreground. Packed as + * 24-bit RGB for the renderer. */ +function defaultColors( + fallback: TermOptions["defaultTheme"], + reported: { theme: RuntimeCapabilities["theme"] } | undefined, +): { defaultBg: number; defaultFg: number } { + let bg = reported?.theme.background ?? fallback?.background ?? + { r: 0, g: 0, b: 0 }; + let fg = reported?.theme.foreground ?? fallback?.foreground ?? + { r: 255, g: 255, b: 255 }; + return { + defaultBg: (bg.r & 0xff) << 16 | (bg.g & 0xff) << 8 | (bg.b & 0xff), + defaultFg: (fg.r & 0xff) << 16 | (fg.g & 0xff) << 8 | (fg.b & 0xff), + }; +} + export interface RenderOptions { mode?: "line"; row?: number; @@ -162,7 +190,7 @@ export interface Term { } export async function createTerm(options: TermOptions): Promise { - let { width, height, terminfo } = options; + let { width, height, terminfo, defaultTheme } = options; let seed = terminfo?.capabilities ?? { colors: 256, @@ -177,6 +205,7 @@ export async function createTerm(options: TermOptions): Promise { let native = await createTermNative(width, height, { colors: seed.colors, trueColor: seed.trueColor, + ...defaultColors(defaultTheme, undefined), }); let { memory } = native; @@ -310,15 +339,24 @@ export async function createTerm(options: TermOptions): Promise { } currentCaps = next; - // Folded evidence changed the renderer's color capabilities: - // push it so the next render resolves the new tier - // (color-encoding-spec §4.2). The tier change itself forces a - // complete redraw on the next transaction (§6.4). + // Folded evidence changed the renderer's color capabilities or + // theme: push it so the next render resolves the tier and the + // compositing defaults (color-encoding-spec §4.2; renderer-spec + // §7.9 theme changes). The update transaction itself emits no + // bytes; the next transaction picks the change up. + let colors = defaultColors(defaultTheme, next); if ( next.colors !== native.caps.colors || - next.trueColor !== native.caps.trueColor + next.trueColor !== native.caps.trueColor || + colors.defaultBg !== native.caps.defaultBg || + colors.defaultFg !== native.caps.defaultFg ) { - native.setCapabilities(next.colors, next.trueColor); + native.setCapabilities( + next.colors, + next.trueColor, + colors.defaultBg, + colors.defaultFg, + ); } if (bytes.length) out.push(bytes); } diff --git a/test/width.test.ts b/test/width.test.ts index 267ca64..9478d50 100644 --- a/test/width.test.ts +++ b/test/width.test.ts @@ -60,6 +60,8 @@ describe("width", () => { let native = await createTermNative(10, 3, { colors: 256, trueColor: false, + defaultBg: 0x000000, // §7.9 fallback: black background + defaultFg: 0xffffff, // §7.9 fallback: white foreground }); // TextEncoder never produces surrogate bytes, so pack a placeholder