Merge branch 'svg-color-glyphs' into 'master'

Draft: Add support for color glyphs to SVG backend

See merge request cairo/cairo!562
This commit is contained in:
Raman Varabets 2026-05-06 04:42:48 +00:00
commit 72f76208ed
13 changed files with 226 additions and 23 deletions

View file

@ -49,4 +49,3 @@ text-rotate
text-unhinted-metrics
tighten-bounds
unbounded-operator
user-font-color

View file

@ -28,7 +28,6 @@ filter-bilinear-extents
filter-nearest-offset
filter-nearest-transformed
finer-grained-fallbacks
ft-color-font
ft-show-glyphs-positioning
ft-text-vertical-layout-type1
ft-text-vertical-layout-type3
@ -106,4 +105,3 @@ tighten-bounds
unbounded-operator
xcb-surface-source
xlib-surface-source
user-font-color

View file

@ -45,6 +45,13 @@
#include "cairo-surface-private.h"
typedef struct _cairo_svg_color_glyph {
cairo_hash_entry_t base;
cairo_scaled_font_t *scaled_font;
unsigned long glyph_index;
cairo_bool_t supported;
} cairo_svg_color_glyph_t;
struct _cairo_svg_surface_start {
cairo_surface_t base;

View file

@ -117,6 +117,25 @@ _cairo_svg_source_surface_equal (const void *key_a, const void *key_b)
return a->id == b->id;
}
static cairo_bool_t
_cairo_svg_color_glyph_equal (const void *key_a, const void *key_b)
{
const cairo_svg_color_glyph_t *a = key_a;
const cairo_svg_color_glyph_t *b = key_b;
if (a->scaled_font != b->scaled_font)
return FALSE;
return (a->glyph_index == b->glyph_index);
}
static void
_cairo_svg_color_glyph_init_key (cairo_svg_color_glyph_t *key)
{
key->base.hash = _cairo_hash_uintptr (_CAIRO_HASH_INIT_VALUE, (uintptr_t)key->scaled_font);
key->base.hash = _cairo_hash_uintptr (key->base.hash, key->glyph_index);
}
static void
_cairo_svg_source_surface_pluck (void *entry, void *closure)
{
@ -128,6 +147,18 @@ _cairo_svg_source_surface_pluck (void *entry, void *closure)
free (source_surface);
}
static void
_cairo_svg_color_glyph_pluck (void *entry, void *closure)
{
cairo_svg_color_glyph_t *glyph_entry = entry;
cairo_hash_table_t *patterns = closure;
_cairo_hash_table_remove (patterns, &glyph_entry->base);
cairo_scaled_font_destroy (glyph_entry->scaled_font);
free (glyph_entry);
}
static void
_cairo_svg_paint_init_key (cairo_svg_paint_t *paint)
{
@ -526,6 +557,8 @@ typedef struct _cairo_svg_surface {
cairo_svg_document_t *document;
cairo_hash_table_t *color_glyphs;
cairo_svg_stream_t xml_node;
cairo_array_t page_set;
@ -1080,13 +1113,19 @@ _cairo_svg_surface_create_for_document (cairo_svg_document_t *document,
surface->document = _cairo_svg_document_reference (document);
surface->color_glyphs = _cairo_hash_table_create (_cairo_svg_color_glyph_equal);
if (unlikely (surface->color_glyphs == NULL)) {
status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
goto CLEANUP1;
}
surface->xml_node = _cairo_svg_stream_create ();
_cairo_array_init (&surface->page_set, sizeof (cairo_svg_page_t));
surface->source_surfaces = _cairo_hash_table_create (_cairo_svg_source_surface_equal);
if (unlikely (surface->source_surfaces == NULL)) {
status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
goto CLEANUP;
goto CLEANUP2;
}
_cairo_surface_clipper_init (&surface->clipper, _cairo_svg_surface_clipper_intersect_clip_path);
@ -1110,7 +1149,9 @@ _cairo_svg_surface_create_for_document (cairo_svg_document_t *document,
}
/* ignore status as we are on the error path */
CLEANUP:
CLEANUP2:
(void) _cairo_hash_table_destroy (surface->color_glyphs);
CLEANUP1:
(void) _cairo_svg_stream_destroy (&surface->xml_node);
(void) _cairo_svg_document_destroy (document);
@ -1346,6 +1387,59 @@ _cairo_svg_document_emit_outline_glyph_data (cairo_svg_document_t *document,
return status;
}
static cairo_int_status_t
_cairo_svg_document_emit_color_glyph_data (cairo_svg_document_t *document,
cairo_scaled_font_t *scaled_font,
unsigned long glyph_index)
{
cairo_status_t status;
cairo_scaled_glyph_t *scaled_glyph;
status = _cairo_scaled_glyph_lookup (scaled_font,
glyph_index,
CAIRO_SCALED_GLYPH_INFO_METRICS | CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE,
NULL, /* foreground color */
&scaled_glyph);
if (unlikely (status)) {
return status;
}
// User fonts always have a recording surface, but they may not be color glyphs.
if (scaled_glyph->color_glyph_set && !scaled_glyph->color_glyph) {
return CAIRO_INT_STATUS_UNSUPPORTED;
}
cairo_surface_t *paginated_surface = _cairo_svg_surface_create_for_document (document,
CAIRO_CONTENT_COLOR_ALPHA,
0,
0,
FALSE);
cairo_svg_surface_t *svg_surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (paginated_surface);
status = paginated_surface->status;
if (unlikely (status)) {
goto cleanup;
}
cairo_surface_set_fallback_resolution (paginated_surface,
document->owner->x_fallback_resolution,
document->owner->y_fallback_resolution);
cairo_pattern_t *pattern = cairo_pattern_create_for_surface (scaled_glyph->recording_surface);
_cairo_svg_surface_emit_composite_pattern (&document->xml_node_glyphs,
svg_surface,
(cairo_surface_pattern_t *) pattern,
invalid_pattern_id,
NULL);
cairo_pattern_destroy (pattern);
cleanup:
if (status == CAIRO_STATUS_SUCCESS) {
status = cairo_surface_status (paginated_surface);
}
cairo_surface_destroy (paginated_surface);
return status;
}
static cairo_int_status_t
_cairo_svg_document_emit_bitmap_glyph_data (cairo_svg_document_t *document,
cairo_scaled_font_t *scaled_font,
@ -1483,7 +1577,7 @@ _cairo_svg_document_emit_bitmap_glyph_data (cairo_svg_document_t *document,
goto cleanup;
}
cleanup:
cleanup:
if (status == CAIRO_STATUS_SUCCESS) {
status = cairo_surface_status (paginated_surface);
}
@ -1510,13 +1604,19 @@ _cairo_svg_document_emit_glyph (cairo_svg_document_t *document,
font_id,
subset_glyph_index);
status = _cairo_svg_document_emit_outline_glyph_data (document,
scaled_font,
scaled_font_glyph_index);
if (status == CAIRO_INT_STATUS_UNSUPPORTED)
status = _cairo_svg_document_emit_bitmap_glyph_data (document,
scaled_font,
scaled_font_glyph_index);
status = _cairo_svg_document_emit_color_glyph_data (document,
scaled_font,
scaled_font_glyph_index);
if (status == CAIRO_INT_STATUS_UNSUPPORTED) {
status = _cairo_svg_document_emit_outline_glyph_data (document,
scaled_font,
scaled_font_glyph_index);
if (status == CAIRO_INT_STATUS_UNSUPPORTED) {
status = _cairo_svg_document_emit_bitmap_glyph_data (document,
scaled_font,
scaled_font_glyph_index);
}
}
if (unlikely (status))
return status;
@ -1660,6 +1760,11 @@ _cairo_svg_surface_finish (void *abstract_surface)
_cairo_hash_table_foreach (surface->source_surfaces, _cairo_svg_source_surface_pluck, surface->source_surfaces);
_cairo_hash_table_destroy (surface->source_surfaces);
_cairo_hash_table_foreach (surface->color_glyphs,
_cairo_svg_color_glyph_pluck,
surface->color_glyphs);
_cairo_hash_table_destroy (surface->color_glyphs);
status = _cairo_svg_document_destroy (surface->document);
if (final_status == CAIRO_STATUS_SUCCESS) {
final_status = status;
@ -2430,6 +2535,28 @@ _cairo_svg_surface_emit_composite_recording_pattern (cairo_svg_stream_t *output,
return status;
}
// FIXME error handling order
cairo_svg_paint_t *paint_entry = _cairo_calloc (sizeof (cairo_svg_paint_t));
if (paint_entry == NULL) {
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
cairo_rectangle_int_t extents;
cairo_bool_t is_bounded;
// how to get extents ???
is_bounded =_cairo_surface_get_extents (pattern->surface, &extents);
//assert (is_bounded); ???
paint_entry->source_id = surface->source_id;
paint_entry->box.p1.x = extents.x;
paint_entry->box.p1.y = extents.y;
paint_entry->box.p2.x = extents.x + extents.width;
paint_entry->box.p2.y = extents.y + extents.height;
_cairo_svg_paint_box_add_padding (&paint_entry->box);
_cairo_array_init (&paint_entry->paint_elements, sizeof (cairo_svg_paint_element_t));
_cairo_svg_paint_init_key (paint_entry);
status = _cairo_hash_table_insert (document->paints, &paint_entry->base);
surface->transitive_paint_used = TRUE;
}
@ -4082,14 +4209,25 @@ _cairo_svg_surface_show_glyphs_impl (cairo_svg_stream_t *output,
return status;
}
static cairo_bool_t
_cairo_svg_surface_has_show_text_glyphs (void *abstract_surface)
{
return TRUE;
}
static cairo_int_status_t
_cairo_svg_surface_show_glyphs (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
cairo_glyph_t *glyphs,
int num_glyphs,
cairo_scaled_font_t *scaled_font,
const cairo_clip_t *clip)
_cairo_svg_surface_show_text_glyphs (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
const char *utf8,
int utf8_len,
cairo_glyph_t *glyphs,
int num_glyphs,
const cairo_text_cluster_t *clusters,
int num_clusters,
cairo_text_cluster_flags_t cluster_flags,
cairo_scaled_font_t *scaled_font,
const cairo_clip_t *clip)
{
cairo_svg_surface_t *surface = abstract_surface;
cairo_int_status_t status;
@ -4126,6 +4264,63 @@ _cairo_svg_surface_get_supported_mime_types (void *abstract_surface)
return _cairo_svg_supported_mime_types;
}
static cairo_bool_t
_cairo_svg_surface_supports_color_glyph (void *abstract_surface,
cairo_scaled_font_t *scaled_font,
unsigned long glyph_index)
{
cairo_svg_surface_t *surface = abstract_surface;
cairo_svg_color_glyph_t glyph_key;
cairo_svg_color_glyph_t *glyph_entry;
cairo_scaled_glyph_t *scaled_glyph;
cairo_status_t status;
glyph_key.scaled_font = scaled_font;
glyph_key.glyph_index = glyph_index;
_cairo_svg_color_glyph_init_key (&glyph_key);
glyph_entry = _cairo_hash_table_lookup (surface->color_glyphs, &glyph_key.base);
if (glyph_entry)
return glyph_entry->supported;
glyph_entry = _cairo_malloc (sizeof (cairo_svg_color_glyph_t));
if (glyph_entry == NULL) {
status = _cairo_surface_set_error (&surface->base,
_cairo_error (CAIRO_STATUS_NO_MEMORY));
return FALSE;
}
glyph_entry->scaled_font = cairo_scaled_font_reference (scaled_font);
glyph_entry->glyph_index = glyph_index;
_cairo_svg_color_glyph_init_key (glyph_entry);
glyph_entry->supported = FALSE;
_cairo_scaled_font_freeze_cache (scaled_font);
status = _cairo_scaled_glyph_lookup (scaled_font,
glyph_index,
CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE,
NULL, /* foreground color */
&scaled_glyph);
if (unlikely (status))
goto done;
glyph_entry->supported = !(scaled_glyph->recording_uses_foreground_color ||
scaled_glyph->recording_uses_foreground_marker);
done:
_cairo_scaled_font_thaw_cache (scaled_font);
status = _cairo_hash_table_insert (surface->color_glyphs,
&glyph_entry->base);
if (unlikely(status)) {
status = _cairo_surface_set_error (&surface->base,
_cairo_error (CAIRO_STATUS_NO_MEMORY));
return FALSE;
}
return glyph_entry->supported;
}
static const cairo_surface_backend_t cairo_svg_surface_backend = {
CAIRO_SURFACE_TYPE_SVG,
_cairo_svg_surface_finish,
@ -4156,10 +4351,14 @@ static const cairo_surface_backend_t cairo_svg_surface_backend = {
_cairo_svg_surface_stroke,
_cairo_svg_surface_fill,
_cairo_svg_surface_fill_stroke,
_cairo_svg_surface_show_glyphs,
NULL, /* has_show_text_glyphs */
NULL, /* show_text_glyphs */
NULL, /* show_glyphs */
_cairo_svg_surface_has_show_text_glyphs,
_cairo_svg_surface_show_text_glyphs,
_cairo_svg_surface_get_supported_mime_types,
NULL, /* tag */
_cairo_svg_surface_supports_color_glyph,
NULL, /* analyze_recording_surface */
NULL, /* command_id */
};
static cairo_status_t

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB