summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-03-29 08:28:44 +0200
committercashmere <cashmere@autistici.org>2026-03-29 08:28:44 +0200
commit807a7ae4fa4741733f4c1d05b0c42c723439a0cc (patch)
treedcc802a2e24b8861c745467eac8a2809d6229b65
parent71d5c4402248e45eb2c713830dd012d3bd4c9d13 (diff)
downloadkitty-graphics-807a7ae4fa4741733f4c1d05b0c42c723439a0cc.tar.gz
docs: deep research on OSC 66 implementations
Comprehensive analysis of 5 OSC 66 implementations: - mdfried (Rust): erase-character dance, fractional scaling with s=2 base, unicode chunking for wide chars, bug history - neovim#32539: architecture analysis confirming our overlay approach - render-markdown.nvim#560: ecosystem blocked on neovim core - opentui (TS+Zig): separator convention, ST vs BEL terminator - foot!1927 (C): width-only impl, variation selector bugs Key actionable findings: ECH erase, s=2 fractional tiers, wide-char chunking, pre-erase before emission.
-rw-r--r--docs/osc66-research.md236
1 files changed, 236 insertions, 0 deletions
diff --git a/docs/osc66-research.md b/docs/osc66-research.md
new file mode 100644
index 0000000..619f397
--- /dev/null
+++ b/docs/osc66-research.md
@@ -0,0 +1,236 @@
+# OSC 66 Text Sizing — Research Notes
+
+## User Observations (v0.4.0-dev, 2026-03-29)
+
+### What works
+- Heading rendering at scale: ✅ (2x, 1.5x, 1.2x all working)
+- Protocol detection (CPR-based): ✅ (`KittyGfx[K+T]` modeline)
+- Folding (TAB/S-TAB): ✅ works
+- Color extraction from org-level faces: ✅
+
+### Known issues
+- **Scroll artifacts**: small visual artifacts when scrolling
+- **Heading horizontal drift**: headings shift left/right during
+ scroll — likely a column calculation issue in `posn-at-point`
+ or the cursor positioning escape (`\e[row;colH`)
+
+---
+
+## Existing Implementations (Deep Dive)
+
+### 1. mdfried — Terminal Markdown Viewer (Rust + ratatui)
+- **URL**: https://github.com/benjajaja/mdfried
+- **Status**: Most mature real-world OSC 66 consumer (v0.12.0+)
+- **Language**: Rust
+
+#### Architecture
+Two rendering paths for headings:
+1. **With OSC 66**: Direct escape sequences via `BigText` widget
+2. **Without OSC 66**: Falls back to rendering heading text into
+ images via `cosmic-text` font shaping, displayed via Kitty/Sixel
+
+#### The Erase-Character Dance (KEY FINDING)
+Before emitting OSC 66, mdfried **erases the target area** using ECH:
+```
+\x1b[{width}X\x1B[?7l -- erase current line + disable DECAWM
+\x1b[1B -- move down 1 row
+\x1b[{width}X\x1B[?7l -- erase next line
+\x1b[1A -- move back up
+```
+This prevents ghost artifacts. Uses ECH (`\x1b[NX`) which is more
+efficient than writing spaces. **We should consider adopting this
+for `kitty-gfx--erase-heading`.**
+
+#### Fractional Scaling for Size Tiers (KEY FINDING)
+All tiers use `s=2` as base, then `n:d` for fine-grained sizing:
+```
+Tier 1: s=2, n=7, d=7 (full 2x)
+Tier 2: s=2, n=5, d=6
+Tier 3: s=2, n=3, d=4
+Tier 4: s=2, n=2, d=3
+Tier 5: s=2, n=3, d=5
+Tier 6: s=2, n=1, d=3
+```
+Gives 6 distinct sizes that are smoother than integer `s=` alone.
+All headings occupy exactly 2 terminal rows regardless of tier.
+
+#### Unicode Chunking (BUG FIX v0.18.1)
+Wide characters (emoji, CJK) in headings broke rendering.
+Fix: emit each wide character in its own OSC 66 escape sequence.
+Width for partial chunks: `(chunk_width * n).div_ceil(d)` — round up.
+Comment in code says uncertain if Kitty bug or expected behavior.
+
+#### Ratatui Buffer Integration
+Puts entire escape sequence in a single cell, marks all other
+cells as "skip". **Equivalent to our overlay `display` property
+approach** — first cell gets the payload, rest is reserved space.
+
+#### Bug History
+- v0.12.0: Initial text sizing
+- v0.15.0: "All tiers above #1 had letter spacing too wide"
+ (wrong `w` calculation for partial chunks)
+- v0.18.1: Emoji/wide grapheme rendering bugs (unicode_chunks fix)
+
+### 2. neovim/neovim#32539 — Variable Font Sizes
+- **URL**: https://github.com/neovim/neovim/issues/32539
+- **Status**: Open, labeled `gsoc`. No implementation.
+
+#### justinmk (Neovim core):
+Variable line-height is feasible, could allow markdown headings
+at different heights. 54 hearts on this comment — massive demand.
+
+#### fredizzimo's Architecture Analysis (KEY FINDING)
+Identified fundamental challenges for any terminal editor:
+1. **j/k navigation**: scaled text breaks column mapping
+2. **Line height**: UI must tell editor the rendered height
+3. **Variable-width fonts**: UI must handle wrapping
+4. **Grid protocol**: can't send more cols/rows than grid allows
+
+His proposed approach (matches ours exactly):
+- Variable text is non-navigable (like virtual text / our overlays)
+- Plugin switches to normal rendering when cursor is on those lines
+- UI tells box height/width before drawing (avoid flicker)
+- Text attached to buffer with extmarks, space reserved with vtext
+
+#### algmyr's Use Case
+LSP inlay hints at smaller font — makes them visually distinct
+AND saves horizontal space. 20 thumbs up.
+
+#### CWood-sdf's Rendering Approaches
+1. Uniform row height: entire line height = tallest char (simpler)
+2. Push-aside: large chars push aside text below (drop-cap style)
+
+### 3. render-markdown.nvim#560 — Figlet Headings Request
+- **URL**: https://github.com/MeanderingProgrammer/render-markdown.nvim/issues/560
+- **Status**: Closed, deferred to OSC 66
+
+MeanderingProgrammer (owner): waiting for kitty text sizing to
+mature, get adoption, and get integrated in neovim first.
+
+**Key insight**: Neovim ecosystem is blocked on core UI changes.
+Emacs has advantage — `send-string-to-terminal` bypasses framework.
+
+### 4. opentui commit c246044 — TypeScript+Zig TUI Framework
+- **URL**: https://github.com/melMass/opentui/commit/c246044
+- **Language**: TypeScript + Zig
+- **Status**: AI-generated implementation (Nov 2025)
+
+#### Separator Convention (confirmed)
+- Within parameter group: colon `:` (e.g., `s=2:n=1:d=2`)
+- Between params and text: semicolon `;`
+- Format: `\x1b]66;PARAMS;TEXT\x1b\\`
+
+#### Terminator Choice
+Uses `ESC \` (ST) not BEL — "seems safer". Our code uses BEL (`\a`).
+Both are valid per spec. BEL is shorter (1 byte vs 2).
+
+#### Performance Note
+~10% terminal throughput overhead from multicell bookkeeping.
+"Use text sizing judiciously."
+
+### 5. foot!1927 — Width-Only OSC 66 Implementation
+- **URL**: https://codeberg.org/dnkl/foot/pulls/1927
+- **Language**: C (terminal emulator internals)
+- **Status**: Merged Feb 2025
+
+#### Scope
+Width parameter (`w=`) only. No font scaling, no multi-line characters.
+This is the `'width` tier our detection reports.
+
+#### w=0 Semantics
+Process each codepoint normally, as if printed directly to terminal.
+
+#### w!=0 Semantics
+Entire text string becomes a single combining character stored in
+first cell, followed by SPACER cells. Treats "foobar" with `w=6`
+as a single 6-column wide "character".
+
+#### Variation Selector Bug (krobelus)
+Splitting `⚠\ufe0f` across two OSC 66 sequences breaks rendering.
+Fix: always emit codepoint + variation selector in same sequence.
+
+#### Cursor on Multi-Width Characters
+Foot doesn't render cursor in the middle of a forced-width character.
+"Mostly doesn't make sense to place cursor on filler cell."
+
+#### Minimum Terminal Width
+Changed from 2 to 7 cells. Reflow logic breaks when a character
+(via `w=`) is wider than the terminal itself.
+
+#### Glyph Bleeding
+When glyph is wider than allocated cells, pixels bleed into adjacent
+cells. Visual-only, doesn't affect grid state.
+
+---
+
+## Actionable Findings for kitty-graphics.el
+
+### HIGH PRIORITY
+
+1. **Erase via ECH instead of spaces** (from mdfried)
+ Our `kitty-gfx--erase-heading` writes spaces. ECH (`\x1b[NX`)
+ is more efficient and what mdfried uses. Also consider disabling
+ DECAWM during erase to prevent wrapping artifacts.
+
+2. **Fractional scaling with s=2 base** (from mdfried)
+ Instead of our current s=1/2/3 integer scales, use s=2 for ALL
+ heading tiers with n:d ratios for fine-grained sizing. This means
+ all headings are exactly 2 rows tall, simplifying space reservation.
+
+3. **Unicode chunking for wide chars** (from mdfried v0.18.1)
+ If heading text contains emoji or CJK, emit each wide character
+ in its own OSC 66 sequence. We don't do this yet.
+
+### MEDIUM PRIORITY
+
+4. **Horizontal drift investigation**
+ The heading left/right shift during scroll could be related to:
+ - posn-at-point returning inconsistent col values
+ - body-left offset not being stable across redraws
+ - org-indent-mode or line-number-mode changing column offsets
+ fredizzimo's analysis confirms this is a fundamental challenge.
+
+5. **Pre-erase before OSC 66 emission** (from mdfried)
+ mdfried erases BEFORE emitting, not just when moving. We could
+ adopt this pattern in `kitty-gfx--place-heading`: erase area first,
+ then emit OSC 66. Prevents artifacts from partial overwrites.
+
+### LOW PRIORITY
+
+6. **ST vs BEL terminator**
+ Our code uses BEL (`\a`). Both valid. Consider ST (`\x1b\\`) for
+ consistency with other implementations, but BEL is 1 byte shorter.
+
+7. **w= parameter for heading text**
+ We currently use `w=0` (auto). This is fine for ASCII headings.
+ If we want exact column control (which could fix the drift), we
+ could calculate and emit explicit `w=` per character/chunk.
+
+---
+
+## Terminal Support Matrix
+
+| Terminal | Width (w=) | Scale (s=) | Fractional (n:d) | Notes |
+|----------|-----------|-----------|-----------------|-------|
+| Kitty ≥0.40.0 | ✅ | ✅ | ✅ | Full support |
+| Foot | ✅ | ❌ | ❌ | Width only (merged Feb 2025) |
+| Ghostty | 🔄 | ❌ | ❌ | Parsing added, rendering WIP |
+| tmux | ❌ | ❌ | ❌ | Breaks OSC 66 entirely |
+| WezTerm | ❌ | ❌ | ❌ | No support |
+| Neovide | ❌ | ❌ | ❌ | Feature requested |
+
+## Competitive Landscape
+
+| Project | Type | OSC 66 | Scroll Strategy |
+|---------|------|--------|----------------|
+| **kitty-graphics.el** | Editor overlay | ✅ | Two-phase re-emission |
+| mdfried | Full-screen TUI | ✅ | Full redraw per frame |
+| presenterm | Full-screen TUI | ✅ | Full redraw per frame |
+| opentui | TUI framework | ✅ | Double-buffered |
+| libvaxis | TUI framework | w= only | Double-buffered |
+| render-markdown.nvim | Neovim plugin | ❌ | Blocked on neovim core |
+| Neovim core | Editor | ❌ | GSoC project, no impl |
+
+**We are the only editor-overlay implementation.** All others
+are full-screen TUIs that own the viewport, or are waiting
+for framework support.