mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-04-13 08:21:03 -04:00
Add font ligature rendering support
Add tweak.ligatures option (default: no) for OpenType liga/calt rendering with programming fonts. Ligatures are render-only — the grid and copy/paste are unchanged. Uses fcft_rasterize_shaped_run() to shape full runs with HarfBuzz and render each glyph by its shaped glyph ID, correctly handling contextual alternates and ligatures. Block cursor over a ligature uses a pixman gradient as composite source for single-pass correct-color rendering.
This commit is contained in:
parent
c291194a4e
commit
ade745f303
4 changed files with 599 additions and 19 deletions
13
config.c
13
config.c
|
|
@ -2832,6 +2832,18 @@ parse_section_tweak(struct context *ctx)
|
|||
return true;
|
||||
}
|
||||
|
||||
else if (streq(key, "ligatures")) {
|
||||
if (!value_to_bool(ctx, &conf->tweak.ligatures))
|
||||
return false;
|
||||
if (conf->tweak.ligatures && !conf->can_shape_grapheme) {
|
||||
LOG_WARN(
|
||||
"fcft lacks grapheme shaping support; "
|
||||
"ligatures disabled");
|
||||
conf->tweak.ligatures = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
else if (streq(key, "grapheme-width-method")) {
|
||||
_Static_assert(sizeof(conf->tweak.grapheme_width_method) == sizeof(int),
|
||||
"enum is not 32-bit");
|
||||
|
|
@ -3578,6 +3590,7 @@ config_load(struct config *conf, const char *conf_path,
|
|||
#if defined(FOOT_GRAPHEME_CLUSTERING) && FOOT_GRAPHEME_CLUSTERING
|
||||
.grapheme_shaping = fcft_caps & FCFT_CAPABILITY_GRAPHEME_SHAPING,
|
||||
#endif
|
||||
.ligatures = false,
|
||||
.grapheme_width_method = GRAPHEME_WIDTH_DOUBLE,
|
||||
.delayed_render_lower_ns = 500000, /* 0.5ms */
|
||||
.delayed_render_upper_ns = 16666666 / 2, /* half a frame period (60Hz) */
|
||||
|
|
|
|||
1
config.h
1
config.h
|
|
@ -423,6 +423,7 @@ struct config {
|
|||
enum fcft_scaling_filter fcft_filter;
|
||||
bool overflowing_glyphs;
|
||||
bool grapheme_shaping;
|
||||
bool ligatures;
|
||||
enum {
|
||||
GRAPHEME_WIDTH_WCSWIDTH,
|
||||
GRAPHEME_WIDTH_DOUBLE,
|
||||
|
|
|
|||
|
|
@ -1964,6 +1964,21 @@ any of these options.
|
|||
|
||||
Default: _yes_
|
||||
|
||||
*ligatures*
|
||||
Boolean. When enabled, foot renders font ligatures —
|
||||
multi-character sequences like ->, =>, !=, <= that
|
||||
programming fonts (Fira Code, JetBrains Mono, Cascadia Code,
|
||||
etc.) display as combined glyphs.
|
||||
|
||||
Ligatures are purely visual — copy/paste always preserves
|
||||
the original individual characters.
|
||||
|
||||
Requires a font with ligature tables and fcft compiled with
|
||||
HarfBuzz support. Has no visible effect with fonts that do
|
||||
not define ligatures.
|
||||
|
||||
Default: _no_
|
||||
|
||||
*grapheme-width-method*
|
||||
Selects which method to use when calculating the width
|
||||
(i.e. number of columns) of a grapheme cluster. One of
|
||||
|
|
|
|||
589
render.c
589
render.c
|
|
@ -684,23 +684,12 @@ draw_cursor(const struct terminal *term, const struct cell *cell,
|
|||
}
|
||||
}
|
||||
|
||||
static int
|
||||
render_cell(struct terminal *term, pixman_image_t *pix,
|
||||
pixman_region32_t *damage, struct row *row, int row_no, int col,
|
||||
bool has_cursor)
|
||||
static void
|
||||
resolve_colors(const struct terminal *term,
|
||||
const struct cell *cell,
|
||||
uint32_t *out_fg, uint32_t *out_bg,
|
||||
uint16_t *out_alpha)
|
||||
{
|
||||
struct cell *cell = &row->cells[col];
|
||||
if (cell->attrs.clean)
|
||||
return 0;
|
||||
|
||||
cell->attrs.clean = 1;
|
||||
cell->attrs.confined = true;
|
||||
|
||||
int width = term->cell_width;
|
||||
int height = term->cell_height;
|
||||
const int x = term->margins.left + col * width;
|
||||
const int y = term->margins.top + row_no * height;
|
||||
|
||||
uint32_t _fg = 0;
|
||||
uint32_t _bg = 0;
|
||||
|
||||
|
|
@ -845,6 +834,40 @@ render_cell(struct terminal *term, pixman_image_t *pix,
|
|||
if (cell->attrs.blink && term->blink.state == BLINK_OFF)
|
||||
_fg = color_blend_towards(_fg, 0x00000000, term->conf->dim.amount);
|
||||
|
||||
*out_fg = _fg;
|
||||
*out_bg = _bg;
|
||||
*out_alpha = alpha;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_pua_codepoint(char32_t wc)
|
||||
{
|
||||
return (wc >= 0xE000 && wc <= 0xF8FF) || /* BMP PUA */
|
||||
(wc >= 0xF0000 && wc <= 0xFFFFF) || /* Supplementary PUA-A */
|
||||
(wc >= 0x100000 && wc <= 0x10FFFD); /* Supplementary PUA-B */
|
||||
}
|
||||
|
||||
static int
|
||||
render_cell(struct terminal *term, pixman_image_t *pix,
|
||||
pixman_region32_t *damage, struct row *row, int row_no, int col,
|
||||
bool has_cursor)
|
||||
{
|
||||
struct cell *cell = &row->cells[col];
|
||||
if (cell->attrs.clean)
|
||||
return 0;
|
||||
|
||||
cell->attrs.clean = 1;
|
||||
cell->attrs.confined = true;
|
||||
|
||||
int width = term->cell_width;
|
||||
int height = term->cell_height;
|
||||
const int x = term->margins.left + col * width;
|
||||
const int y = term->margins.top + row_no * height;
|
||||
|
||||
uint32_t _fg, _bg;
|
||||
uint16_t alpha;
|
||||
resolve_colors(term, cell, &_fg, &_bg, &alpha);
|
||||
|
||||
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
|
||||
pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct);
|
||||
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct);
|
||||
|
|
@ -1038,7 +1061,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
|
|||
}
|
||||
|
||||
if (cell->wc == 0 || cell->wc >= CELL_SPACER || cell->wc == U'\t' ||
|
||||
(unlikely(cell->attrs.conceal) && !is_selected))
|
||||
(unlikely(cell->attrs.conceal) && !cell->attrs.selected))
|
||||
{
|
||||
goto draw_cursor;
|
||||
}
|
||||
|
|
@ -1189,16 +1212,544 @@ draw_cursor:
|
|||
}
|
||||
|
||||
pixman_image_set_clip_region32(pix, NULL);
|
||||
for (int i = 1; i < cell_cols; i++) {
|
||||
row->cells[col + i].attrs.clean = 1;
|
||||
row->cells[col + i].attrs.confined = true;
|
||||
}
|
||||
return cell_cols;
|
||||
}
|
||||
|
||||
static bool
|
||||
cell_eligible_for_ligature(const struct cell *cell,
|
||||
const struct cell *next)
|
||||
{
|
||||
char32_t wc = cell->wc;
|
||||
if (wc == 0 || wc == U' ' || wc == U'\t')
|
||||
return false;
|
||||
if (wc >= CELL_COMB_CHARS_LO)
|
||||
return false;
|
||||
/* Skip wide characters (next cell is a spacer) */
|
||||
if (next != NULL && next->wc >= CELL_SPACER)
|
||||
return false;
|
||||
if ((wc >= GLYPH_BOX_DRAWING_FIRST &&
|
||||
wc <= GLYPH_BOX_DRAWING_LAST) ||
|
||||
(wc >= GLYPH_BRAILLE_FIRST &&
|
||||
wc <= GLYPH_BRAILLE_LAST) ||
|
||||
(wc >= GLYPH_LEGACY_FIRST &&
|
||||
wc <= GLYPH_LEGACY_LAST) ||
|
||||
(wc >= GLYPH_OCTANTS_FIRST &&
|
||||
wc <= GLYPH_OCTANTS_LAST))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/* Private Use Areas — NerdFont icons, custom symbols */
|
||||
if (is_pua_codepoint(wc))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
attrs_run_compatible(const struct attributes *a,
|
||||
const struct attributes *b)
|
||||
{
|
||||
return a->bold == b->bold
|
||||
&& a->italic == b->italic
|
||||
&& a->fg_src == b->fg_src && a->fg == b->fg
|
||||
&& a->bg_src == b->bg_src && a->bg == b->bg
|
||||
&& a->reverse == b->reverse
|
||||
&& a->dim == b->dim
|
||||
&& a->blink == b->blink
|
||||
&& a->selected == b->selected
|
||||
&& a->conceal == b->conceal
|
||||
&& a->strikethrough == b->strikethrough
|
||||
&& a->underline == b->underline
|
||||
&& a->url == b->url;
|
||||
}
|
||||
|
||||
static inline void
|
||||
composite_glyph(pixman_image_t *src_pix,
|
||||
const struct fcft_glyph *glyph,
|
||||
pixman_image_t *pix,
|
||||
int dst_x, int dst_y, int x_origin,
|
||||
bool blink_off)
|
||||
{
|
||||
if (unlikely(glyph->is_color_glyph)) {
|
||||
if (unlikely(blink_off))
|
||||
return;
|
||||
pixman_image_composite32(
|
||||
PIXMAN_OP_OVER,
|
||||
glyph->pix, NULL, pix,
|
||||
0, 0, 0, 0,
|
||||
dst_x, dst_y,
|
||||
glyph->width, glyph->height);
|
||||
} else {
|
||||
pixman_image_composite32(
|
||||
PIXMAN_OP_OVER,
|
||||
src_pix, glyph->pix, pix,
|
||||
dst_x - x_origin, 0, 0, 0,
|
||||
dst_x, dst_y,
|
||||
glyph->width, glyph->height);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
render_ligature_run(struct terminal *term,
|
||||
pixman_image_t *pix,
|
||||
pixman_region32_t *damage,
|
||||
struct row *row, int row_no,
|
||||
int run_start, int run_len,
|
||||
int cursor_col)
|
||||
{
|
||||
const int width = term->cell_width;
|
||||
const int height = term->cell_height;
|
||||
const int x = term->margins.left + run_start * width;
|
||||
const int y = term->margins.top + row_no * height;
|
||||
const int run_width = run_len * width;
|
||||
|
||||
struct cell *first_cell = &row->cells[run_start];
|
||||
|
||||
/* Resolve colors from the first cell (all cells
|
||||
* in the run share the same visual attributes) */
|
||||
uint32_t _fg, _bg;
|
||||
uint16_t alpha;
|
||||
resolve_colors(term, first_cell, &_fg, &_bg, &alpha);
|
||||
|
||||
const bool gamma_correct =
|
||||
wayl_do_linear_blending(term->wl, term->conf);
|
||||
|
||||
struct fcft_font *font =
|
||||
attrs_to_font(term, &first_cell->attrs);
|
||||
|
||||
const bool has_cursor =
|
||||
cursor_col >= run_start &&
|
||||
cursor_col < run_start + run_len;
|
||||
const bool is_block_cursor =
|
||||
has_cursor &&
|
||||
term->cursor_style == CURSOR_BLOCK &&
|
||||
term->kbd_focus;
|
||||
|
||||
/* Mark all cells clean */
|
||||
for (int i = 0; i < run_len; i++) {
|
||||
row->cells[run_start + i].attrs.clean = 1;
|
||||
row->cells[run_start + i].attrs.confined = true;
|
||||
}
|
||||
|
||||
/* Clip to run rectangle */
|
||||
pixman_region32_t clip;
|
||||
pixman_region32_init_rect(
|
||||
&clip, x, y, run_width, height);
|
||||
pixman_image_set_clip_region32(pix, &clip);
|
||||
|
||||
if (damage != NULL) {
|
||||
pixman_region32_union_rect(
|
||||
damage, damage, x, y, run_width, height);
|
||||
}
|
||||
|
||||
pixman_region32_fini(&clip);
|
||||
|
||||
/* Background */
|
||||
pixman_color_t bg =
|
||||
color_hex_to_pixman_with_alpha(
|
||||
_bg, alpha, gamma_correct);
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_SRC, pix, &bg, 1,
|
||||
&(pixman_rectangle16_t){
|
||||
x, y, run_width, height});
|
||||
|
||||
/* Block cursor colors */
|
||||
pixman_color_t cursor_color = {0};
|
||||
pixman_color_t text_color = {0};
|
||||
if (is_block_cursor) {
|
||||
struct cell *cursor_cell =
|
||||
&row->cells[cursor_col];
|
||||
pixman_color_t fg_pix =
|
||||
color_hex_to_pixman(_fg, gamma_correct);
|
||||
const pixman_color_t bg_pix =
|
||||
color_hex_to_pixman(_bg, gamma_correct);
|
||||
cursor_colors_for_cell(
|
||||
term, cursor_cell, &fg_pix, &bg_pix,
|
||||
&cursor_color, &text_color, gamma_correct);
|
||||
|
||||
if (likely(
|
||||
term->cursor_blink.state ==
|
||||
CURSOR_BLINK_ON))
|
||||
{
|
||||
int cx = term->margins.left +
|
||||
cursor_col * width;
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_SRC, pix, &cursor_color, 1,
|
||||
&(pixman_rectangle16_t){
|
||||
cx, y, width, height});
|
||||
}
|
||||
}
|
||||
|
||||
/* Skip glyph rendering if concealed */
|
||||
if (unlikely(first_cell->attrs.conceal) &&
|
||||
!first_cell->attrs.selected)
|
||||
{
|
||||
goto cursor_overlay;
|
||||
}
|
||||
|
||||
/* Blink handling */
|
||||
const bool blink_off =
|
||||
first_cell->attrs.blink &&
|
||||
term->blink.state == BLINK_OFF;
|
||||
if (first_cell->attrs.blink && term->blink.fd < 0) {
|
||||
mtx_lock(&term->render.workers.lock);
|
||||
term_arm_blink_timer(term);
|
||||
mtx_unlock(&term->render.workers.lock);
|
||||
}
|
||||
|
||||
{
|
||||
pixman_color_t fg =
|
||||
color_hex_to_pixman(_fg, gamma_correct);
|
||||
|
||||
const bool cursor_visible =
|
||||
is_block_cursor &&
|
||||
likely(term->cursor_blink.state ==
|
||||
CURSOR_BLINK_ON);
|
||||
|
||||
pixman_image_t *src_pix;
|
||||
if (cursor_visible) {
|
||||
/*
|
||||
* Linear gradient with hard color stops:
|
||||
* fg everywhere, text_color at cursor cell.
|
||||
* No pixel buffer — just 4 stop parameters.
|
||||
* PAD repeat clamps out-of-range coords to
|
||||
* edge colors (avoids fixed-point rounding
|
||||
* producing transparent pixels).
|
||||
*/
|
||||
xassert(run_width > 0);
|
||||
int cursor_px =
|
||||
(cursor_col - run_start) * width;
|
||||
pixman_fixed_t frac_start =
|
||||
pixman_double_to_fixed(
|
||||
(double)cursor_px / run_width);
|
||||
pixman_fixed_t frac_end =
|
||||
pixman_double_to_fixed(
|
||||
(double)(cursor_px + width)
|
||||
/ run_width);
|
||||
pixman_gradient_stop_t stops[4] = {
|
||||
{ frac_start, fg },
|
||||
{ frac_start, text_color },
|
||||
{ frac_end, text_color },
|
||||
{ frac_end, fg },
|
||||
};
|
||||
pixman_point_fixed_t p1 = { 0, 0 };
|
||||
pixman_point_fixed_t p2 = {
|
||||
pixman_int_to_fixed(run_width), 0 };
|
||||
src_pix =
|
||||
pixman_image_create_linear_gradient(
|
||||
&p1, &p2, stops, 4);
|
||||
pixman_image_set_repeat(
|
||||
src_pix, PIXMAN_REPEAT_PAD);
|
||||
} else {
|
||||
src_pix =
|
||||
pixman_image_create_solid_fill(&fg);
|
||||
}
|
||||
|
||||
/* VLA; bounded by term->cols (typically ≤ 200) */
|
||||
uint32_t codepoints[run_len];
|
||||
for (int i = 0; i < run_len; i++)
|
||||
codepoints[i] =
|
||||
row->cells[run_start + i].wc;
|
||||
|
||||
int pen_x = x + term->font_x_ofs;
|
||||
|
||||
/*
|
||||
* Shape the full run and get
|
||||
* individually-cached glyphs.
|
||||
*/
|
||||
const struct fcft_glyph *glyphs[run_len];
|
||||
fcft_rasterize_shaped_run(
|
||||
font, run_len, codepoints,
|
||||
term->font_subpixel, glyphs);
|
||||
|
||||
for (int i = 0; i < run_len; i++) {
|
||||
const struct fcft_glyph *gl =
|
||||
glyphs[i];
|
||||
|
||||
if (gl != NULL) {
|
||||
int dst_x = pen_x + gl->x;
|
||||
int dst_y =
|
||||
y + term->font_baseline
|
||||
- gl->y;
|
||||
composite_glyph(
|
||||
src_pix, gl, pix,
|
||||
dst_x, dst_y, x,
|
||||
blink_off);
|
||||
}
|
||||
|
||||
pen_x = x + term->font_x_ofs
|
||||
+ (i + 1) * width;
|
||||
}
|
||||
|
||||
pixman_image_unref(src_pix);
|
||||
|
||||
}
|
||||
|
||||
/* Per-cell decorations */
|
||||
{
|
||||
pixman_color_t fg =
|
||||
color_hex_to_pixman(_fg, gamma_correct);
|
||||
|
||||
for (int i = 0; i < run_len; i++) {
|
||||
const int col = run_start + i;
|
||||
const struct cell *cell =
|
||||
&row->cells[col];
|
||||
const int cx =
|
||||
term->margins.left + col * width;
|
||||
|
||||
if (cell->attrs.underline) {
|
||||
pixman_color_t ul_color = fg;
|
||||
enum underline_style ul_style =
|
||||
UNDERLINE_SINGLE;
|
||||
|
||||
if (row->extra != NULL) {
|
||||
for (int j = 0;
|
||||
j < row->extra->
|
||||
underline_ranges.count;
|
||||
j++)
|
||||
{
|
||||
const struct row_range *range =
|
||||
&row->extra->
|
||||
underline_ranges.v[j];
|
||||
|
||||
if (range->start > col)
|
||||
break;
|
||||
|
||||
if (range->start <= col &&
|
||||
col <= range->end)
|
||||
{
|
||||
switch (
|
||||
range->underline
|
||||
.color_src)
|
||||
{
|
||||
case COLOR_BASE256:
|
||||
ul_color =
|
||||
color_hex_to_pixman(
|
||||
term->colors
|
||||
.table[
|
||||
range->
|
||||
underline
|
||||
.color],
|
||||
gamma_correct);
|
||||
break;
|
||||
|
||||
case COLOR_RGB:
|
||||
ul_color =
|
||||
color_hex_to_pixman(
|
||||
range->underline
|
||||
.color,
|
||||
gamma_correct);
|
||||
break;
|
||||
|
||||
case COLOR_DEFAULT:
|
||||
break;
|
||||
|
||||
case COLOR_BASE16:
|
||||
BUG("underline color "
|
||||
"can't be base-16");
|
||||
break;
|
||||
}
|
||||
|
||||
ul_style =
|
||||
range->underline.style;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw_styled_underline(
|
||||
term, pix, font, &ul_color,
|
||||
ul_style, cx, y, 1);
|
||||
}
|
||||
|
||||
if (cell->attrs.strikethrough) {
|
||||
draw_strikeout(
|
||||
term, pix, font, &fg,
|
||||
cx, y, 1);
|
||||
}
|
||||
|
||||
if (unlikely(cell->attrs.url)) {
|
||||
pixman_color_t url_color =
|
||||
color_hex_to_pixman(
|
||||
term->conf->colors_dark
|
||||
.use_custom.url
|
||||
? term->conf->colors_dark
|
||||
.url
|
||||
: term->colors.table[3],
|
||||
gamma_correct);
|
||||
draw_underline(
|
||||
term, pix, font, &url_color,
|
||||
cx, y, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor_overlay:
|
||||
/* Non-block cursors: draw overlay on top */
|
||||
if (has_cursor &&
|
||||
(term->cursor_style != CURSOR_BLOCK ||
|
||||
!term->kbd_focus))
|
||||
{
|
||||
struct cell *cursor_cell =
|
||||
&row->cells[cursor_col];
|
||||
pixman_color_t fg_pix =
|
||||
color_hex_to_pixman(_fg, gamma_correct);
|
||||
const pixman_color_t bg_pix =
|
||||
color_hex_to_pixman(_bg, gamma_correct);
|
||||
int cx = term->margins.left +
|
||||
cursor_col * width;
|
||||
draw_cursor(
|
||||
term, cursor_cell, font, pix,
|
||||
&fg_pix, &bg_pix, cx, y, 1);
|
||||
}
|
||||
|
||||
pixman_image_set_clip_region32(pix, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
render_row_ligatures(struct terminal *term,
|
||||
pixman_image_t *pix,
|
||||
pixman_region32_t *damage,
|
||||
struct row *row, int row_no,
|
||||
int cursor_col)
|
||||
{
|
||||
/*
|
||||
* Dirty-propagate spacer cells: if a spacer (right
|
||||
* half of a wide char) is dirty, the wide char cell
|
||||
* must be re-rendered too — left-to-right rendering
|
||||
* can't rely on overpainting like right-to-left does.
|
||||
*/
|
||||
for (int c = 1; c < term->cols; c++) {
|
||||
if (!row->cells[c].attrs.clean &&
|
||||
row->cells[c].wc >= CELL_SPACER &&
|
||||
row->cells[c - 1].attrs.clean)
|
||||
{
|
||||
row->cells[c - 1].attrs.clean = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Dirty-propagate ligature breakage: if a dirty cell
|
||||
* is adjacent to a clean, ligature-eligible cell, that
|
||||
* neighbor must be re-rendered — its pixel area may
|
||||
* still show a stale ligature glyph from the previous
|
||||
* frame. Dirtying one cell per neighboring run is
|
||||
* enough: the run loop below re-renders the entire run
|
||||
* when any member is dirty.
|
||||
*/
|
||||
for (int c = 0; c < term->cols; c++) {
|
||||
if (row->cells[c].attrs.clean)
|
||||
continue;
|
||||
|
||||
if (c > 0
|
||||
&& row->cells[c - 1].attrs.clean
|
||||
&& cell_eligible_for_ligature(
|
||||
&row->cells[c - 1],
|
||||
&row->cells[c]))
|
||||
{
|
||||
row->cells[c - 1].attrs.clean = 0;
|
||||
}
|
||||
|
||||
if (c + 1 < term->cols
|
||||
&& row->cells[c + 1].attrs.clean
|
||||
&& cell_eligible_for_ligature(
|
||||
&row->cells[c + 1],
|
||||
c + 2 < term->cols
|
||||
? &row->cells[c + 2] : NULL))
|
||||
{
|
||||
row->cells[c + 1].attrs.clean = 0;
|
||||
c++; /* skip to prevent rightward cascade */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Ligature runs are identified and rendered left-to-right (pass 1).
|
||||
* Individual cells — those ineligible for ligature shaping and
|
||||
* single-cell runs — are collected and rendered right-to-left
|
||||
* (pass 2), matching the non-ligature render_row path so that an
|
||||
* overflowing glyph is composited last and not overwritten by the
|
||||
* background fill of the following cell.
|
||||
*/
|
||||
|
||||
/* VLA bounded by term->cols; collect individual cell columns. */
|
||||
int singles[term->cols];
|
||||
int n_singles = 0;
|
||||
|
||||
int col = 0;
|
||||
while (col < term->cols) {
|
||||
struct cell *cell = &row->cells[col];
|
||||
|
||||
const struct cell *next =
|
||||
col + 1 < term->cols
|
||||
? &row->cells[col + 1] : NULL;
|
||||
|
||||
if (!cell_eligible_for_ligature(cell, next)) {
|
||||
singles[n_singles++] = col;
|
||||
col++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int run_start = col;
|
||||
col++;
|
||||
|
||||
while (col < term->cols &&
|
||||
cell_eligible_for_ligature(
|
||||
&row->cells[col],
|
||||
col + 1 < term->cols
|
||||
? &row->cells[col + 1] : NULL) &&
|
||||
attrs_run_compatible(
|
||||
&row->cells[run_start].attrs,
|
||||
&row->cells[col].attrs))
|
||||
{
|
||||
col++;
|
||||
}
|
||||
|
||||
int run_len = col - run_start;
|
||||
if (run_len == 1) {
|
||||
singles[n_singles++] = run_start;
|
||||
} else {
|
||||
/* Check if any cell in the run is dirty */
|
||||
bool any_dirty = false;
|
||||
for (int i = 0; i < run_len; i++) {
|
||||
if (!row->cells[run_start + i]
|
||||
.attrs.clean)
|
||||
{
|
||||
any_dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (any_dirty) {
|
||||
render_ligature_run(
|
||||
term, pix, damage, row,
|
||||
row_no, run_start, run_len,
|
||||
cursor_col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Pass 2: individual cells, right-to-left */
|
||||
for (int i = n_singles - 1; i >= 0; i--)
|
||||
render_cell(term, pix, damage, row, row_no,
|
||||
singles[i], singles[i] == cursor_col);
|
||||
}
|
||||
|
||||
static void
|
||||
render_row(struct terminal *term, pixman_image_t *pix,
|
||||
pixman_region32_t *damage, struct row *row,
|
||||
int row_no, int cursor_col)
|
||||
{
|
||||
for (int col = term->cols - 1; col >= 0; col--)
|
||||
render_cell(term, pix, damage, row, row_no, col, cursor_col == col);
|
||||
if (term->conf->tweak.ligatures) {
|
||||
render_row_ligatures(
|
||||
term, pix, damage, row,
|
||||
row_no, cursor_col);
|
||||
} else {
|
||||
for (int col = term->cols - 1; col >= 0; col--)
|
||||
render_cell(term, pix, damage, row, row_no, col, cursor_col == col);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue