/* * line.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "text-cache.h" // TODO: Test setting of ch_and_idx to make sure the right ch_is_idx bit is set // TODO: Test handling of calt ligatures with scale see is_group_calt_ligature() // TODO: Font Rendering of scale > 1 and width > 1 // TODO: Handle selection with multicell // TODO: URL detection with multicell // TODO: Cursor rendering over multicell // TODO: Test the escape codes to delete and insert characters and lines with multicell // TODO: Handle replay of dumped graphics_command and multicell_command // TODO: Handle rewrap of multiline chars // TODO: Handle rewrap when a character is too wide/tall to fit on resized screen typedef union CellAttrs { struct { uint16_t decoration : 3; uint16_t bold : 1; uint16_t italic : 1; uint16_t reverse : 1; uint16_t strike : 1; uint16_t dim : 1; uint16_t mark : 2; uint16_t : 6; }; uint16_t val; } CellAttrs; static_assert(sizeof(CellAttrs) == sizeof(uint16_t), "Fix the ordering of CellAttrs"); #define WIDTH_MASK (3u) #define DECORATION_MASK (7u) #define NUM_UNDERLINE_STYLES (5u) #define SGR_MASK (~(((CellAttrs){.mark=MARK_MASK}).val)) // Text presentation selector #define VS15 0xfe0e // Emoji presentation selector #define VS16 0xfe0f typedef struct { color_type fg, bg, decoration_fg; sprite_index sprite_x, sprite_y, sprite_z; CellAttrs attrs; } GPUCell; static_assert(sizeof(GPUCell) == 20, "Fix the ordering of GPUCell"); typedef union MultiCellData { struct { char_type scale: 3; char_type width: 3; char_type subscale: 2; char_type vertical_align: 2; char_type explicitly_set: 1; char_type : 20; char_type msb : 1; }; char_type val; } MultiCellData; static_assert(sizeof(MultiCellData) == sizeof(char_type), "Fix the ordering of MultiCellData"); typedef union CPUCell { struct { char_type ch_or_idx: sizeof(char_type) * 8 - 1; char_type ch_is_idx: 1; char_type hyperlink_id: sizeof(hyperlink_id_type) * 8; char_type x : 8; char_type y : 4; char_type is_multicell : 1; char_type next_char_was_wrapped : 1; char_type : 2; }; struct { char_type ch_and_idx: sizeof(char_type) * 8; char_type : sizeof(char_type) * 8; }; uint64_t val; } CPUCell; static_assert(sizeof(CPUCell) == sizeof(uint64_t), "Fix the ordering of CPUCell"); typedef union LineAttrs { struct { uint8_t is_continued : 1; uint8_t has_dirty_text : 1; uint8_t has_image_placeholders : 1; uint8_t prompt_kind : 2; uint8_t : 3; }; uint8_t val; } LineAttrs ; static_assert(sizeof(LineAttrs) == sizeof(uint8_t), "Fix the ordering of LineAttrs"); typedef struct { PyObject_HEAD GPUCell *gpu_cells; CPUCell *cpu_cells; index_type xnum, ynum; bool needs_free; LineAttrs attrs; TextCache *text_cache; } Line; typedef struct MultiCellCommand { unsigned int width, scale, subscale; size_t payload_sz; } MultiCellCommand; Line* alloc_line(TextCache *text_cache); void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, unsigned int count, bool is_group); const char* cell_as_sgr(const GPUCell *, const GPUCell *); static inline bool cell_has_text(const CPUCell *c) { return c->ch_and_idx != 0; } static inline void cell_set_char(CPUCell *c, char_type ch) { c->ch_and_idx = ch & 0x7fffffff; } static inline bool cell_is_char(const CPUCell *c, char_type ch) { return c->ch_and_idx == ch; } static inline unsigned num_codepoints_in_cell(const CPUCell *c, const TextCache *tc) { unsigned ans; if (c->ch_is_idx) { ans = tc_num_codepoints(tc, c->ch_or_idx); if (c->is_multicell) ans--; } else ans = c->ch_or_idx ? 1 : 0; return ans; } static inline MultiCellData cell_multicell_data(const CPUCell *c, const TextCache *tc) { return (MultiCellData){.val = tc_last_char_at_index(tc, c->ch_or_idx)}; } static inline unsigned mcd_x_limit(MultiCellData mcd) { return mcd.scale * mcd.width; } static inline void text_in_cell(const CPUCell *c, const TextCache *tc, ListOfChars *ans) { ans->is_multicell = false; if (c->ch_is_idx) { tc_chars_at_index(tc, c->ch_or_idx, ans); if (c->is_multicell) { ans->is_multicell = true; ans->is_topleft = c->x + c->y == 0; if (ans->count > 0) { ans->count--; } } } else { ans->count = 1; ans->chars[0] = c->ch_or_idx; } } static inline bool text_in_cell_without_alloc(const CPUCell *c, const TextCache *tc, ListOfChars *ans) { ans->is_multicell = false; if (c->ch_is_idx) { if (!tc_chars_at_index_without_alloc(tc, c->ch_or_idx, ans)) return false; if (c->is_multicell) { ans->is_multicell = true; ans->is_topleft = c->x + c->y == 0; if (ans->count > 0) { ans->count--; } } return true; } ans->count = 1; if (ans->capacity < 1) return false; ans->chars[0] = c->ch_or_idx; return true; } static inline void cell_set_chars(CPUCell *c, TextCache *tc, const ListOfChars *lc) { if (lc->count <= 1) cell_set_char(c, lc->chars[0]); else { c->ch_or_idx = tc_get_or_insert_chars(tc, lc); c->ch_is_idx = true; } } static inline char_type cell_first_char(const CPUCell *c, const TextCache *tc) { if (c->ch_is_idx) { if (c->is_multicell && (c->x || c->y)) return 0; return tc_first_char_at_index(tc, c->ch_or_idx); } return c->ch_or_idx; } static inline CellAttrs cursor_to_attrs(const Cursor *c) { CellAttrs ans = { .decoration=c->decoration, .bold=c->bold, .italic=c->italic, .reverse=c->reverse, .strike=c->strikethrough, .dim=c->dim}; return ans; } static inline void attrs_to_cursor(const CellAttrs attrs, Cursor *c) { c->decoration = attrs.decoration; c->bold = attrs.bold; c->italic = attrs.italic; c->reverse = attrs.reverse; c->strikethrough = attrs.strike; c->dim = attrs.dim; } #define cursor_as_gpu_cell(cursor) {.attrs=cursor_to_attrs(cursor), .fg=(cursor->fg & COL_MASK), .bg=(cursor->bg & COL_MASK), .decoration_fg=cursor->decoration_fg & COL_MASK}