Yeah, this stuff is hard. The approach used in wezterm is to use unicode graphemes for cells, and use the unicode width of the grapheme to decide whether a given cell is double-width.
Ligatures are handled at rendering time, partly because the terminal emulation and model layer doesn't know about fonts (it can run "headless" in a multiplexer where there are no fonts), and partly because the shaper library doesn't output information about cells but rather about which glyphs to render in which positions--that information doesn't easily map to the terminal cell model.
For extra pain, some font designers have ligatured sequences that map to a single glyph that may be several cells wide, while others use alternative glyph fragments for each component of the ligature, and others may emit two blank glyphs, followed by a triple wide glyph with a negative x offset of almost two blank glyphs in width. It's difficult to map this information to cells.
Ligatures are handled at rendering time, partly because the terminal emulation and model layer doesn't know about fonts (it can run "headless" in a multiplexer where there are no fonts), and partly because the shaper library doesn't output information about cells but rather about which glyphs to render in which positions--that information doesn't easily map to the terminal cell model.
For extra pain, some font designers have ligatured sequences that map to a single glyph that may be several cells wide, while others use alternative glyph fragments for each component of the ligature, and others may emit two blank glyphs, followed by a triple wide glyph with a negative x offset of almost two blank glyphs in width. It's difficult to map this information to cells.