| /* |
| * 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-paint.hh" |
| #include "hb-paint.hh" |
| #include "hb-vector-path.hh" |
| |
| #include <math.h> |
| |
| static const char * |
| hb_vector_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 void |
| hb_vector_svg_emit_color_stops (hb_vector_paint_t *paint, |
| hb_vector_buf_t *buf, |
| hb_vector_t<hb_color_stop_t> *stops) |
| { |
| for (unsigned i = 0; i < stops->length; i++) |
| { |
| hb_color_t c = stops->arrayZ[i].color; |
| buf->append_str ("<stop offset=\""); |
| buf->append_num (stops->arrayZ[i].offset, 4); |
| buf->append_str ("\" stop-color=\"rgb("); |
| buf->append_unsigned (hb_color_get_red (c)); |
| buf->append_c (','); |
| buf->append_unsigned (hb_color_get_green (c)); |
| buf->append_c (','); |
| buf->append_unsigned (hb_color_get_blue (c)); |
| buf->append_str (")\""); |
| if (hb_color_get_alpha (c) != 255) |
| { |
| buf->append_str (" stop-opacity=\""); |
| buf->append_num (hb_color_get_alpha (c) / 255.f, 4); |
| buf->append_c ('"'); |
| } |
| buf->append_str ("/>\n"); |
| } |
| } |
| |
| static const char * |
| hb_vector_svg_composite_mode_str (hb_paint_composite_mode_t mode) |
| { |
| switch (mode) |
| { |
| /* Porter-Duff modes have no SVG mix-blend-mode equivalent; |
| * approximate the two that have a plausible color-blend analog, |
| * and let the rest fall through to normal (SRC_OVER). */ |
| case HB_PAINT_COMPOSITE_MODE_PLUS: return "screen"; |
| case HB_PAINT_COMPOSITE_MODE_XOR: return "difference"; |
| 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: |
| 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_vector_svg_point_t { float x, y; }; |
| struct hb_vector_svg_rgba_t { float r, g, b, a; }; |
| |
| static inline float |
| hb_vector_svg_lerp (float a, float b, float t) |
| { return a + (b - a) * t; } |
| |
| static inline float |
| hb_vector_svg_clamp01 (float v) |
| { |
| if (v < 0.f) return 0.f; |
| if (v > 1.f) return 1.f; |
| return v; |
| } |
| |
| static inline hb_vector_svg_rgba_t |
| hb_vector_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_vector_svg_hb_color_from_rgba (const hb_vector_svg_rgba_t &c) |
| { |
| unsigned r = (unsigned) roundf (hb_vector_svg_clamp01 (c.r) * 255.f); |
| unsigned g = (unsigned) roundf (hb_vector_svg_clamp01 (c.g) * 255.f); |
| unsigned b = (unsigned) roundf (hb_vector_svg_clamp01 (c.b) * 255.f); |
| unsigned a = (unsigned) roundf (hb_vector_svg_clamp01 (c.a) * 255.f); |
| return HB_COLOR (b, g, r, a); |
| } |
| |
| static inline hb_vector_svg_rgba_t |
| hb_vector_svg_lerp_rgba (const hb_vector_svg_rgba_t &c0, |
| const hb_vector_svg_rgba_t &c1, |
| float t) |
| { |
| return {hb_vector_svg_lerp (c0.r, c1.r, t), |
| hb_vector_svg_lerp (c0.g, c1.g, t), |
| hb_vector_svg_lerp (c0.b, c1.b, t), |
| hb_vector_svg_lerp (c0.a, c1.a, t)}; |
| } |
| |
| static inline float hb_vector_svg_dot (const hb_vector_svg_point_t &p, const hb_vector_svg_point_t &q) { return p.x * q.x + p.y * q.y; } |
| static inline hb_vector_svg_point_t hb_vector_svg_add (const hb_vector_svg_point_t &p, const hb_vector_svg_point_t &q) { return {p.x + q.x, p.y + q.y}; } |
| static inline hb_vector_svg_point_t hb_vector_svg_sub (const hb_vector_svg_point_t &p, const hb_vector_svg_point_t &q) { return {p.x - q.x, p.y - q.y}; } |
| static inline hb_vector_svg_point_t hb_vector_svg_scale (const hb_vector_svg_point_t &p, float f) { return {p.x * f, p.y * f}; } |
| |
| static inline hb_vector_svg_point_t |
| hb_vector_svg_normalize (const hb_vector_svg_point_t &p) |
| { |
| float len = sqrtf (hb_vector_svg_dot (p, p)); |
| if (len == 0.f) return {0.f, 0.f}; |
| return hb_vector_svg_scale (p, 1.f / len); |
| } |
| |
| static void |
| hb_vector_svg_add_sweep_patch (hb_vector_buf_t *body, |
| unsigned precision, |
| float cx, float cy, float radius, |
| float a0, const hb_vector_svg_rgba_t &c0_in, |
| float a1, const hb_vector_svg_rgba_t &c1_in) |
| { |
| static const float max_angle = HB_PI / 16.f; |
| hb_vector_svg_point_t center = {cx, cy}; |
| int num_splits = (int) ceilf (fabsf (a1 - a0) / max_angle); |
| if (num_splits < 1) num_splits = 1; |
| |
| hb_vector_svg_point_t p0 = {cosf (a0), sinf (a0)}; |
| hb_vector_svg_rgba_t color0 = c0_in; |
| |
| for (int a = 0; a < num_splits; a++) |
| { |
| float k = (a + 1.f) / num_splits; |
| float angle1 = hb_vector_svg_lerp (a0, a1, k); |
| hb_vector_svg_rgba_t color1 = hb_vector_svg_lerp_rgba (c0_in, c1_in, k); |
| |
| hb_vector_svg_point_t p1 = {cosf (angle1), sinf (angle1)}; |
| hb_vector_svg_point_t sp0 = hb_vector_svg_add (center, hb_vector_svg_scale (p0, radius)); |
| hb_vector_svg_point_t sp1 = hb_vector_svg_add (center, hb_vector_svg_scale (p1, radius)); |
| |
| hb_vector_svg_point_t A = hb_vector_svg_normalize (hb_vector_svg_add (p0, p1)); |
| hb_vector_svg_point_t U = {-A.y, A.x}; |
| float up0 = hb_vector_svg_dot (U, p0); |
| float up1 = hb_vector_svg_dot (U, p1); |
| if (fabsf (up0) < 1e-6f || fabsf (up1) < 1e-6f) |
| { |
| p0 = p1; |
| color0 = color1; |
| continue; |
| } |
| hb_vector_svg_point_t C0 = hb_vector_svg_add (A, hb_vector_svg_scale (U, hb_vector_svg_dot (hb_vector_svg_sub (p0, A), p0) / up0)); |
| hb_vector_svg_point_t C1 = hb_vector_svg_add (A, hb_vector_svg_scale (U, hb_vector_svg_dot (hb_vector_svg_sub (p1, A), p1) / up1)); |
| |
| hb_vector_svg_point_t sc0 = hb_vector_svg_add (center, hb_vector_svg_scale (hb_vector_svg_add (C0, hb_vector_svg_scale (hb_vector_svg_sub (C0, p0), 0.33333f)), radius)); |
| hb_vector_svg_point_t sc1 = hb_vector_svg_add (center, hb_vector_svg_scale (hb_vector_svg_add (C1, hb_vector_svg_scale (hb_vector_svg_sub (C1, p1), 0.33333f)), radius)); |
| |
| hb_vector_svg_rgba_t mid_color = hb_vector_svg_lerp_rgba (color0, color1, 0.5f); |
| hb_color_t mid = hb_vector_svg_hb_color_from_rgba (mid_color); |
| |
| body->append_str ("<path d=\"M"); |
| body->append_num (center.x, precision); |
| body->append_c (','); |
| body->append_num (center.y, precision); |
| body->append_str ("L"); |
| body->append_num (sp0.x, precision); |
| body->append_c (','); |
| body->append_num (sp0.y, precision); |
| body->append_str ("C"); |
| body->append_num (sc0.x, precision); |
| body->append_c (','); |
| body->append_num (sc0.y, precision); |
| body->append_c (' '); |
| body->append_num (sc1.x, precision); |
| body->append_c (','); |
| body->append_num (sc1.y, precision); |
| body->append_c (' '); |
| body->append_num (sp1.x, precision); |
| body->append_c (','); |
| body->append_num (sp1.y, precision); |
| body->append_str ("Z\" fill=\""); |
| body->append_svg_color (mid, true); |
| body->append_str ("\"/>\n"); |
| |
| p0 = p1; |
| color0 = color1; |
| } |
| } |
| |
| /* Callback context + trampoline for hb_paint_sweep_gradient_tiles. */ |
| struct hb_vector_svg_sweep_ctx_t |
| { |
| hb_vector_buf_t *body; |
| unsigned precision; |
| float cx, cy, radius; |
| }; |
| |
| static void |
| hb_vector_svg_sweep_emit_patch (float a0, hb_color_t c0, |
| float a1, hb_color_t c1, |
| void *user_data) |
| { |
| auto *ctx = (hb_vector_svg_sweep_ctx_t *) user_data; |
| hb_vector_svg_add_sweep_patch (ctx->body, ctx->precision, |
| ctx->cx, ctx->cy, ctx->radius, |
| a0, hb_vector_svg_rgba_from_hb_color (c0), |
| a1, hb_vector_svg_rgba_from_hb_color (c1)); |
| } |
| |
| |
| 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 hb_draw_funcs_t * hb_vector_paint_push_clip_path_start (hb_paint_funcs_t *, void *, void **, void *); |
| static void hb_vector_paint_push_clip_path_end (hb_paint_funcs_t *, void *, 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 *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 (!color) |
| return false; |
| |
| hb_color_t *value = nullptr; |
| if (!paint->custom_palette_colors.has (color_index, &value) || !value) |
| return false; |
| *color = *value; |
| return true; |
| } |
| |
| static inline void free_static_vector_paint_funcs (); |
| static struct hb_vector_paint_funcs_lazy_loader_t |
| : hb_paint_funcs_lazy_loader_t<hb_vector_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_push_clip_path_start_func (funcs, (hb_paint_push_clip_path_start_func_t) hb_vector_paint_push_clip_path_start, nullptr, nullptr); |
| hb_paint_funcs_set_push_clip_path_end_func (funcs, (hb_paint_push_clip_path_end_func_t) hb_vector_paint_push_clip_path_end, 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 (); |
| } |
| |
| hb_paint_funcs_t * |
| hb_vector_paint_svg_funcs_get () |
| { |
| return static_vector_paint_funcs.get_unconst (); |
| } |
| |
| |
| 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; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| |
| 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 (paint->sx (dx)) < 1e-6f && fabsf (paint->sy (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 = paint->defs.scale_precision (); |
| body.append_str ("<g transform=\"matrix("); |
| body.append_num (xx, sprec); |
| body.append_c (','); |
| body.append_num (yx, sprec); |
| body.append_c (','); |
| body.append_num (xy, sprec); |
| body.append_c (','); |
| body.append_num (yy, sprec); |
| body.append_c (','); |
| body.append_num (paint->sx (dx)); |
| body.append_c (','); |
| body.append_num (paint->sy (dy)); |
| body.append_str (")\">\n"); |
| } |
| |
| static void |
| hb_vector_paint_pop_transform (hb_paint_funcs_t *, |
| void *paint_data, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| 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) |
| paint->current_body ().append_str ("</g>\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; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| |
| const char *pfx = paint->id_prefix; |
| unsigned pfx_len = paint->id_prefix_length; |
| |
| paint->path.clear (); |
| { |
| hb_vector_path_sink_t sink = {&paint->path, paint->get_precision (), |
| paint->x_scale_factor, paint->y_scale_factor}; |
| hb_font_draw_glyph (font, glyph, hb_vector_svg_path_draw_funcs_get (), &sink); |
| } |
| |
| unsigned def_id = paint->path_def_count++; |
| paint->defs.append_str ("<path id=\""); |
| paint->defs.append_len (pfx, pfx_len); |
| paint->defs.append_c ('p'); |
| paint->defs.append_unsigned (def_id); |
| paint->defs.append_str ("\" d=\""); |
| paint->defs.append_len (paint->path.arrayZ, paint->path.length); |
| paint->defs.append_str ("\"/>\n"); |
| paint->defs.append_str ("<clipPath id=\""); |
| paint->defs.append_len (pfx, pfx_len); |
| paint->defs.append_str ("clip-p"); |
| paint->defs.append_unsigned (def_id); |
| paint->defs.append_str ("\"><use href=\"#"); |
| paint->defs.append_len (pfx, pfx_len); |
| paint->defs.append_c ('p'); |
| paint->defs.append_unsigned (def_id); |
| paint->defs.append_str ("\"/></clipPath>\n"); |
| |
| paint->current_body ().append_str ("<g clip-path=\"url(#"); |
| paint->current_body ().append_len (pfx, pfx_len); |
| paint->current_body ().append_str ("clip-p"); |
| paint->current_body ().append_unsigned (def_id); |
| paint->current_body ().append_str (")\">\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; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| |
| const char *pfx = paint->id_prefix; |
| unsigned pfx_len = paint->id_prefix_length; |
| unsigned clip_id = paint->clip_rect_counter++; |
| paint->defs.append_str ("<clipPath id=\""); |
| paint->defs.append_len (pfx, pfx_len); |
| paint->defs.append_c ('c'); |
| paint->defs.append_unsigned (clip_id); |
| paint->defs.append_str ("\"><rect x=\""); |
| paint->defs.append_num (paint->sx (xmin)); |
| paint->defs.append_str ("\" y=\""); |
| paint->defs.append_num (paint->sy (ymin)); |
| paint->defs.append_str ("\" width=\""); |
| paint->defs.append_num (paint->sx (xmax - xmin)); |
| paint->defs.append_str ("\" height=\""); |
| paint->defs.append_num (paint->sy (ymax - ymin)); |
| paint->defs.append_str ("\"/></clipPath>\n"); |
| |
| paint->current_body ().append_str ("<g clip-path=\"url(#"); |
| paint->current_body ().append_len (pfx, pfx_len); |
| paint->current_body ().append_c ('c'); |
| paint->current_body ().append_unsigned (clip_id); |
| paint->current_body ().append_str (")\">\n"); |
| } |
| |
| static hb_draw_funcs_t * |
| hb_vector_paint_push_clip_path_start (hb_paint_funcs_t *, |
| void *paint_data, |
| void **draw_data, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!paint->ensure_initialized ())) |
| { |
| *draw_data = nullptr; |
| return nullptr; |
| } |
| |
| paint->path.clear (); |
| paint->clip_path_sink = {&paint->path, paint->get_precision (), |
| paint->x_scale_factor, |
| paint->y_scale_factor}; |
| *draw_data = &paint->clip_path_sink; |
| return hb_vector_svg_path_draw_funcs_get (); |
| } |
| |
| static void |
| hb_vector_paint_push_clip_path_end (hb_paint_funcs_t *, |
| void *paint_data, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| |
| const char *pfx = paint->id_prefix; |
| unsigned pfx_len = paint->id_prefix_length; |
| unsigned clip_id = paint->clip_path_counter++; |
| |
| /* The accumulated path is in font Y-up coords (the |
| * convention used inside per-glyph <use scale(_,-sy)> |
| * wrappers); this clip is emitted at base body level |
| * (free-form between glyphs), so flip its geometry inside |
| * the clipPath via the path's own transform attribute, |
| * keeping the body's <g clip-path> at body Y-down. */ |
| paint->defs.append_str ("<clipPath id=\""); |
| paint->defs.append_len (pfx, pfx_len); |
| paint->defs.append_str ("cp"); |
| paint->defs.append_unsigned (clip_id); |
| paint->defs.append_str ("\"><path transform=\"scale(1,-1)\" d=\""); |
| paint->defs.append_len (paint->path.arrayZ, paint->path.length); |
| paint->defs.append_str ("\"/></clipPath>\n"); |
| |
| paint->current_body ().append_str ("<g clip-path=\"url(#"); |
| paint->current_body ().append_len (pfx, pfx_len); |
| paint->current_body ().append_str ("cp"); |
| paint->current_body ().append_unsigned (clip_id); |
| paint->current_body ().append_str (")\">\n"); |
| } |
| |
| static void |
| hb_vector_paint_pop_clip (hb_paint_funcs_t *, |
| void *paint_data, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| paint->current_body ().append_str ("</g>\n"); |
| } |
| |
| static void |
| hb_vector_paint_color (hb_paint_funcs_t *, |
| void *paint_data, |
| hb_bool_t, |
| hb_color_t color, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| |
| auto &body = paint->current_body (); |
| body.append_str ("<rect x=\"-32767\" y=\"-32767\" width=\"65534\" height=\"65534\" fill=\""); |
| body.append_svg_color (color, true); |
| body.append_str ("\"/>\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; |
| if (unlikely (!paint->ensure_initialized ())) |
| return false; |
| |
| auto &body = paint->current_body (); |
| 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; |
| |
| body.append_str ("<g transform=\"translate("); |
| body.append_num (paint->sx ((float) extents->x_bearing)); |
| body.append_c (','); |
| body.append_num (paint->sy ((float) extents->y_bearing)); |
| body.append_str (") scale("); |
| body.append_num (paint->sx ((float) extents->width) / width); |
| body.append_c (','); |
| body.append_num (paint->sy ((float) extents->height) / height); |
| body.append_str (")\">\n"); |
| |
| body.append_str ("<image href=\"data:image/png;base64,"); |
| body.append_base64 ((const uint8_t *) png_data, len); |
| body.append_str ("\" width=\""); |
| body.append_num ((float) width); |
| body.append_str ("\" height=\""); |
| body.append_num ((float) height); |
| body.append_str ("\"/>\n</g>\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; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| |
| if (!paint->fetch_color_stops (color_line)) |
| return; |
| hb_vector_t<hb_color_stop_t> &stops = paint->color_stops_scratch; |
| |
| /* Sort + rescale stops to [0, 1]; shift the gradient axis |
| * by the original (mn, mx) so the visible gradient stays |
| * put. SVG <stop offset="..."> requires offsets in [0,1]; |
| * out-of-range stops would otherwise be silently clamped. */ |
| float mn, mx; |
| hb_paint_normalize_color_line (stops.arrayZ, stops.length, &mn, &mx); |
| |
| const char *pfx = paint->id_prefix; |
| unsigned pfx_len = paint->id_prefix_length; |
| unsigned grad_id = paint->gradient_counter++; |
| |
| /* Reduce COLR's 3-anchor (P0, P1, P2) to SVG's 2-point |
| * (start, end) gradient. */ |
| float lx0, ly0, lx1, ly1; |
| hb_paint_reduce_linear_anchors (x0, y0, x1, y1, x2, y2, |
| &lx0, &ly0, &lx1, &ly1); |
| float gx0 = lx0 + mn * (lx1 - lx0); |
| float gy0 = ly0 + mn * (ly1 - ly0); |
| float gx1 = lx0 + mx * (lx1 - lx0); |
| float gy1 = ly0 + mx * (ly1 - ly0); |
| |
| paint->defs.append_str ("<linearGradient id=\""); |
| paint->defs.append_len (pfx, pfx_len); |
| paint->defs.append_str ("gr"); |
| paint->defs.append_unsigned (grad_id); |
| paint->defs.append_str ("\" gradientUnits=\"userSpaceOnUse\" x1=\""); |
| paint->defs.append_num (paint->sx (gx0)); |
| paint->defs.append_str ("\" y1=\""); |
| paint->defs.append_num (paint->sy (gy0)); |
| paint->defs.append_str ("\" x2=\""); |
| paint->defs.append_num (paint->sx (gx1)); |
| paint->defs.append_str ("\" y2=\""); |
| paint->defs.append_num (paint->sy (gy1)); |
| paint->defs.append_str ("\" spreadMethod=\""); |
| paint->defs.append_str (hb_vector_svg_extend_mode_str (hb_color_line_get_extend (color_line))); |
| paint->defs.append_str ("\">\n"); |
| hb_vector_svg_emit_color_stops (paint, &paint->defs, &stops); |
| paint->defs.append_str ("</linearGradient>\n"); |
| |
| paint->current_body ().append_str ( |
| "<rect x=\"-32767\" y=\"-32767\" width=\"65534\" height=\"65534\" fill=\"url(#"); |
| paint->current_body ().append_len (pfx, pfx_len); |
| paint->current_body ().append_str ("gr"); |
| paint->current_body ().append_unsigned (grad_id); |
| paint->current_body ().append_str (")\"/>\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; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| |
| if (!paint->fetch_color_stops (color_line)) |
| return; |
| hb_vector_t<hb_color_stop_t> &stops = paint->color_stops_scratch; |
| |
| float mn, mx; |
| hb_paint_normalize_color_line (stops.arrayZ, stops.length, &mn, &mx); |
| |
| /* Shift centers + radii by (mn, mx) along the gradient axis |
| * to compensate for rescaling stops to [0, 1]. */ |
| float gx0 = x0 + mn * (x1 - x0); |
| float gy0 = y0 + mn * (y1 - y0); |
| float gr0 = r0 + mn * (r1 - r0); |
| float gx1 = x0 + mx * (x1 - x0); |
| float gy1 = y0 + mx * (y1 - y0); |
| float gr1 = r0 + mx * (r1 - r0); |
| |
| const char *pfx = paint->id_prefix; |
| unsigned pfx_len = paint->id_prefix_length; |
| unsigned grad_id = paint->gradient_counter++; |
| |
| paint->defs.append_str ("<radialGradient id=\""); |
| paint->defs.append_len (pfx, pfx_len); |
| paint->defs.append_str ("gr"); |
| paint->defs.append_unsigned (grad_id); |
| paint->defs.append_str ("\" gradientUnits=\"userSpaceOnUse\" cx=\""); |
| paint->defs.append_num (paint->sx (gx1)); |
| paint->defs.append_str ("\" cy=\""); |
| paint->defs.append_num (paint->sy (gy1)); |
| paint->defs.append_str ("\" r=\""); |
| paint->defs.append_num (paint->sx (gr1)); |
| paint->defs.append_str ("\" fx=\""); |
| paint->defs.append_num (paint->sx (gx0)); |
| paint->defs.append_str ("\" fy=\""); |
| paint->defs.append_num (paint->sy (gy0)); |
| if (gr0 > 0) |
| { |
| paint->defs.append_str ("\" fr=\""); |
| paint->defs.append_num (paint->sx (gr0)); |
| } |
| paint->defs.append_str ("\" spreadMethod=\""); |
| paint->defs.append_str (hb_vector_svg_extend_mode_str (hb_color_line_get_extend (color_line))); |
| paint->defs.append_str ("\">\n"); |
| hb_vector_svg_emit_color_stops (paint, &paint->defs, &stops); |
| paint->defs.append_str ("</radialGradient>\n"); |
| |
| paint->current_body ().append_str ( |
| "<rect x=\"-32767\" y=\"-32767\" width=\"65534\" height=\"65534\" fill=\"url(#"); |
| paint->current_body ().append_len (pfx, pfx_len); |
| paint->current_body ().append_str ("gr"); |
| paint->current_body ().append_unsigned (grad_id); |
| paint->current_body ().append_str (")\"/>\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; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| |
| if (!paint->fetch_color_stops (color_line)) |
| return; |
| hb_vector_t<hb_color_stop_t> &stops = paint->color_stops_scratch; |
| |
| float mn, mx; |
| hb_paint_normalize_color_line (stops.arrayZ, stops.length, &mn, &mx); |
| |
| /* Shift the angle range to compensate for rescaling stops |
| * to [0, 1]. */ |
| float ga0 = start_angle + mn * (end_angle - start_angle); |
| float ga1 = start_angle + mx * (end_angle - start_angle); |
| |
| hb_vector_svg_sweep_ctx_t ctx { |
| &paint->current_body (), paint->get_precision (), paint->sx (cx), paint->sy (cy), 32767.f |
| }; |
| hb_paint_sweep_gradient_tiles (stops.arrayZ, stops.length, |
| hb_color_line_get_extend (color_line), |
| ga0, ga1, |
| hb_vector_svg_sweep_emit_patch, |
| &ctx); |
| } |
| |
| static void |
| hb_vector_paint_push_group (hb_paint_funcs_t *, |
| void *paint_data, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| if (unlikely (!paint->group_stack.push_or_fail (hb_vector_buf_t {}))) |
| return; |
| } |
| |
| 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; |
| if (unlikely (!paint->ensure_initialized ())) |
| return; |
| if (paint->group_stack.length < 2) |
| return; |
| |
| hb_vector_buf_t group = paint->group_stack.pop (); |
| auto &body = paint->current_body (); |
| |
| const char *blend = hb_vector_svg_composite_mode_str (mode); |
| if (blend) |
| { |
| body.append_str ("<g style=\"mix-blend-mode:"); |
| body.append_str (blend); |
| body.append_str ("\">\n"); |
| body.append_len (group.arrayZ, group.length); |
| body.append_str ("</g>\n"); |
| } |
| else |
| body.append_len (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; |
| if (unlikely (!paint->ensure_initialized ())) |
| return false; |
| if (unlikely (paint->color_glyph_depth >= HB_MAX_NESTING_LEVEL)) |
| return false; |
| if (unlikely (hb_set_has (paint->active_color_glyphs, glyph))) |
| return false; |
| |
| paint->color_glyph_depth++; |
| hb_set_add (paint->active_color_glyphs, glyph); |
| |
| hb_font_paint_glyph (font, glyph, |
| hb_vector_paint_svg_funcs_get (), |
| paint, |
| paint->palette, |
| paint->foreground); |
| hb_set_del (paint->active_color_glyphs, glyph); |
| paint->color_glyph_depth--; |
| return true; |
| } |
| |
| |
| |
| |
| hb_blob_t * |
| hb_vector_paint_render_svg (hb_vector_paint_t *paint) |
| { |
| if (!paint->has_extents) |
| return nullptr; |
| |
| if (unlikely (!paint->ensure_initialized ())) |
| return nullptr; |
| |
| hb_vector_buf_t out; |
| hb_buf_recover_recycled (paint->recycled_blob, &out); |
| unsigned estimated = paint->defs.length + |
| paint->group_stack.arrayZ[0].length + |
| 320; |
| out.alloc (estimated); |
| float vb_x = paint->extents.x; |
| float vb_y = -(paint->extents.y + paint->extents.height); |
| float vb_w = paint->extents.width; |
| float vb_h = paint->extents.height; |
| out.append_str ("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\""); |
| out.append_num (vb_x); |
| out.append_c (' '); |
| out.append_num (vb_y); |
| out.append_c (' '); |
| out.append_num (vb_w); |
| out.append_c (' '); |
| out.append_num (vb_h); |
| out.append_str ("\" width=\""); |
| out.append_num (vb_w); |
| out.append_str ("\" height=\""); |
| out.append_num (vb_h); |
| out.append_str ("\">\n"); |
| |
| if (paint->defs.length) |
| { |
| out.append_str ("<defs>\n"); |
| out.append_len (paint->defs.arrayZ, paint->defs.length); |
| out.append_str ("</defs>\n"); |
| } |
| |
| if (hb_color_get_alpha (paint->background)) |
| { |
| out.append_str ("<rect x=\""); |
| out.append_num (vb_x); |
| out.append_str ("\" y=\""); |
| out.append_num (vb_y); |
| out.append_str ("\" width=\""); |
| out.append_num (vb_w); |
| out.append_str ("\" height=\""); |
| out.append_num (vb_h); |
| out.append_str ("\" fill=\"rgb("); |
| out.append_unsigned (hb_color_get_red (paint->background)); |
| out.append_c (','); |
| out.append_unsigned (hb_color_get_green (paint->background)); |
| out.append_c (','); |
| out.append_unsigned (hb_color_get_blue (paint->background)); |
| out.append_str (")\""); |
| if (hb_color_get_alpha (paint->background) < 255) |
| { |
| out.append_str (" fill-opacity=\""); |
| out.append_num (hb_color_get_alpha (paint->background) / 255.f, 4); |
| out.append_c ('"'); |
| } |
| out.append_str ("/>\n"); |
| } |
| |
| out.append_str ("<g transform=\"scale(1,-1)\">\n"); |
| out.append_len (paint->group_stack.arrayZ[0].arrayZ, paint->group_stack.arrayZ[0].length); |
| out.append_str ("</g>\n"); |
| |
| out.append_str ("</svg>\n"); |
| |
| hb_blob_t *blob = hb_buf_blob_from (&paint->recycled_blob, &out); |
| |
| hb_vector_paint_clear (paint); |
| |
| return blob; |
| } |