fix: preserve grapheme clusters (combining marks) in cell emission - #114
fix: preserve grapheme clusters (combining marks) in cell emission#114natemoo-re wants to merge 4 commits into
Conversation
The Cell struct stored only a single uint32_t codepoint, so render_text silently dropped every combining mark (wcwidth ≤ 0): the emitted ANSI stream contained bare base codepoints with no continuations. Root cause: kitty-graphics Unicode placeholder cells require two combining diacritics (row/col index from the rowcolumn-diacritics table) to follow the base U+10EEEE codepoint. Because those marks were dropped, every placeholder cell emitted as row 0 — producing the N-repeated-top-band banding artifact in multi-row placements. The same drop affected any combining-mark or ZWJ content: accented characters (e + U+0301), flag pairs, skin-tone modifiers. Fix: - Cell gains `uint32_t combining[8]` (zero-terminated). Marks beyond 8 are silently truncated from the end; the first marks always survive. - cells_fill uses a designated-init template so combining[] is zeroed on every back-buffer reset, and setcell clears it on every base write. - render_text tracks the last-written column; zero-width codepoints go to append_combining() instead of being discarded. - present_cups / present_lines emit combining[] bytes immediately after the base character, before any cursor repositioning. - OUT_BYTES_PER_CELL bumped 64→128 to cover base + 8 combining marks. - cell_cmp checks combining[] so a changed mark triggers a diff/rediff. - Spec updated: §8.3.3 normative preservation requirement, §13 cluster cell representation with truncation semantics, §13 measurement note. Four new tests: kitty placeholder (U+10EEEE + 2 marks), combining accent (e + U+0301), ZWJ family emoji (ZWJ preserved per-cell; following emoji start new cells — inherent cell-model constraint, documented), and truncation-from-end pinned at 8 marks.
| /* Maximum combining marks stored per cell. Marks beyond this limit are | ||
| * silently truncated from the end; the first CELL_MAX_COMBINING are kept. */ | ||
| #define CELL_MAX_COMBINING 8 |
There was a problem hiding this comment.
this gives us plenty of headroom for the common case and is unlikely to be a problem in the short-term, but I have a slight concern that it will be a long-term issue if any terminal protocols introduce complex metadata via ZWJ that requires >8 codepoints
an alternative design would be bumping this cieling and keeping a dynamic map of combining size per cell rather than reserving a flat 8 per cell.
commit: |
|
Size Increased — +1.1 KB 119.9 KB unpacked |
Merging this PR will degrade performance by 15.74%
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing |
cells with combining marks (zero-width codepoints) were silently dropped when emitted because
render_textskipped codepoints withcw == 0. the cell model stored only a singleuint32_t, meaning combining accents, ZWJ emoji, skin-tone modifiers, flag variation emoji, and kitty graphics were broken.the fix here is to update
Cell's model to trackuint32_t combining[8]and haverender_textappend zero-width codepoints to the current column before cursor output. this requires updatingOUT_BYTES_PER_CELLfrom64to128to cover worst-case scenario (base char + 8 combining marks, silently dropping marks beyond 8).spec §8.3.3 has been updated with a grapheme-cluster preservation requirement. §13 now describes cells as "a grapheme cluster" rather than "a Unicode codepoint" and defines the capacity and truncation behavior.
semi-related to #84