From f51ca62485c867c5671fb3cbd6cb336b596a97d5 Mon Sep 17 00:00:00 2001 From: Sauyon Lee Date: Mon, 18 May 2026 00:53:32 -0700 Subject: [PATCH 1/2] perf(renderer): hoist quadMap out of renderBlockElement hot path Move the quadrant codepoint lookup table (and its QUAD_* bit constants) to module scope as a Map so it's constructed once at load time rather than on every renderBlockElement call. The switch to Map also drops the need for the lib/renderer.ts useSimpleNumberKeys lint override added in 3836409. Co-Authored-By: Claude Opus 4.7 (1M context) --- biome.json | 14 +------------- lib/renderer.ts | 39 ++++++++++++++++++++------------------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/biome.json b/biome.json index f620855..ca014e2 100644 --- a/biome.json +++ b/biome.json @@ -39,17 +39,5 @@ }, "files": { "ignore": ["node_modules", "dist", "coverage", "*.wasm", ".git", ".vite", "bun.lock"] - }, - "overrides": [ - { - "include": ["lib/renderer.ts"], - "linter": { - "rules": { - "complexity": { - "useSimpleNumberKeys": "off" - } - } - } - } - ] + } } diff --git a/lib/renderer.ts b/lib/renderer.ts index 1a7581e..c67aabc 100644 --- a/lib/renderer.ts +++ b/lib/renderer.ts @@ -115,6 +115,25 @@ export const DEFAULT_THEME: Required = { brightWhite: '#ffffff', }; +// Quadrant block elements ▖▗▘▙▚▛▜▝▞▟ at U+2596..U+259F. +// Bitmap of which corners (UL, UR, LL, LR) are filled per codepoint. +const QUAD_UL = 0b1000; +const QUAD_UR = 0b0100; +const QUAD_LL = 0b0010; +const QUAD_LR = 0b0001; +const QUAD_MAP = new Map([ + [0x2596, QUAD_LL], + [0x2597, QUAD_LR], + [0x2598, QUAD_UL], + [0x2599, QUAD_UL | QUAD_LL | QUAD_LR], + [0x259a, QUAD_UL | QUAD_LR], + [0x259b, QUAD_UL | QUAD_UR | QUAD_LL], + [0x259c, QUAD_UL | QUAD_UR | QUAD_LR], + [0x259d, QUAD_UR], + [0x259e, QUAD_UR | QUAD_LL], + [0x259f, QUAD_UR | QUAD_LL | QUAD_LR], +]); + // ============================================================================ // CanvasRenderer Class // ============================================================================ @@ -1188,25 +1207,7 @@ export class CanvasRenderer { return true; } - // Quadrants ▖▗▘▙▚▛▜▝▞▟ at U+2596..U+259F. Bitmap of which corners - // (UL, UR, LL, LR) are filled per codepoint. - const QUAD_UL = 0b1000; - const QUAD_UR = 0b0100; - const QUAD_LL = 0b0010; - const QUAD_LR = 0b0001; - const quadMap: Record = { - 0x2596: QUAD_LL, - 0x2597: QUAD_LR, - 0x2598: QUAD_UL, - 0x2599: QUAD_UL | QUAD_LL | QUAD_LR, - 0x259a: QUAD_UL | QUAD_LR, - 0x259b: QUAD_UL | QUAD_UR | QUAD_LL, - 0x259c: QUAD_UL | QUAD_UR | QUAD_LR, - 0x259d: QUAD_UR, - 0x259e: QUAD_UR | QUAD_LL, - 0x259f: QUAD_UR | QUAD_LL | QUAD_LR, - }; - const quads = quadMap[codepoint]; + const quads = QUAD_MAP.get(codepoint); if (quads === undefined) return false; const halfW = Math.round(w / 2); const halfH = Math.round(h / 2); From e14427aa27cbad8df13a8e1e61325b71387ac72d Mon Sep 17 00:00:00 2001 From: Sauyon Lee Date: Mon, 18 May 2026 01:06:49 -0700 Subject: [PATCH 2/2] refactor(renderer): drop unused renderBlockElement renderBlockElement was the original procedural block-element renderer introduced in b6ef353. It was superseded by lib/box-drawing.ts's drawBoxOrBlock (wired into renderCellText in f737572) and has had no call sites since the box-drawing module landed. Removes the method, the QUAD_* constants, and the QUAD_MAP added in the previous commit (which optimized a code path that wasn't executed). Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/renderer.ts | 110 ------------------------------------------------ 1 file changed, 110 deletions(-) diff --git a/lib/renderer.ts b/lib/renderer.ts index c67aabc..6bc04a8 100644 --- a/lib/renderer.ts +++ b/lib/renderer.ts @@ -115,25 +115,6 @@ export const DEFAULT_THEME: Required = { brightWhite: '#ffffff', }; -// Quadrant block elements ▖▗▘▙▚▛▜▝▞▟ at U+2596..U+259F. -// Bitmap of which corners (UL, UR, LL, LR) are filled per codepoint. -const QUAD_UL = 0b1000; -const QUAD_UR = 0b0100; -const QUAD_LL = 0b0010; -const QUAD_LR = 0b0001; -const QUAD_MAP = new Map([ - [0x2596, QUAD_LL], - [0x2597, QUAD_LR], - [0x2598, QUAD_UL], - [0x2599, QUAD_UL | QUAD_LL | QUAD_LR], - [0x259a, QUAD_UL | QUAD_LR], - [0x259b, QUAD_UL | QUAD_UR | QUAD_LL], - [0x259c, QUAD_UL | QUAD_UR | QUAD_LR], - [0x259d, QUAD_UR], - [0x259e, QUAD_UR | QUAD_LL], - [0x259f, QUAD_UR | QUAD_LL | QUAD_LR], -]); - // ============================================================================ // CanvasRenderer Class // ============================================================================ @@ -1127,97 +1108,6 @@ export class CanvasRenderer { return canvas; } - /** - * Render a Block Elements codepoint (U+2580..U+259F) as fillRect(s) in - * the current fillStyle. Returns true if the codepoint is a handled - * block element; false to fall through to fillText. - * - * Drawing block elements through the font produces ~1-device-px gaps - * at cell edges at integer dpr because the rasterized glyph doesn't - * exactly fill the cell box. In half-block image renderings (ansimage, - * pixterm) those gaps line up into a visible cell grid. Native - * terminals draw block elements programmatically for the same reason. - * - * The eighths blocks (U+2581..U+2587 lower; U+2589..U+258F left) and - * full block (U+2588) are stripes of n/8 of the cell. Shading blocks - * (U+2591..U+2593) modulate globalAlpha for 25/50/75% fill. Quadrant - * blocks (U+2596..U+259F) split the cell into a 2x2 grid and fill - * some subset. - */ - private renderBlockElement( - codepoint: number, - cellX: number, - cellY: number, - cellWidth: number - ): boolean { - if (codepoint < 0x2580 || codepoint > 0x259f) return false; - - const w = cellWidth; - const h = this.metrics.height; - - // Upper half ▀ - if (codepoint === 0x2580) { - this.ctx.fillRect(cellX, cellY, w, Math.round(h / 2)); - return true; - } - - // Lower n/8 blocks ▁▂▃▄▅▆▇ + full block █ (= 8/8) - if (codepoint >= 0x2581 && codepoint <= 0x2588) { - const eighths = codepoint - 0x2580; - const blockH = Math.round((h * eighths) / 8); - this.ctx.fillRect(cellX, cellY + h - blockH, w, blockH); - return true; - } - - // Left n/8 blocks ▉▊▋▌▍▎▏ — eighths decreases as codepoint increases - if (codepoint >= 0x2589 && codepoint <= 0x258f) { - const eighths = 0x2590 - codepoint; - const blockW = Math.round((w * eighths) / 8); - this.ctx.fillRect(cellX, cellY, blockW, h); - return true; - } - - // Right half ▐ - if (codepoint === 0x2590) { - const left = Math.round(w / 2); - this.ctx.fillRect(cellX + left, cellY, w - left, h); - return true; - } - - // Shading ░▒▓ — modulate globalAlpha against current fillStyle - if (codepoint >= 0x2591 && codepoint <= 0x2593) { - const alphaForShade = [0.25, 0.5, 0.75][codepoint - 0x2591]; - const prev = this.ctx.globalAlpha; - this.ctx.globalAlpha = prev * alphaForShade; - this.ctx.fillRect(cellX, cellY, w, h); - this.ctx.globalAlpha = prev; - return true; - } - - // Upper 1/8 ▔ - if (codepoint === 0x2594) { - this.ctx.fillRect(cellX, cellY, w, Math.round(h / 8)); - return true; - } - - // Right 1/8 ▕ - if (codepoint === 0x2595) { - const left = Math.round((w * 7) / 8); - this.ctx.fillRect(cellX + left, cellY, w - left, h); - return true; - } - - const quads = QUAD_MAP.get(codepoint); - if (quads === undefined) return false; - const halfW = Math.round(w / 2); - const halfH = Math.round(h / 2); - if (quads & QUAD_UL) this.ctx.fillRect(cellX, cellY, halfW, halfH); - if (quads & QUAD_UR) this.ctx.fillRect(cellX + halfW, cellY, w - halfW, halfH); - if (quads & QUAD_LL) this.ctx.fillRect(cellX, cellY + halfH, halfW, h - halfH); - if (quads & QUAD_LR) this.ctx.fillRect(cellX + halfW, cellY + halfH, w - halfW, h - halfH); - return true; - } - /** * Substitute a cell's text rendering with a slice of a kitty graphics * image. Called from renderCellText when the cell's codepoint is