Fix color render path for non-emoji codepoints on color-capable faces

render_group() used has_emoji_presentation() for all codepoints, but that
only makes sense for emoji (VS15/VS16 selectors). Non-emoji codepoints now
use face_has_color() instead, and *was_colored is set from actual render
output rather than preset before rendering.
This commit is contained in:
kyzabuilds 2026-06-25 20:06:52 -04:00
parent 4483a6c110
commit 2f93de3d68
4 changed files with 26 additions and 6 deletions

View file

@ -522,6 +522,11 @@ face_apply_scaling(PyObject *f, const FONTS_DATA_HANDLE fg) {
return set_size_for_face(f, 0, false, fg);
}
bool
face_has_color(PyObject *f) {
return CTFontSupportsColorGlyphs(((CTFace*)f)->ct_font);
}
static PyObject*
set_size(CTFace *self, PyObject *args) {
double font_sz_in_pts, dpi_x, dpi_y;

View file

@ -1200,7 +1200,13 @@ render_group(
is_only_filled_boxes = true;
for (unsigned i = 1; i < num_glyphs && is_only_filled_boxes; i++) if (global_glyph_render_scratch.glyphs[i] != box_glyph_id) is_only_filled_boxes = false;
}
bool was_colored = !is_only_filled_boxes && has_emoji_presentation(cpu_cells, global_glyph_render_scratch.lc);
const ListOfChars *lc = global_glyph_render_scratch.lc;
// For emoji, honor the text/emoji presentation selectors (VS15/VS16); for a
// non-emoji codepoint on a color-capable face (e.g. an icon font mapped via
// symbol_map), use the color render path.
bool first_is_emoji = lc->count && char_props_for(lc->chars[0]).is_emoji;
bool was_colored = !is_only_filled_boxes && (
first_is_emoji ? has_emoji_presentation(cpu_cells, lc) : face_has_color(font->face));
GlyphRenderInfo ri = {0};
pixel *canvas = rendering_in_smaller_area && canvas_width != scaled_canvas_width ? scratch : fg->canvas.buf;
if (is_only_filled_boxes) { // special case rendering of █ for tests

View file

@ -62,6 +62,7 @@ typedef void (*free_extra_data_func)(void*);
StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline);
StringCanvas render_simple_text(FONTS_DATA_HANDLE fg_, const char *text);
bool face_apply_scaling(PyObject*face, const FONTS_DATA_HANDLE fg);
bool face_has_color(PyObject *face);
bool
add_font_name_record(PyObject *table, uint16_t platform_id, uint16_t encoding_id, uint16_t language_id, uint16_t name_id, const char *string, uint16_t string_len);

View file

@ -246,6 +246,11 @@ face_apply_scaling(PyObject *f, const FONTS_DATA_HANDLE fg) {
return false;
}
bool
face_has_color(PyObject *f) {
return ((Face*)f)->has_color;
}
static bool
init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, long index, FONTS_DATA_HANDLE fg) {
copy_face_metrics(self);
@ -964,24 +969,27 @@ static const ProcessedBitmap EMPTY_PBM = {.factor = 1};
bool
render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, GlyphRenderInfo *ri) {
Face *self = (Face*)f;
bool is_emoji = *was_colored; *was_colored = is_emoji && self->has_color;
// *was_colored is an incoming hint; it is set on output to reflect whether
// any glyph was actually rendered as color.
bool want_color = *was_colored && self->has_color;
*was_colored = false;
float x = 0.f, y = 0.f;
ProcessedBitmap bm;
unsigned int canvas_width = cell_width * num_cells;
GlyphColorType colored;
for (unsigned int i = 0; i < num_glyphs; i++) {
bm = EMPTY_PBM;
bool colored_glyph = false;
// dont load the space glyph since loading it fails for some fonts/sizes and it is anyway to be rendered as a blank
if (info[i].codepoint != self->space_glyph_id) {
if (*was_colored && (colored = glyph_color_type(self, info[i].codepoint)) != NOT_COLORED) {
if (want_color && (colored = glyph_color_type(self, info[i].codepoint)) != NOT_COLORED) {
if (!render_color_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, baseline)) {
if (PyErr_Occurred()) PyErr_Print();
if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) {
free_processed_bitmap(&bm);
return false;
}
*was_colored = false;
}
} else { colored_glyph = true; *was_colored = true; }
} else {
if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) {
free_processed_bitmap(&bm);
@ -992,7 +1000,7 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
float x_offset = x + (float)positions[i].x_offset / 64.0f;
y = (float)positions[i].y_offset / 64.0f;
if (debug_placement) printf("%d: x=%f canvas: %u", i, x_offset, canvas_width);
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) {
if ((colored_glyph || self->face->glyph->metrics.width > 0) && bm.width > 0) {
place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline, i, 0xffffff, 0, 0);
}
if (debug_placement) printf(" adv: %f\n", (float)positions[i].x_advance / 64.0f);