/* * Copyright © 2026 Behdad Esfahbod * * This is part of HarfBuzz, a text shaping library. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the * above copyright notice and the following two paragraphs appear in * all copies of this software. * * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * Author(s): Behdad Esfahbod */ #include "hb.hh" #include "hb-vector.h" #include "hb-blob.hh" #include "hb-geometry.hh" #include "hb-machinery.hh" #include "hb-map.hh" #include "hb-vector-svg-path.hh" #include "hb-vector-svg-subset.hh" #include "hb-vector-svg-utils.hh" #include #include #include static bool hb_svg_append_str (hb_vector_t *buf, const char *s) { return hb_svg_append_len (buf, s, (unsigned) strlen (s)); } static bool hb_svg_append_unsigned (hb_vector_t *buf, unsigned v) { char tmp[10]; unsigned n = 0; do { tmp[n++] = (char) ('0' + (v % 10)); v /= 10; } while (v); unsigned old_len = buf->length; if (unlikely (!buf->resize_dirty ((int) (old_len + n)))) return false; for (unsigned i = 0; i < n; i++) buf->arrayZ[old_len + i] = tmp[n - 1 - i]; return true; } static bool hb_svg_append_hex_byte (hb_vector_t *buf, unsigned v) { static const char hex[] = "0123456789ABCDEF"; char tmp[2] = {hex[(v >> 4) & 15], hex[v & 15]}; return hb_svg_append_len (buf, tmp, 2); } static bool hb_svg_append_base64 (hb_vector_t *buf, const uint8_t *data, unsigned len) { static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; unsigned out_len = ((len + 2) / 3) * 4; unsigned old_len = buf->length; if (unlikely (!buf->resize_dirty ((int) (old_len + out_len)))) return false; char *dst = buf->arrayZ + old_len; unsigned di = 0; unsigned i = 0; while (i + 2 < len) { unsigned v = ((unsigned) data[i] << 16) | ((unsigned) data[i + 1] << 8) | ((unsigned) data[i + 2]); dst[di++] = b64[(v >> 18) & 63]; dst[di++] = b64[(v >> 12) & 63]; dst[di++] = b64[(v >> 6) & 63]; dst[di++] = b64[v & 63]; i += 3; } if (i < len) { unsigned v = (unsigned) data[i] << 16; if (i + 1 < len) v |= (unsigned) data[i + 1] << 8; dst[di++] = b64[(v >> 18) & 63]; dst[di++] = b64[(v >> 12) & 63]; dst[di++] = (i + 1 < len) ? b64[(v >> 6) & 63] : '='; dst[di++] = '='; } return true; } struct hb_svg_blob_meta_t { char *data; int allocated; bool transferred; bool in_replace; }; static hb_user_data_key_t hb_svg_blob_meta_user_data_key; static void hb_svg_blob_meta_set_buffer (hb_svg_blob_meta_t *meta, char *data, int allocated) { meta->data = data; meta->allocated = allocated; meta->transferred = false; } static void hb_svg_blob_meta_release_buffer (hb_svg_blob_meta_t *meta) { if (!meta) return; if (!meta->transferred && meta->data) hb_free (meta->data); meta->data = nullptr; meta->allocated = 0; meta->transferred = true; } static void hb_svg_blob_meta_destroy (void *data) { auto *meta = (hb_svg_blob_meta_t *) data; hb_svg_blob_meta_release_buffer (meta); if (meta->in_replace) { meta->in_replace = false; return; } hb_free (meta); } static hb_blob_t * hb_svg_blob_from_buffer (hb_blob_t **recycled_blob, hb_vector_t *buf) { unsigned len = 0; int allocated = 0; char *data = buf->steal (&len, &allocated); if (!data) return nullptr; hb_blob_t *blob = nullptr; if (*recycled_blob) blob = *recycled_blob; bool reused_blob = blob && blob != hb_blob_get_empty (); bool new_meta = false; auto *meta = reused_blob ? (hb_svg_blob_meta_t *) hb_blob_get_user_data (blob, &hb_svg_blob_meta_user_data_key) : nullptr; if (!meta) { meta = (hb_svg_blob_meta_t *) hb_malloc (sizeof (hb_svg_blob_meta_t)); if (!meta) { hb_free (data); return nullptr; } meta->data = nullptr; meta->allocated = 0; meta->transferred = true; meta->in_replace = false; new_meta = true; } if (reused_blob) { /* replace_buffer() first destroys old buffer user_data; keep meta alive. */ meta->in_replace = true; blob->replace_buffer (data, len, HB_MEMORY_MODE_WRITABLE, meta, hb_svg_blob_meta_destroy); hb_svg_blob_meta_set_buffer (meta, data, allocated); } else { hb_svg_blob_meta_set_buffer (meta, data, allocated); blob = hb_blob_create (data, len, HB_MEMORY_MODE_WRITABLE, meta, hb_svg_blob_meta_destroy); } if (unlikely (blob == hb_blob_get_empty ())) { if (new_meta) hb_free (meta); hb_free (data); return nullptr; } if (new_meta && !hb_blob_set_user_data (blob, &hb_svg_blob_meta_user_data_key, meta, nullptr, true)) { if (!reused_blob) hb_blob_destroy (blob); return nullptr; } if (*recycled_blob) *recycled_blob = nullptr; return blob; } static void hb_svg_recover_recycled_buffer (hb_blob_t *blob, hb_vector_t *buf) { if (!blob) return; auto *meta = (hb_svg_blob_meta_t *) hb_blob_get_user_data (blob, &hb_svg_blob_meta_user_data_key); if (!meta || meta->transferred || !meta->data) return; buf->recycle_buffer (meta->data, 0, meta->allocated); meta->data = nullptr; meta->allocated = 0; meta->transferred = true; } static void hb_svg_append_color (hb_vector_t *buf, hb_color_t color, bool with_alpha) { static const char hex[] = "0123456789ABCDEF"; unsigned r = hb_color_get_red (color); unsigned g = hb_color_get_green (color); unsigned b = hb_color_get_blue (color); unsigned a = hb_color_get_alpha (color); hb_svg_append_c (buf, '#'); if (((r >> 4) == (r & 0xF)) && ((g >> 4) == (g & 0xF)) && ((b >> 4) == (b & 0xF))) { hb_svg_append_c (buf, hex[r & 0xF]); hb_svg_append_c (buf, hex[g & 0xF]); hb_svg_append_c (buf, hex[b & 0xF]); } else { hb_svg_append_hex_byte (buf, r); hb_svg_append_hex_byte (buf, g); hb_svg_append_hex_byte (buf, b); } if (with_alpha && a != 255) { hb_svg_append_str (buf, "\" fill-opacity=\""); hb_svg_append_num (buf, a / 255.f, 4); } } static void hb_svg_transform_point (const hb_transform_t<> &t, float x_scale_factor, float y_scale_factor, float x, float y, float *tx, float *ty) { float xx = x, yy = y; t.transform_point (xx, yy); *tx = xx / (x_scale_factor > 0 ? x_scale_factor : 1.f); *ty = yy / (y_scale_factor > 0 ? y_scale_factor : 1.f); } static hb_bool_t hb_svg_set_glyph_extents_common (const hb_transform_t<> &transform, float x_scale_factor, float y_scale_factor, const hb_glyph_extents_t *glyph_extents, hb_vector_extents_t *extents, hb_bool_t *has_extents) { float x0 = (float) glyph_extents->x_bearing; float y0 = (float) glyph_extents->y_bearing; float x1 = x0 + glyph_extents->width; float y1 = y0 + glyph_extents->height; float px[4] = {x0, x0, x1, x1}; float py[4] = {y0, y1, y0, y1}; float tx, ty; hb_svg_transform_point (transform, x_scale_factor, y_scale_factor, px[0], py[0], &tx, &ty); float tx_min = tx, tx_max = tx; float ty_min = ty, ty_max = ty; for (unsigned i = 1; i < 4; i++) { hb_svg_transform_point (transform, x_scale_factor, y_scale_factor, px[i], py[i], &tx, &ty); tx_min = hb_min (tx_min, tx); tx_max = hb_max (tx_max, tx); ty_min = hb_min (ty_min, ty); ty_max = hb_max (ty_max, ty); } if (tx_max <= tx_min || ty_max <= ty_min) { return false; } if (*has_extents) { float x0 = hb_min (extents->x, tx_min); float y0 = hb_min (extents->y, ty_min); float x1 = hb_max (extents->x + extents->width, tx_max); float y1 = hb_max (extents->y + extents->height, ty_max); *extents = {x0, y0, x1 - x0, y1 - y0}; } else { *extents = {tx_min, ty_min, tx_max - tx_min, ty_max - ty_min}; *has_extents = true; } return true; } struct hb_svg_color_glyph_cache_key_t { hb_codepoint_t glyph = HB_CODEPOINT_INVALID; unsigned palette = 0; hb_color_t foreground = 0; hb_svg_color_glyph_cache_key_t () = default; hb_svg_color_glyph_cache_key_t (hb_codepoint_t g, unsigned p, hb_color_t f) : glyph (g), palette (p), foreground (f) {} bool operator == (const hb_svg_color_glyph_cache_key_t &o) const { return glyph == o.glyph && palette == o.palette && foreground == o.foreground; } uint32_t hash () const { uint32_t h = hb_hash (glyph); h = h * 31u + hb_hash (palette); h = h * 31u + hb_hash (foreground); return h; } }; struct hb_vector_paint_t { hb_object_header_t header; hb_vector_format_t format = HB_VECTOR_FORMAT_SVG; hb_transform_t<> transform = {1, 0, 0, 1, 0, 0}; float x_scale_factor = 1.f; float y_scale_factor = 1.f; hb_vector_extents_t extents = {0, 0, 0, 0}; bool has_extents = false; hb_color_t foreground = HB_COLOR (0, 0, 0, 255); int palette = 0; hb_hashmap_t custom_palette_colors; unsigned precision = 2; bool flat = false; hb_vector_t defs; hb_vector_t path; hb_vector_t> group_stack; uint64_t transform_group_open_mask = 0; unsigned transform_group_depth = 0; unsigned transform_group_overflow_depth = 0; unsigned clip_rect_counter = 0; unsigned gradient_counter = 0; unsigned color_glyph_counter = 0; hb_set_t *defined_outlines = nullptr; hb_set_t *defined_clips = nullptr; hb_hashmap_t defined_color_glyphs; hb_vector_t color_stops_scratch; hb_vector_t subset_body_scratch; hb_vector_t captured_scratch; hb_blob_t *recycled_blob = nullptr; bool current_color_glyph_has_svg_image = false; hb_codepoint_t current_svg_image_glyph = HB_CODEPOINT_INVALID; hb_face_t *current_face = nullptr; unsigned svg_image_counter = 0; hb_vector_t ¤t_body () { return group_stack.tail (); } void append_global_transform_prefix (hb_vector_t *buf) { if (transform.xx == 1.f && transform.yx == 0.f && transform.xy == 0.f && transform.yy == 1.f && transform.x0 == 0.f && transform.y0 == 0.f && x_scale_factor == 1.f && y_scale_factor == 1.f) return; unsigned sprec = hb_svg_scale_precision (precision); hb_svg_append_str (buf, "\n"); } void append_global_transform_suffix (hb_vector_t *buf) { if (transform.xx == 1.f && transform.yx == 0.f && transform.xy == 0.f && transform.yy == 1.f && transform.x0 == 0.f && transform.y0 == 0.f && x_scale_factor == 1.f && y_scale_factor == 1.f) return; hb_svg_append_str (buf, "\n"); } }; static inline uint64_t hb_svg_pack_color_glyph_cache_entry (unsigned def_id, bool image_like) { return ((uint64_t) def_id << 1) | (image_like ? 1ull : 0ull); } static inline unsigned hb_svg_cache_entry_def_id (uint64_t v) { return (unsigned) (v >> 1); } static inline bool hb_svg_cache_entry_image_like (uint64_t v) { return !!(v & 1ull); } static inline hb_svg_color_glyph_cache_key_t hb_svg_color_glyph_cache_key (hb_codepoint_t glyph, unsigned palette, hb_color_t foreground) { return {glyph, palette, foreground}; } static inline void hb_svg_append_instance_transform (hb_vector_t *out, unsigned precision, float x_scale_factor, float y_scale_factor, float xx, float yx, float xy, float yy, float tx, float ty) { unsigned sprec = hb_svg_scale_precision (precision); if (xx == 1.f && yx == 0.f && xy == 0.f && yy == 1.f) { float sx = 1.f / x_scale_factor; float sy = 1.f / y_scale_factor; hb_svg_append_str (out, "translate("); hb_svg_append_num (out, tx / x_scale_factor, precision); hb_svg_append_c (out, ','); hb_svg_append_num (out, -ty / y_scale_factor, precision); hb_svg_append_str (out, ") scale("); hb_svg_append_num (out, sx, sprec, true); hb_svg_append_c (out, ','); hb_svg_append_num (out, -sy, sprec, true); hb_svg_append_c (out, ')'); } else { hb_svg_append_str (out, "matrix("); hb_svg_append_num (out, xx / x_scale_factor, sprec, true); hb_svg_append_c (out, ','); hb_svg_append_num (out, yx / y_scale_factor, sprec, true); hb_svg_append_c (out, ','); hb_svg_append_num (out, -xy / x_scale_factor, sprec, true); hb_svg_append_c (out, ','); hb_svg_append_num (out, -yy / y_scale_factor, sprec, true); hb_svg_append_c (out, ','); hb_svg_append_num (out, tx / x_scale_factor, precision); hb_svg_append_c (out, ','); hb_svg_append_num (out, -ty / y_scale_factor, precision); hb_svg_append_c (out, ')'); } } static inline void hb_svg_append_image_instance_translate (hb_vector_t *out, unsigned precision, float x_scale_factor, float y_scale_factor, float tx, float ty) { hb_svg_append_str (out, "translate("); hb_svg_append_num (out, tx / x_scale_factor, precision); hb_svg_append_c (out, ','); hb_svg_append_num (out, -ty / y_scale_factor, precision); hb_svg_append_c (out, ')'); } static hb_bool_t hb_svg_get_color_stops (hb_vector_paint_t *paint, hb_color_line_t *color_line, hb_vector_t *stops) { unsigned len = hb_color_line_get_color_stops (color_line, 0, nullptr, nullptr); if (unlikely (!stops->resize (len))) return false; hb_color_line_get_color_stops (color_line, 0, &len, stops->arrayZ); for (unsigned i = 0; i < len; i++) if (stops->arrayZ[i].is_foreground) stops->arrayZ[i].color = HB_COLOR (hb_color_get_blue (paint->foreground), hb_color_get_green (paint->foreground), hb_color_get_red (paint->foreground), (unsigned) hb_color_get_alpha (stops->arrayZ[i].color) * hb_color_get_alpha (paint->foreground) / 255); return true; } static const char * hb_svg_extend_mode_str (hb_paint_extend_t ext) { switch (ext) { case HB_PAINT_EXTEND_PAD: return "pad"; case HB_PAINT_EXTEND_REPEAT: return "repeat"; case HB_PAINT_EXTEND_REFLECT: return "reflect"; default: return "pad"; } } static int hb_svg_color_stop_cmp (const void *a, const void *b) { const hb_color_stop_t *x = (const hb_color_stop_t *) a; const hb_color_stop_t *y = (const hb_color_stop_t *) b; if (x->offset < y->offset) return -1; if (x->offset > y->offset) return 1; return 0; } static void hb_svg_emit_color_stops (hb_vector_paint_t *paint, hb_vector_t *buf, hb_vector_t *stops) { for (unsigned i = 0; i < stops->length; i++) { hb_color_t c = stops->arrayZ[i].color; hb_svg_append_str (buf, "arrayZ[i].offset, 4); hb_svg_append_str (buf, "\" stop-color=\"rgb("); hb_svg_append_unsigned (buf, hb_color_get_red (c)); hb_svg_append_c (buf, ','); hb_svg_append_unsigned (buf, hb_color_get_green (c)); hb_svg_append_c (buf, ','); hb_svg_append_unsigned (buf, hb_color_get_blue (c)); hb_svg_append_str (buf, ")\""); if (hb_color_get_alpha (c) != 255) { hb_svg_append_str (buf, " stop-opacity=\""); hb_svg_append_num (buf, hb_color_get_alpha (c) / 255.f, 4); hb_svg_append_c (buf, '"'); } hb_svg_append_str (buf, "/>\n"); } } static const char * hb_svg_composite_mode_str (hb_paint_composite_mode_t mode) { switch (mode) { case HB_PAINT_COMPOSITE_MODE_CLEAR: case HB_PAINT_COMPOSITE_MODE_SRC: case HB_PAINT_COMPOSITE_MODE_DEST: case HB_PAINT_COMPOSITE_MODE_DEST_OVER: case HB_PAINT_COMPOSITE_MODE_SRC_IN: case HB_PAINT_COMPOSITE_MODE_DEST_IN: case HB_PAINT_COMPOSITE_MODE_SRC_OUT: case HB_PAINT_COMPOSITE_MODE_DEST_OUT: case HB_PAINT_COMPOSITE_MODE_SRC_ATOP: case HB_PAINT_COMPOSITE_MODE_DEST_ATOP: case HB_PAINT_COMPOSITE_MODE_XOR: case HB_PAINT_COMPOSITE_MODE_PLUS: return nullptr; case HB_PAINT_COMPOSITE_MODE_SRC_OVER: return "normal"; case HB_PAINT_COMPOSITE_MODE_SCREEN: return "screen"; case HB_PAINT_COMPOSITE_MODE_OVERLAY: return "overlay"; case HB_PAINT_COMPOSITE_MODE_DARKEN: return "darken"; case HB_PAINT_COMPOSITE_MODE_LIGHTEN: return "lighten"; case HB_PAINT_COMPOSITE_MODE_COLOR_DODGE: return "color-dodge"; case HB_PAINT_COMPOSITE_MODE_COLOR_BURN: return "color-burn"; case HB_PAINT_COMPOSITE_MODE_HARD_LIGHT: return "hard-light"; case HB_PAINT_COMPOSITE_MODE_SOFT_LIGHT: return "soft-light"; case HB_PAINT_COMPOSITE_MODE_DIFFERENCE: return "difference"; case HB_PAINT_COMPOSITE_MODE_EXCLUSION: return "exclusion"; case HB_PAINT_COMPOSITE_MODE_MULTIPLY: return "multiply"; case HB_PAINT_COMPOSITE_MODE_HSL_HUE: return "hue"; case HB_PAINT_COMPOSITE_MODE_HSL_SATURATION: return "saturation"; case HB_PAINT_COMPOSITE_MODE_HSL_COLOR: return "color"; case HB_PAINT_COMPOSITE_MODE_HSL_LUMINOSITY: return "luminosity"; default: return nullptr; } } struct hb_svg_point_t { float x, y; }; struct hb_svg_rgba_t { float r, g, b, a; }; static inline float hb_svg_lerp (float a, float b, float t) { return a + (b - a) * t; } static inline float hb_svg_clamp01 (float v) { if (v < 0.f) return 0.f; if (v > 1.f) return 1.f; return v; } static inline hb_svg_rgba_t hb_svg_rgba_from_hb_color (hb_color_t c) { return {(float) hb_color_get_red (c) / 255.f, (float) hb_color_get_green (c) / 255.f, (float) hb_color_get_blue (c) / 255.f, (float) hb_color_get_alpha (c) / 255.f}; } static inline hb_color_t hb_svg_hb_color_from_rgba (const hb_svg_rgba_t &c) { unsigned r = (unsigned) roundf (hb_svg_clamp01 (c.r) * 255.f); unsigned g = (unsigned) roundf (hb_svg_clamp01 (c.g) * 255.f); unsigned b = (unsigned) roundf (hb_svg_clamp01 (c.b) * 255.f); unsigned a = (unsigned) roundf (hb_svg_clamp01 (c.a) * 255.f); return HB_COLOR (b, g, r, a); } static inline hb_svg_rgba_t hb_svg_lerp_rgba (const hb_svg_rgba_t &c0, const hb_svg_rgba_t &c1, float t) { return {hb_svg_lerp (c0.r, c1.r, t), hb_svg_lerp (c0.g, c1.g, t), hb_svg_lerp (c0.b, c1.b, t), hb_svg_lerp (c0.a, c1.a, t)}; } static inline float hb_svg_dot (const hb_svg_point_t &p, const hb_svg_point_t &q) { return p.x * q.x + p.y * q.y; } static inline hb_svg_point_t hb_svg_add (const hb_svg_point_t &p, const hb_svg_point_t &q) { return {p.x + q.x, p.y + q.y}; } static inline hb_svg_point_t hb_svg_sub (const hb_svg_point_t &p, const hb_svg_point_t &q) { return {p.x - q.x, p.y - q.y}; } static inline hb_svg_point_t hb_svg_scale (const hb_svg_point_t &p, float f) { return {p.x * f, p.y * f}; } static inline hb_svg_point_t hb_svg_normalize (const hb_svg_point_t &p) { float len = sqrtf (hb_svg_dot (p, p)); if (len == 0.f) return {0.f, 0.f}; return hb_svg_scale (p, 1.f / len); } static void hb_svg_add_sweep_patch (hb_vector_t *body, unsigned precision, float cx, float cy, float radius, float a0, const hb_svg_rgba_t &c0_in, float a1, const hb_svg_rgba_t &c1_in) { static const float max_angle = HB_PI / 16.f; hb_svg_point_t center = {cx, cy}; int num_splits = (int) ceilf (fabsf (a1 - a0) / max_angle); if (num_splits < 1) num_splits = 1; hb_svg_point_t p0 = {cosf (a0), sinf (a0)}; hb_svg_rgba_t color0 = c0_in; for (int a = 0; a < num_splits; a++) { float k = (a + 1.f) / num_splits; float angle1 = hb_svg_lerp (a0, a1, k); hb_svg_rgba_t color1 = hb_svg_lerp_rgba (c0_in, c1_in, k); hb_svg_point_t p1 = {cosf (angle1), sinf (angle1)}; hb_svg_point_t sp0 = hb_svg_add (center, hb_svg_scale (p0, radius)); hb_svg_point_t sp1 = hb_svg_add (center, hb_svg_scale (p1, radius)); hb_svg_point_t A = hb_svg_normalize (hb_svg_add (p0, p1)); hb_svg_point_t U = {-A.y, A.x}; float up0 = hb_svg_dot (U, p0); float up1 = hb_svg_dot (U, p1); if (fabsf (up0) < 1e-6f || fabsf (up1) < 1e-6f) { p0 = p1; color0 = color1; continue; } hb_svg_point_t C0 = hb_svg_add (A, hb_svg_scale (U, hb_svg_dot (hb_svg_sub (p0, A), p0) / up0)); hb_svg_point_t C1 = hb_svg_add (A, hb_svg_scale (U, hb_svg_dot (hb_svg_sub (p1, A), p1) / up1)); hb_svg_point_t sc0 = hb_svg_add (center, hb_svg_scale (hb_svg_add (C0, hb_svg_scale (hb_svg_sub (C0, p0), 0.33333f)), radius)); hb_svg_point_t sc1 = hb_svg_add (center, hb_svg_scale (hb_svg_add (C1, hb_svg_scale (hb_svg_sub (C1, p1), 0.33333f)), radius)); hb_svg_rgba_t mid_color = hb_svg_lerp_rgba (color0, color1, 0.5f); hb_color_t mid = hb_svg_hb_color_from_rgba (mid_color); hb_svg_append_str (body, "\n"); p0 = p1; color0 = color1; } } static void hb_svg_add_sweep_gradient_patches (hb_vector_t *body, unsigned precision, hb_color_stop_t *stops, unsigned n_stops, hb_paint_extend_t extend, float cx, float cy, float radius, float start_angle, float end_angle) { if (!n_stops) return; hb_svg_rgba_t colors_buf[16]; float angles_buf[16]; hb_svg_rgba_t *colors = colors_buf; float *angles = angles_buf; bool dynamic = false; if (start_angle == end_angle) { if (extend == HB_PAINT_EXTEND_PAD) { if (start_angle > 0.f) { hb_svg_rgba_t c = hb_svg_rgba_from_hb_color (stops[0].color); hb_svg_add_sweep_patch (body, precision, cx, cy, radius, 0.f, c, start_angle, c); } if (end_angle < HB_2_PI) { hb_svg_rgba_t c = hb_svg_rgba_from_hb_color (stops[n_stops - 1].color); hb_svg_add_sweep_patch (body, precision, cx, cy, radius, end_angle, c, HB_2_PI, c); } } return; } if (end_angle < start_angle) { float tmp = start_angle; start_angle = end_angle; end_angle = tmp; for (unsigned i = 0; i < n_stops - 1 - i; i++) { hb_color_stop_t t = stops[i]; stops[i] = stops[n_stops - 1 - i]; stops[n_stops - 1 - i] = t; } for (unsigned i = 0; i < n_stops; i++) stops[i].offset = 1.f - stops[i].offset; } if (n_stops > 16) { angles = (float *) hb_malloc (sizeof (float) * n_stops); colors = (hb_svg_rgba_t *) hb_malloc (sizeof (hb_svg_rgba_t) * n_stops); if (!angles || !colors) { hb_free (angles); hb_free (colors); return; } dynamic = true; } for (unsigned i = 0; i < n_stops; i++) { angles[i] = start_angle + stops[i].offset * (end_angle - start_angle); colors[i] = hb_svg_rgba_from_hb_color (stops[i].color); } if (extend == HB_PAINT_EXTEND_PAD) { unsigned pos; hb_svg_rgba_t color0 = colors[0]; for (pos = 0; pos < n_stops; pos++) { if (angles[pos] >= 0) { if (pos > 0) { float f = (0.f - angles[pos - 1]) / (angles[pos] - angles[pos - 1]); color0 = hb_svg_lerp_rgba (colors[pos - 1], colors[pos], f); } break; } } if (pos == n_stops) { color0 = colors[n_stops - 1]; hb_svg_add_sweep_patch (body, precision, cx, cy, radius, 0.f, color0, HB_2_PI, color0); goto done; } hb_svg_add_sweep_patch (body, precision, cx, cy, radius, 0.f, color0, angles[pos], colors[pos]); for (pos++; pos < n_stops; pos++) { if (angles[pos] <= HB_2_PI) hb_svg_add_sweep_patch (body, precision, cx, cy, radius, angles[pos - 1], colors[pos - 1], angles[pos], colors[pos]); else { float f = (HB_2_PI - angles[pos - 1]) / (angles[pos] - angles[pos - 1]); hb_svg_rgba_t color1 = hb_svg_lerp_rgba (colors[pos - 1], colors[pos], f); hb_svg_add_sweep_patch (body, precision, cx, cy, radius, angles[pos - 1], colors[pos - 1], HB_2_PI, color1); break; } } if (pos == n_stops) { color0 = colors[n_stops - 1]; hb_svg_add_sweep_patch (body, precision, cx, cy, radius, angles[n_stops - 1], color0, HB_2_PI, color0); goto done; } } else { float span = angles[n_stops - 1] - angles[0]; if (fabsf (span) < 1e-6f) goto done; int k = 0; if (angles[0] >= 0) { float ss = angles[0]; while (ss > 0) { if (span > 0) { ss -= span; k--; } else { ss += span; k++; } } } else { float ee = angles[n_stops - 1]; while (ee < 0) { if (span > 0) { ee += span; k++; } else { ee -= span; k--; } } } span = fabsf (span); for (int l = k; l < 1000; l++) { for (unsigned i = 1; i < n_stops; i++) { float a0_l, a1_l; const hb_svg_rgba_t *col0, *col1; if ((l % 2 != 0) && (extend == HB_PAINT_EXTEND_REFLECT)) { a0_l = angles[0] + angles[n_stops - 1] - angles[n_stops - i] + l * span; a1_l = angles[0] + angles[n_stops - 1] - angles[n_stops - 1 - i] + l * span; col0 = &colors[n_stops - i]; col1 = &colors[n_stops - 1 - i]; } else { a0_l = angles[i - 1] + l * span; a1_l = angles[i] + l * span; col0 = &colors[i - 1]; col1 = &colors[i]; } if (a1_l < 0.f) continue; if (a0_l < 0.f) { float f = (0.f - a0_l) / (a1_l - a0_l); hb_svg_rgba_t c = hb_svg_lerp_rgba (*col0, *col1, f); hb_svg_add_sweep_patch (body, precision, cx, cy, radius, 0.f, c, a1_l, *col1); } else if (a1_l >= HB_2_PI) { float f = (HB_2_PI - a0_l) / (a1_l - a0_l); hb_svg_rgba_t c = hb_svg_lerp_rgba (*col0, *col1, f); hb_svg_add_sweep_patch (body, precision, cx, cy, radius, a0_l, *col0, HB_2_PI, c); goto done; } else hb_svg_add_sweep_patch (body, precision, cx, cy, radius, a0_l, *col0, a1_l, *col1); } } } done: if (dynamic) { hb_free (angles); hb_free (colors); } } static void hb_vector_paint_push_transform (hb_paint_funcs_t *, void *, float, float, float, float, float, float, void *); static void hb_vector_paint_pop_transform (hb_paint_funcs_t *, void *, void *); static void hb_vector_paint_push_clip_glyph (hb_paint_funcs_t *, void *, hb_codepoint_t, hb_font_t *, void *); static void hb_vector_paint_push_clip_rectangle (hb_paint_funcs_t *, void *, float, float, float, float, void *); static void hb_vector_paint_pop_clip (hb_paint_funcs_t *, void *, void *); static void hb_vector_paint_color (hb_paint_funcs_t *, void *, hb_bool_t, hb_color_t, void *); static hb_bool_t hb_vector_paint_image (hb_paint_funcs_t *, void *, hb_blob_t *, unsigned, unsigned, hb_tag_t, float, hb_glyph_extents_t *, void *); static void hb_vector_paint_linear_gradient (hb_paint_funcs_t *, void *, hb_color_line_t *, float, float, float, float, float, float, void *); static void hb_vector_paint_radial_gradient (hb_paint_funcs_t *, void *, hb_color_line_t *, float, float, float, float, float, float, void *); static void hb_vector_paint_sweep_gradient (hb_paint_funcs_t *, void *, hb_color_line_t *, float, float, float, float, void *); static void hb_vector_paint_push_group (hb_paint_funcs_t *, void *, void *); static void hb_vector_paint_pop_group (hb_paint_funcs_t *, void *, hb_paint_composite_mode_t, void *); static hb_bool_t hb_vector_paint_color_glyph (hb_paint_funcs_t *, void *, hb_codepoint_t, hb_font_t *, void *); static hb_bool_t hb_vector_paint_custom_palette_color (hb_paint_funcs_t *, void *, unsigned, hb_color_t *, void *); static inline void free_static_vector_paint_funcs (); static struct hb_vector_paint_funcs_lazy_loader_t : hb_paint_funcs_lazy_loader_t { static hb_paint_funcs_t *create () { hb_paint_funcs_t *funcs = hb_paint_funcs_create (); hb_paint_funcs_set_push_transform_func (funcs, (hb_paint_push_transform_func_t) hb_vector_paint_push_transform, nullptr, nullptr); hb_paint_funcs_set_pop_transform_func (funcs, (hb_paint_pop_transform_func_t) hb_vector_paint_pop_transform, nullptr, nullptr); hb_paint_funcs_set_push_clip_glyph_func (funcs, (hb_paint_push_clip_glyph_func_t) hb_vector_paint_push_clip_glyph, nullptr, nullptr); hb_paint_funcs_set_push_clip_rectangle_func (funcs, (hb_paint_push_clip_rectangle_func_t) hb_vector_paint_push_clip_rectangle, nullptr, nullptr); hb_paint_funcs_set_pop_clip_func (funcs, (hb_paint_pop_clip_func_t) hb_vector_paint_pop_clip, nullptr, nullptr); hb_paint_funcs_set_color_func (funcs, (hb_paint_color_func_t) hb_vector_paint_color, nullptr, nullptr); hb_paint_funcs_set_image_func (funcs, (hb_paint_image_func_t) hb_vector_paint_image, nullptr, nullptr); hb_paint_funcs_set_linear_gradient_func (funcs, (hb_paint_linear_gradient_func_t) hb_vector_paint_linear_gradient, nullptr, nullptr); hb_paint_funcs_set_radial_gradient_func (funcs, (hb_paint_radial_gradient_func_t) hb_vector_paint_radial_gradient, nullptr, nullptr); hb_paint_funcs_set_sweep_gradient_func (funcs, (hb_paint_sweep_gradient_func_t) hb_vector_paint_sweep_gradient, nullptr, nullptr); hb_paint_funcs_set_push_group_func (funcs, (hb_paint_push_group_func_t) hb_vector_paint_push_group, nullptr, nullptr); hb_paint_funcs_set_pop_group_func (funcs, (hb_paint_pop_group_func_t) hb_vector_paint_pop_group, nullptr, nullptr); hb_paint_funcs_set_color_glyph_func (funcs, (hb_paint_color_glyph_func_t) hb_vector_paint_color_glyph, nullptr, nullptr); hb_paint_funcs_set_custom_palette_color_func (funcs, (hb_paint_custom_palette_color_func_t) hb_vector_paint_custom_palette_color, nullptr, nullptr); hb_paint_funcs_make_immutable (funcs); hb_atexit (free_static_vector_paint_funcs); return funcs; } } static_vector_paint_funcs; static inline void free_static_vector_paint_funcs () { static_vector_paint_funcs.free_instance (); } static hb_paint_funcs_t * hb_vector_paint_funcs_get () { return static_vector_paint_funcs.get_unconst (); } static void hb_vector_paint_ensure_initialized (hb_vector_paint_t *paint) { if (paint->group_stack.length) return; auto *root = paint->group_stack.push (); if (likely (root)) root->alloc (4096); } static void hb_vector_paint_push_transform (hb_paint_funcs_t *, void *paint_data, float xx, float yx, float xy, float yy, float dx, float dy, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); if (unlikely (paint->transform_group_overflow_depth)) { paint->transform_group_overflow_depth++; return; } if (unlikely (paint->transform_group_depth >= 64)) { paint->transform_group_overflow_depth = 1; return; } hb_bool_t opened = !(fabsf (xx - 1.f) < 1e-6f && fabsf (yx) < 1e-6f && fabsf (xy) < 1e-6f && fabsf (yy - 1.f) < 1e-6f && fabsf (dx) < 1e-6f && fabsf (dy) < 1e-6f); paint->transform_group_open_mask = (paint->transform_group_open_mask << 1) | (opened ? 1ull : 0ull); paint->transform_group_depth++; if (!opened) return; auto &body = paint->current_body (); unsigned sprec = hb_svg_scale_precision (paint->precision); hb_svg_append_str (&body, "precision); hb_svg_append_c (&body, ','); hb_svg_append_num (&body, dy, paint->precision); hb_svg_append_str (&body, ")\">\n"); } static void hb_vector_paint_pop_transform (hb_paint_funcs_t *, void *paint_data, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); if (unlikely (paint->transform_group_overflow_depth)) { paint->transform_group_overflow_depth--; return; } if (!paint->transform_group_depth) return; paint->transform_group_depth--; hb_bool_t opened = !!(paint->transform_group_open_mask & 1ull); paint->transform_group_open_mask >>= 1; if (opened) hb_svg_append_str (&paint->current_body (), "\n"); } static void hb_vector_paint_push_clip_glyph (hb_paint_funcs_t *, void *paint_data, hb_codepoint_t glyph, hb_font_t *font, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); if (!hb_set_has (paint->defined_outlines, glyph)) { hb_set_add (paint->defined_outlines, glyph); paint->path.clear (); hb_svg_path_sink_t sink = {&paint->path, paint->precision}; hb_font_draw_glyph (font, glyph, hb_svg_path_draw_funcs_get (), &sink); hb_svg_append_str (&paint->defs, "defs, glyph); hb_svg_append_str (&paint->defs, "\" d=\""); hb_svg_append_len (&paint->defs, paint->path.arrayZ, paint->path.length); hb_svg_append_str (&paint->defs, "\"/>\n"); } if (!hb_set_has (paint->defined_clips, glyph)) { hb_set_add (paint->defined_clips, glyph); hb_svg_append_str (&paint->defs, "defs, glyph); hb_svg_append_str (&paint->defs, "\">defs, glyph); hb_svg_append_str (&paint->defs, "\"/>\n"); } hb_svg_append_str (&paint->current_body (), "current_body (), glyph); hb_svg_append_str (&paint->current_body (), ")\">\n"); } static void hb_vector_paint_push_clip_rectangle (hb_paint_funcs_t *, void *paint_data, float xmin, float ymin, float xmax, float ymax, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); unsigned clip_id = paint->clip_rect_counter++; hb_svg_append_str (&paint->defs, "defs, clip_id); hb_svg_append_str (&paint->defs, "\">defs, xmin, paint->precision); hb_svg_append_str (&paint->defs, "\" y=\""); hb_svg_append_num (&paint->defs, ymin, paint->precision); hb_svg_append_str (&paint->defs, "\" width=\""); hb_svg_append_num (&paint->defs, xmax - xmin, paint->precision); hb_svg_append_str (&paint->defs, "\" height=\""); hb_svg_append_num (&paint->defs, ymax - ymin, paint->precision); hb_svg_append_str (&paint->defs, "\"/>\n"); hb_svg_append_str (&paint->current_body (), "current_body (), clip_id); hb_svg_append_str (&paint->current_body (), ")\">\n"); } static void hb_vector_paint_pop_clip (hb_paint_funcs_t *, void *paint_data, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); hb_svg_append_str (&paint->current_body (), "\n"); } static void hb_vector_paint_color (hb_paint_funcs_t *, void *paint_data, hb_bool_t is_foreground, hb_color_t color, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); hb_color_t c = color; if (is_foreground) c = HB_COLOR (hb_color_get_blue (paint->foreground), hb_color_get_green (paint->foreground), hb_color_get_red (paint->foreground), (unsigned) hb_color_get_alpha (paint->foreground) * hb_color_get_alpha (color) / 255); auto &body = paint->current_body (); hb_svg_append_str (&body, "\n"); } static hb_bool_t hb_vector_paint_image (hb_paint_funcs_t *, void *paint_data, hb_blob_t *image, unsigned width, unsigned height, hb_tag_t format, float slant HB_UNUSED, hb_glyph_extents_t *extents, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); auto &body = paint->current_body (); if (format == HB_TAG ('s','v','g',' ')) { paint->current_color_glyph_has_svg_image = true; paint->subset_body_scratch.clear (); bool subset_ok = hb_svg_subset_glyph_image (paint->current_face, image, paint->current_svg_image_glyph, &paint->svg_image_counter, &paint->defs, &paint->subset_body_scratch); if (unlikely (!subset_ok)) return false; if (extents) { hb_svg_append_str (&body, "x_bearing, paint->precision); hb_svg_append_c (&body, ','); hb_svg_append_num (&body, (float) extents->y_bearing, paint->precision); hb_svg_append_str (&body, ") scale("); hb_svg_append_num (&body, (float) extents->width / width, paint->precision); hb_svg_append_c (&body, ','); hb_svg_append_num (&body, (float) extents->height / height, paint->precision); hb_svg_append_str (&body, ")\">\n"); } hb_svg_append_len (&body, paint->subset_body_scratch.arrayZ, paint->subset_body_scratch.length); hb_svg_append_c (&body, '\n'); if (extents) hb_svg_append_str (&body, "\n"); return true; } if (format == HB_TAG ('p','n','g',' ')) { if (!extents || !width || !height) return false; unsigned len = 0; const char *png_data = hb_blob_get_data (image, &len); if (!png_data || !len) return false; hb_svg_append_str (&body, "x_bearing, paint->precision); hb_svg_append_c (&body, ','); hb_svg_append_num (&body, (float) extents->y_bearing, paint->precision); hb_svg_append_str (&body, ") scale("); hb_svg_append_num (&body, (float) extents->width / width, paint->precision); hb_svg_append_c (&body, ','); hb_svg_append_num (&body, (float) extents->height / height, paint->precision); hb_svg_append_str (&body, ")\">\n"); hb_svg_append_str (&body, "precision); hb_svg_append_str (&body, "\" height=\""); hb_svg_append_num (&body, (float) height, paint->precision); hb_svg_append_str (&body, "\"/>\n\n"); return true; } return false; } static void hb_vector_paint_linear_gradient (hb_paint_funcs_t *, void *paint_data, hb_color_line_t *color_line, float x0, float y0, float x1, float y1, float x2, float y2, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); hb_vector_t &stops = paint->color_stops_scratch; if (!hb_svg_get_color_stops (paint, color_line, &stops) || !stops.length) return; qsort (stops.arrayZ, stops.length, sizeof (hb_color_stop_t), hb_svg_color_stop_cmp); unsigned grad_id = paint->gradient_counter++; hb_svg_append_str (&paint->defs, "defs, grad_id); hb_svg_append_str (&paint->defs, "\" gradientUnits=\"userSpaceOnUse\" x1=\""); hb_svg_append_num (&paint->defs, x0, paint->precision); hb_svg_append_str (&paint->defs, "\" y1=\""); hb_svg_append_num (&paint->defs, y0, paint->precision); hb_svg_append_str (&paint->defs, "\" x2=\""); hb_svg_append_num (&paint->defs, x1 + (x1 - x2), paint->precision); hb_svg_append_str (&paint->defs, "\" y2=\""); hb_svg_append_num (&paint->defs, y1 + (y1 - y2), paint->precision); hb_svg_append_str (&paint->defs, "\" spreadMethod=\""); hb_svg_append_str (&paint->defs, hb_svg_extend_mode_str (hb_color_line_get_extend (color_line))); hb_svg_append_str (&paint->defs, "\">\n"); hb_svg_emit_color_stops (paint, &paint->defs, &stops); hb_svg_append_str (&paint->defs, "\n"); hb_svg_append_str (&paint->current_body (), "current_body (), grad_id); hb_svg_append_str (&paint->current_body (), ")\"/>\n"); } static void hb_vector_paint_radial_gradient (hb_paint_funcs_t *, void *paint_data, hb_color_line_t *color_line, float x0, float y0, float r0, float x1, float y1, float r1, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); hb_vector_t &stops = paint->color_stops_scratch; if (!hb_svg_get_color_stops (paint, color_line, &stops) || !stops.length) return; qsort (stops.arrayZ, stops.length, sizeof (hb_color_stop_t), hb_svg_color_stop_cmp); unsigned grad_id = paint->gradient_counter++; hb_svg_append_str (&paint->defs, "defs, grad_id); hb_svg_append_str (&paint->defs, "\" gradientUnits=\"userSpaceOnUse\" cx=\""); hb_svg_append_num (&paint->defs, x1, paint->precision); hb_svg_append_str (&paint->defs, "\" cy=\""); hb_svg_append_num (&paint->defs, y1, paint->precision); hb_svg_append_str (&paint->defs, "\" r=\""); hb_svg_append_num (&paint->defs, r1, paint->precision); hb_svg_append_str (&paint->defs, "\" fx=\""); hb_svg_append_num (&paint->defs, x0, paint->precision); hb_svg_append_str (&paint->defs, "\" fy=\""); hb_svg_append_num (&paint->defs, y0, paint->precision); if (r0 > 0) { hb_svg_append_str (&paint->defs, "\" fr=\""); hb_svg_append_num (&paint->defs, r0, paint->precision); } hb_svg_append_str (&paint->defs, "\" spreadMethod=\""); hb_svg_append_str (&paint->defs, hb_svg_extend_mode_str (hb_color_line_get_extend (color_line))); hb_svg_append_str (&paint->defs, "\">\n"); hb_svg_emit_color_stops (paint, &paint->defs, &stops); hb_svg_append_str (&paint->defs, "\n"); hb_svg_append_str (&paint->current_body (), "current_body (), grad_id); hb_svg_append_str (&paint->current_body (), ")\"/>\n"); } static void hb_vector_paint_sweep_gradient (hb_paint_funcs_t *, void *paint_data, hb_color_line_t *color_line, float cx, float cy, float start_angle, float end_angle, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); hb_vector_t &stops = paint->color_stops_scratch; if (!hb_svg_get_color_stops (paint, color_line, &stops) || !stops.length) return; qsort (stops.arrayZ, stops.length, sizeof (hb_color_stop_t), hb_svg_color_stop_cmp); hb_svg_add_sweep_gradient_patches (&paint->current_body (), paint->precision, stops.arrayZ, stops.length, hb_color_line_get_extend (color_line), cx, cy, 32767.f, start_angle, end_angle); } static void hb_vector_paint_push_group (hb_paint_funcs_t *, void *paint_data, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); paint->group_stack.push (hb_vector_t {}); } static void hb_vector_paint_pop_group (hb_paint_funcs_t *, void *paint_data, hb_paint_composite_mode_t mode, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); if (paint->group_stack.length < 2) return; hb_vector_t group = paint->group_stack.pop (); auto &body = paint->current_body (); const char *blend = hb_svg_composite_mode_str (mode); if (blend) { hb_svg_append_str (&body, "\n"); hb_svg_append_len (&body, group.arrayZ, group.length); hb_svg_append_str (&body, "\n"); } else hb_svg_append_len (&body, group.arrayZ, group.length); } static hb_bool_t hb_vector_paint_color_glyph (hb_paint_funcs_t *, void *paint_data, hb_codepoint_t glyph, hb_font_t *font, void *) { auto *paint = (hb_vector_paint_t *) paint_data; hb_vector_paint_ensure_initialized (paint); hb_codepoint_t old_gid = paint->current_svg_image_glyph; hb_face_t *old_face = paint->current_face; paint->current_svg_image_glyph = glyph; paint->current_face = hb_font_get_face (font); hb_font_paint_glyph (font, glyph, hb_vector_paint_funcs_get (), paint, paint->palette, paint->foreground); paint->current_svg_image_glyph = old_gid; paint->current_face = old_face; return true; } /** * hb_vector_paint_create_or_fail: * @format: output format. * * Creates a new paint context for vector output. * * Return value: (nullable): a newly allocated #hb_vector_paint_t, or `NULL` on failure. * * Since: 13.0.0 */ hb_vector_paint_t * hb_vector_paint_create_or_fail (hb_vector_format_t format) { if (format != HB_VECTOR_FORMAT_SVG) return nullptr; hb_vector_paint_t *paint = hb_object_create (); if (unlikely (!paint)) return nullptr; paint->format = format; paint->defined_outlines = hb_set_create (); paint->defined_clips = hb_set_create (); paint->defs.alloc (4096); paint->path.alloc (2048); paint->subset_body_scratch.alloc (2048); paint->captured_scratch.alloc (4096); paint->color_stops_scratch.alloc (16); return paint; } /** * hb_vector_paint_reference: * @paint: a paint context. * * Increases the reference count of @paint. * * Return value: (transfer full): referenced @paint. * * Since: 13.0.0 */ hb_vector_paint_t * hb_vector_paint_reference (hb_vector_paint_t *paint) { return hb_object_reference (paint); } /** * hb_vector_paint_destroy: * @paint: a paint context. * * Decreases the reference count of @paint and destroys it when it reaches zero. * * Since: 13.0.0 */ void hb_vector_paint_destroy (hb_vector_paint_t *paint) { if (!hb_object_destroy (paint)) return; hb_blob_destroy (paint->recycled_blob); hb_set_destroy (paint->defined_outlines); hb_set_destroy (paint->defined_clips); hb_free (paint); } /** * hb_vector_paint_set_user_data: * @paint: a paint context. * @key: user-data key. * @data: user-data value. * @destroy: (nullable): destroy callback for @data. * @replace: whether to replace an existing value for @key. * * Attaches user data to @paint. * * Return value: `true` on success, `false` otherwise. * * Since: 13.0.0 */ hb_bool_t hb_vector_paint_set_user_data (hb_vector_paint_t *paint, hb_user_data_key_t *key, void *data, hb_destroy_func_t destroy, hb_bool_t replace) { return hb_object_set_user_data (paint, key, data, destroy, replace); } /** * hb_vector_paint_get_user_data: * @paint: a paint context. * @key: user-data key. * * Gets previously attached user data from @paint. * * Return value: (nullable): user-data value associated with @key. * * Since: 13.0.0 */ void * hb_vector_paint_get_user_data (hb_vector_paint_t *paint, hb_user_data_key_t *key) { return hb_object_get_user_data (paint, key); } /** * hb_vector_paint_set_transform: * @paint: a paint context. * @xx: transform xx component. * @yx: transform yx component. * @xy: transform xy component. * @yy: transform yy component. * @dx: transform x translation. * @dy: transform y translation. * * Sets the affine transform used when painting glyphs. * * Since: 13.0.0 */ void hb_vector_paint_set_transform (hb_vector_paint_t *paint, float xx, float yx, float xy, float yy, float dx, float dy) { paint->transform = {xx, yx, xy, yy, dx, dy}; } /** * hb_vector_paint_get_transform: * @paint: a paint context. * @xx: (out) (nullable): transform xx component. * @yx: (out) (nullable): transform yx component. * @xy: (out) (nullable): transform xy component. * @yy: (out) (nullable): transform yy component. * @dx: (out) (nullable): transform x translation. * @dy: (out) (nullable): transform y translation. * * Gets the affine transform used when painting glyphs. * * Since: 13.0.0 */ void hb_vector_paint_get_transform (hb_vector_paint_t *paint, float *xx, float *yx, float *xy, float *yy, float *dx, float *dy) { if (xx) *xx = paint->transform.xx; if (yx) *yx = paint->transform.yx; if (xy) *xy = paint->transform.xy; if (yy) *yy = paint->transform.yy; if (dx) *dx = paint->transform.x0; if (dy) *dy = paint->transform.y0; } /** * hb_vector_paint_set_scale_factor: * @paint: a paint context. * @x_scale_factor: x scale factor. * @y_scale_factor: y scale factor. * * Sets additional output scaling factors. * * Since: 13.0.0 */ void hb_vector_paint_set_scale_factor (hb_vector_paint_t *paint, float x_scale_factor, float y_scale_factor) { paint->x_scale_factor = x_scale_factor > 0.f ? x_scale_factor : 1.f; paint->y_scale_factor = y_scale_factor > 0.f ? y_scale_factor : 1.f; } /** * hb_vector_paint_get_scale_factor: * @paint: a paint context. * @x_scale_factor: (out) (nullable): x scale factor. * @y_scale_factor: (out) (nullable): y scale factor. * * Gets additional output scaling factors. * * Since: 13.0.0 */ void hb_vector_paint_get_scale_factor (hb_vector_paint_t *paint, float *x_scale_factor, float *y_scale_factor) { if (x_scale_factor) *x_scale_factor = paint->x_scale_factor; if (y_scale_factor) *y_scale_factor = paint->y_scale_factor; } /** * hb_vector_paint_set_extents: * @paint: a paint context. * @extents: (nullable): output extents to set or expand. * * Sets or expands output extents on @paint. Passing `NULL` clears extents. * * Since: 13.0.0 */ void hb_vector_paint_set_extents (hb_vector_paint_t *paint, const hb_vector_extents_t *extents) { if (!extents) { paint->extents = {0, 0, 0, 0}; paint->has_extents = false; return; } if (!(extents->width > 0.f && extents->height > 0.f)) return; if (paint->has_extents) { float x0 = hb_min (paint->extents.x, extents->x); float y0 = hb_min (paint->extents.y, extents->y); float x1 = hb_max (paint->extents.x + paint->extents.width, extents->x + extents->width); float y1 = hb_max (paint->extents.y + paint->extents.height, extents->y + extents->height); paint->extents = {x0, y0, x1 - x0, y1 - y0}; } else { paint->extents = *extents; paint->has_extents = true; } } /** * hb_vector_paint_get_extents: * @paint: a paint context. * @extents: (out) (nullable): where to store current output extents. * * Gets current output extents from @paint. * * Return value: `true` if extents are set, `false` otherwise. * * Since: 13.0.0 */ hb_bool_t hb_vector_paint_get_extents (hb_vector_paint_t *paint, hb_vector_extents_t *extents) { if (!paint->has_extents) return false; if (extents) *extents = paint->extents; return true; } /** * hb_vector_paint_set_glyph_extents: * @paint: a paint context. * @glyph_extents: glyph extents in font units. * * Expands @paint extents using @glyph_extents under the current transform. * * Return value: `true` on success, `false` otherwise. * * Since: 13.0.0 */ hb_bool_t hb_vector_paint_set_glyph_extents (hb_vector_paint_t *paint, const hb_glyph_extents_t *glyph_extents) { hb_bool_t has_extents = paint->has_extents; hb_bool_t ret = hb_svg_set_glyph_extents_common (paint->transform, paint->x_scale_factor, paint->y_scale_factor, glyph_extents, &paint->extents, &has_extents); paint->has_extents = has_extents; return ret; } /** * hb_vector_paint_set_foreground: * @paint: a paint context. * @foreground: foreground color used for COLR foreground paints. * * Sets fallback foreground color used by paint operations. * * Since: 13.0.0 */ void hb_vector_paint_set_foreground (hb_vector_paint_t *paint, hb_color_t foreground) { paint->foreground = foreground; } /** * hb_vector_paint_set_palette: * @paint: a paint context. * @palette: palette index for color glyph painting. * * Sets the color palette index used by paint operations. * * Since: 13.0.0 */ void hb_vector_paint_set_palette (hb_vector_paint_t *paint, int palette) { paint->palette = palette; } /** * hb_vector_paint_set_custom_palette_color: * @paint: a paint context. * @color_index: color index to override. * @color: replacement color. * * Overrides one font palette color entry for subsequent paint operations. * Overrides are keyed by @color_index and persist on @paint until cleared * (or replaced for the same index). * * These overrides are consulted by paint operations that resolve CPAL * entries, including SVG glyph content using `var(--colorN)`. * * Since: 13.0.0 */ void hb_vector_paint_set_custom_palette_color (hb_vector_paint_t *paint, unsigned color_index, hb_color_t color) { paint->custom_palette_colors.set (color_index, color); } /** * hb_vector_paint_clear_custom_palette_colors: * @paint: a paint context. * * Clears all custom palette color overrides previously set on @paint. * * After this call, palette lookups use the selected font palette without * custom override entries. * * Since: 13.0.0 */ void hb_vector_paint_clear_custom_palette_colors (hb_vector_paint_t *paint) { paint->custom_palette_colors.clear (); } /** * hb_vector_paint_get_funcs: * * Gets paint callbacks implemented by the vector paint backend. * * Return value: (transfer none): immutable #hb_paint_funcs_t singleton. * * Since: 13.0.0 */ hb_paint_funcs_t * hb_vector_paint_get_funcs (void) { return hb_vector_paint_funcs_get (); } static hb_bool_t hb_vector_paint_custom_palette_color (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, unsigned color_index, hb_color_t *color, void *user_data HB_UNUSED) { hb_vector_paint_t *paint = (hb_vector_paint_t *) paint_data; if (!paint || !color) return false; hb_color_t *value = nullptr; if (!paint->custom_palette_colors.has (color_index, &value) || !value) return false; *color = *value; return true; } /** * hb_vector_paint_glyph: * @paint: a paint context. * @font: font object. * @glyph: glyph ID. * @pen_x: glyph x origin before context transform. * @pen_y: glyph y origin before context transform. * @extents_mode: extents update mode. * * Paints one color glyph into @paint. * * Return value: `true` if glyph paint data was emitted, `false` otherwise. * * Since: 13.0.0 */ hb_bool_t hb_vector_paint_glyph (hb_vector_paint_t *paint, hb_font_t *font, hb_codepoint_t glyph, float pen_x, float pen_y, hb_vector_extents_mode_t extents_mode) { float xx = paint->transform.xx; float yx = paint->transform.yx; float xy = paint->transform.xy; float yy = paint->transform.yy; float tx = paint->transform.x0 + xx * pen_x + xy * pen_y; float ty = paint->transform.y0 + yx * pen_x + yy * pen_y; if (extents_mode == HB_VECTOR_EXTENTS_MODE_EXPAND) { hb_glyph_extents_t ge; if (hb_font_get_glyph_extents (font, glyph, &ge)) { hb_bool_t has_extents = paint->has_extents; hb_transform_t<> extents_transform = {xx, yx, -xy, -yy, tx, ty}; hb_bool_t ret = hb_svg_set_glyph_extents_common (extents_transform, paint->x_scale_factor, paint->y_scale_factor, &ge, &paint->extents, &has_extents); paint->has_extents = has_extents; (void) ret; } } hb_vector_paint_ensure_initialized (paint); bool can_cache = !paint->flat; hb_svg_color_glyph_cache_key_t cache_key = hb_svg_color_glyph_cache_key (glyph, (unsigned) paint->palette, paint->foreground); if (can_cache) { if (paint->defined_color_glyphs.has (cache_key)) { uint64_t entry = paint->defined_color_glyphs.get (cache_key); unsigned def_id = hb_svg_cache_entry_def_id (entry); bool image_like = hb_svg_cache_entry_image_like (entry); auto &body = paint->current_body (); hb_svg_append_str (&body, "precision, paint->x_scale_factor, paint->y_scale_factor, tx, ty); else hb_svg_append_instance_transform (&body, paint->precision, paint->x_scale_factor, paint->y_scale_factor, xx, yx, xy, yy, tx, ty); hb_svg_append_str (&body, "\"/>\n"); return true; } } bool has_svg_image = false; if (can_cache) { paint->group_stack.push (hb_vector_t {}); paint->current_color_glyph_has_svg_image = false; } if (can_cache) { hb_codepoint_t old_gid = paint->current_svg_image_glyph; hb_face_t *old_face = paint->current_face; paint->current_svg_image_glyph = glyph; paint->current_face = hb_font_get_face (font); hb_bool_t ret = hb_font_paint_glyph_or_fail (font, glyph, hb_vector_paint_get_funcs (), paint, (unsigned) paint->palette, paint->foreground); paint->current_svg_image_glyph = old_gid; paint->current_face = old_face; if (unlikely (!ret)) { paint->group_stack.pop (); return false; } paint->captured_scratch = paint->group_stack.pop (); has_svg_image = paint->current_color_glyph_has_svg_image; if (unlikely (!paint->captured_scratch.length)) return false; unsigned def_id = paint->color_glyph_counter++; if (unlikely (!paint->defined_color_glyphs.set (cache_key, hb_svg_pack_color_glyph_cache_entry (def_id, has_svg_image)))) return false; hb_svg_append_str (&paint->defs, "defs, def_id); hb_svg_append_str (&paint->defs, "\">\n"); hb_svg_append_len (&paint->defs, paint->captured_scratch.arrayZ, paint->captured_scratch.length); hb_svg_append_str (&paint->defs, "\n"); auto &body = paint->current_body (); hb_svg_append_str (&body, "precision, paint->x_scale_factor, paint->y_scale_factor, tx, ty); else hb_svg_append_instance_transform (&body, paint->precision, paint->x_scale_factor, paint->y_scale_factor, xx, yx, xy, yy, tx, ty); hb_svg_append_str (&body, "\"/>\n"); return true; } auto &body = paint->current_body (); hb_svg_append_str (&body, "precision, paint->x_scale_factor, paint->y_scale_factor, xx, yx, xy, yy, tx, ty); hb_svg_append_str (&body, "\">\n"); hb_codepoint_t old_gid = paint->current_svg_image_glyph; hb_face_t *old_face = paint->current_face; paint->current_svg_image_glyph = glyph; paint->current_face = hb_font_get_face (font); hb_bool_t ret = hb_font_paint_glyph_or_fail (font, glyph, hb_vector_paint_get_funcs (), paint, (unsigned) paint->palette, paint->foreground); paint->current_svg_image_glyph = old_gid; paint->current_face = old_face; hb_svg_append_str (&body, "\n"); return ret; } /** * hb_vector_svg_paint_set_flat: * @paint: a paint context. * @flat: whether to flatten paint output and disable glyph-group reuse. * * Enables or disables SVG paint flattening. * * Since: 13.0.0 */ void hb_vector_svg_paint_set_flat (hb_vector_paint_t *paint, hb_bool_t flat) { paint->flat = !!flat; } /** * hb_vector_svg_paint_set_precision: * @paint: a paint context. * @precision: decimal precision. * * Sets numeric output precision for SVG paint output. * * Since: 13.0.0 */ void hb_vector_svg_paint_set_precision (hb_vector_paint_t *paint, unsigned precision) { paint->precision = hb_min (precision, 12u); } static void hb_vector_paint_clear_render_state (hb_vector_paint_t *paint) { paint->extents = {0, 0, 0, 0}; paint->has_extents = false; paint->defs.clear (); paint->path.clear (); paint->group_stack.clear (); paint->transform_group_open_mask = 0; paint->transform_group_depth = 0; paint->transform_group_overflow_depth = 0; paint->clip_rect_counter = 0; paint->gradient_counter = 0; paint->color_glyph_counter = 0; paint->current_color_glyph_has_svg_image = false; paint->current_svg_image_glyph = HB_CODEPOINT_INVALID; paint->current_face = nullptr; paint->svg_image_counter = 0; hb_set_clear (paint->defined_outlines); hb_set_clear (paint->defined_clips); paint->defined_color_glyphs.clear (); paint->color_stops_scratch.clear (); paint->subset_body_scratch.clear (); paint->captured_scratch.clear (); } /** * hb_vector_paint_render: * @paint: a paint context. * * Renders accumulated paint content to an SVG blob. * * Return value: (transfer full) (nullable): output blob, or `NULL` if rendering cannot proceed. * * Since: 13.0.0 */ hb_blob_t * hb_vector_paint_render (hb_vector_paint_t *paint) { if (paint->format != HB_VECTOR_FORMAT_SVG) return nullptr; if (!paint->has_extents) return nullptr; hb_vector_paint_ensure_initialized (paint); hb_vector_t out; hb_svg_recover_recycled_buffer (paint->recycled_blob, &out); unsigned estimated = paint->defs.length + paint->group_stack.arrayZ[0].length + 320; out.alloc (estimated); hb_svg_append_str (&out, "extents.x, paint->precision); hb_svg_append_c (&out, ' '); hb_svg_append_num (&out, paint->extents.y, paint->precision); hb_svg_append_c (&out, ' '); hb_svg_append_num (&out, paint->extents.width, paint->precision); hb_svg_append_c (&out, ' '); hb_svg_append_num (&out, paint->extents.height, paint->precision); hb_svg_append_str (&out, "\" width=\""); hb_svg_append_num (&out, paint->extents.width, paint->precision); hb_svg_append_str (&out, "\" height=\""); hb_svg_append_num (&out, paint->extents.height, paint->precision); hb_svg_append_str (&out, "\">\n"); if (paint->defs.length) { hb_svg_append_str (&out, "\n"); hb_svg_append_len (&out, paint->defs.arrayZ, paint->defs.length); hb_svg_append_str (&out, "\n"); } paint->append_global_transform_prefix (&out); hb_svg_append_len (&out, paint->group_stack.arrayZ[0].arrayZ, paint->group_stack.arrayZ[0].length); paint->append_global_transform_suffix (&out); hb_svg_append_str (&out, "\n"); hb_blob_t *blob = hb_svg_blob_from_buffer (&paint->recycled_blob, &out); hb_vector_paint_clear_render_state (paint); return blob; } /** * hb_vector_paint_reset: * @paint: a paint context. * * Resets @paint state and clears accumulated content. * * Since: 13.0.0 */ void hb_vector_paint_reset (hb_vector_paint_t *paint) { paint->transform = {1, 0, 0, 1, 0, 0}; paint->x_scale_factor = 1.f; paint->y_scale_factor = 1.f; paint->foreground = HB_COLOR (0, 0, 0, 255); paint->palette = 0; paint->precision = 2; paint->flat = false; hb_vector_paint_clear_render_state (paint); } /** * hb_vector_paint_recycle_blob: * @paint: a paint context. * @blob: (nullable): previously rendered blob to recycle. * * Provides a blob for internal buffer reuse by later render calls. * * Since: 13.0.0 */ void hb_vector_paint_recycle_blob (hb_vector_paint_t *paint, hb_blob_t *blob) { hb_blob_destroy (paint->recycled_blob); paint->recycled_blob = nullptr; if (!blob || blob == hb_blob_get_empty ()) return; paint->recycled_blob = blob; }