| /* |
| * 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 |
| */ |
| |
| #ifndef HB_NO_RASTER_SVG |
| |
| #include "hb.hh" |
| |
| #include "hb-raster-svg-fill.hh" |
| |
| #include "hb-raster-svg-base.hh" |
| #include "hb-raster-svg-color.hh" |
| #include "hb-decycler.hh" |
| |
| #include <math.h> |
| |
| struct hb_svg_color_line_data_t |
| { |
| const hb_svg_gradient_t *grad; |
| hb_paint_extend_t extend; |
| float alpha_scale; |
| hb_color_t current_color; |
| }; |
| |
| static unsigned |
| svg_color_line_get_stops (hb_color_line_t *color_line HB_UNUSED, |
| void *color_line_data, |
| unsigned int start, |
| unsigned int *count, |
| hb_color_stop_t *color_stops, |
| void *user_data HB_UNUSED) |
| { |
| hb_svg_color_line_data_t *cl = (hb_svg_color_line_data_t *) color_line_data; |
| const hb_svg_gradient_t *grad = cl->grad; |
| unsigned total = grad->stops.length; |
| |
| if (count) |
| { |
| unsigned n = hb_min (*count, total > start ? total - start : 0u); |
| for (unsigned i = 0; i < n; i++) |
| { |
| const hb_svg_gradient_stop_t &stop = grad->stops[start + i]; |
| color_stops[i].offset = stop.offset; |
| color_stops[i].is_foreground = false; |
| hb_color_t c = stop.is_current_color ? cl->current_color : stop.color; |
| if (stop.is_current_color) |
| { |
| uint8_t a = (uint8_t) hb_raster_div255 (hb_color_get_alpha (c) * hb_color_get_alpha (stop.color)); |
| c = HB_COLOR (hb_color_get_blue (c), |
| hb_color_get_green (c), |
| hb_color_get_red (c), |
| a); |
| } |
| if (cl->alpha_scale < 1.f) |
| { |
| uint8_t a = (uint8_t) hb_clamp ((int) (hb_color_get_alpha (c) * cl->alpha_scale + 0.5f), 0, 255); |
| c = HB_COLOR (hb_color_get_blue (c), |
| hb_color_get_green (c), |
| hb_color_get_red (c), |
| a); |
| } |
| color_stops[i].color = c; |
| } |
| *count = n; |
| } |
| return total; |
| } |
| |
| static hb_paint_extend_t |
| svg_color_line_get_extend (hb_color_line_t *color_line HB_UNUSED, |
| void *color_line_data, |
| void *user_data HB_UNUSED) |
| { |
| hb_svg_color_line_data_t *cl = (hb_svg_color_line_data_t *) color_line_data; |
| return cl->extend; |
| } |
| |
| static bool |
| svg_parse_paint_url_with_fallback (hb_svg_str_t s, |
| hb_svg_str_t *out_id, |
| hb_svg_str_t *fallback) |
| { |
| if (out_id) *out_id = {}; |
| hb_svg_str_t id; |
| if (!hb_raster_svg_parse_local_id_ref (s, &id, fallback)) |
| return false; |
| if (!s.trim ().starts_with_ascii_ci ("url(")) |
| return false; |
| if (out_id) *out_id = id; |
| return id.len > 0; |
| } |
| |
| void |
| hb_raster_svg_emit_fill (const hb_svg_fill_context_t *ctx, |
| hb_svg_str_t fill_str, |
| float fill_opacity, |
| const hb_extents_t<> *object_bbox, |
| hb_color_t current_color) |
| { |
| bool is_none = false; |
| |
| hb_svg_str_t url_id; |
| hb_svg_str_t fallback_paint; |
| bool has_url_paint = svg_parse_paint_url_with_fallback (fill_str, &url_id, &fallback_paint); |
| |
| const hb_svg_gradient_t *grad = has_url_paint ? ctx->defs->find_gradient (hb_bytes_t (url_id.data, url_id.len)) : nullptr; |
| if (has_url_paint && !grad) |
| { |
| if (fallback_paint.len) |
| hb_raster_svg_emit_fill (ctx, fallback_paint, fill_opacity, object_bbox, current_color); |
| return; |
| } |
| if (grad) |
| { |
| const unsigned SVG_MAX_GRADIENT_REF_DEPTH = 1024; |
| hb_vector_t<const hb_svg_gradient_t *> chain; |
| hb_decycler_t decycler; |
| const hb_svg_gradient_t *cur = grad; |
| while (cur) |
| { |
| if (chain.length >= SVG_MAX_GRADIENT_REF_DEPTH) |
| break; |
| |
| hb_decycler_node_t node (decycler); |
| if (unlikely (!node.visit ((uintptr_t) cur))) |
| break; |
| |
| if (unlikely (!chain.push_or_fail (cur))) |
| { |
| if (fallback_paint.len) |
| hb_raster_svg_emit_fill (ctx, fallback_paint, fill_opacity, object_bbox, current_color); |
| return; |
| } |
| |
| if (!cur->href_id.length) break; |
| const hb_svg_gradient_t *next = ctx->defs->find_gradient (cur->href_id); |
| if (!next) break; |
| cur = next; |
| } |
| if (!chain.length) |
| { |
| if (fallback_paint.len) |
| hb_raster_svg_emit_fill (ctx, fallback_paint, fill_opacity, object_bbox, current_color); |
| return; |
| } |
| |
| if (!chain.length) { |
| if (fallback_paint.len) |
| hb_raster_svg_emit_fill (ctx, fallback_paint, fill_opacity, object_bbox, current_color); |
| return; |
| } |
| hb_svg_gradient_t effective = *chain.arrayZ[chain.length - 1]; |
| for (int i = (int) chain.length - 2; i >= 0; i--) |
| { |
| const hb_svg_gradient_t *g = chain.arrayZ[i]; |
| effective.type = g->type; |
| if (g->stops.length) effective.stops = g->stops; |
| if (g->has_spread) { effective.spread = g->spread; effective.has_spread = true; } |
| if (g->has_units_user_space) |
| { |
| effective.units_user_space = g->units_user_space; |
| effective.has_units_user_space = true; |
| } |
| if (g->has_gradient_transform) |
| { |
| effective.gradient_transform = g->gradient_transform; |
| effective.has_gradient_transform = true; |
| } |
| if (g->has_x1) { effective.x1 = g->x1; effective.has_x1 = true; } |
| if (g->has_y1) { effective.y1 = g->y1; effective.has_y1 = true; } |
| if (g->has_x2) { effective.x2 = g->x2; effective.has_x2 = true; } |
| if (g->has_y2) { effective.y2 = g->y2; effective.has_y2 = true; } |
| if (g->has_cx) { effective.cx = g->cx; effective.has_cx = true; } |
| if (g->has_cy) { effective.cy = g->cy; effective.has_cy = true; } |
| if (g->has_r) { effective.r = g->r; effective.has_r = true; } |
| if (g->has_fx) { effective.fx = g->fx; effective.has_fx = true; } |
| if (g->has_fy) { effective.fy = g->fy; effective.has_fy = true; } |
| if (g->has_fr) { effective.fr = g->fr; effective.has_fr = true; } |
| } |
| if (!effective.stops.length) |
| { |
| if (fallback_paint.len) |
| hb_raster_svg_emit_fill (ctx, fallback_paint, fill_opacity, object_bbox, current_color); |
| return; |
| } |
| |
| hb_svg_color_line_data_t cl_data; |
| cl_data.grad = &effective; |
| cl_data.extend = effective.spread; |
| cl_data.alpha_scale = hb_clamp (fill_opacity, 0.f, 1.f); |
| cl_data.current_color = current_color; |
| |
| hb_color_line_t cl = { |
| &cl_data, |
| svg_color_line_get_stops, nullptr, |
| svg_color_line_get_extend, nullptr |
| }; |
| |
| bool has_bbox_transform = !effective.units_user_space && object_bbox && !object_bbox->is_empty (); |
| bool pushed_bbox_transform = false; |
| if (has_bbox_transform) |
| { |
| float w = object_bbox->xmax - object_bbox->xmin; |
| float h = object_bbox->ymax - object_bbox->ymin; |
| if (std::isfinite (w) && std::isfinite (h) && w > 0.f && h > 0.f) |
| pushed_bbox_transform = hb_raster_svg_push_transform (ctx->pfuncs, ctx->paint, |
| w, 0, 0, h, |
| object_bbox->xmin, object_bbox->ymin); |
| else |
| has_bbox_transform = false; |
| } |
| |
| bool pushed_gradient_transform = false; |
| if (effective.has_gradient_transform) |
| pushed_gradient_transform = hb_raster_svg_push_transform (ctx->pfuncs, ctx->paint, |
| effective.gradient_transform.xx, |
| effective.gradient_transform.yx, |
| effective.gradient_transform.xy, |
| effective.gradient_transform.yy, |
| effective.gradient_transform.dx, |
| effective.gradient_transform.dy); |
| |
| if (effective.type == SVG_GRADIENT_LINEAR) |
| { |
| hb_paint_linear_gradient (ctx->pfuncs, ctx->paint, &cl, |
| effective.x1, effective.y1, |
| effective.x2, effective.y2, |
| effective.x2 - (effective.y2 - effective.y1), |
| effective.y2 + (effective.x2 - effective.x1)); |
| } |
| else |
| { |
| if (!std::isfinite (effective.r) || effective.r < 0.f) |
| { |
| if (pushed_gradient_transform) |
| hb_paint_pop_transform (ctx->pfuncs, ctx->paint); |
| if (pushed_bbox_transform) |
| hb_paint_pop_transform (ctx->pfuncs, ctx->paint); |
| if (fallback_paint.len) |
| hb_raster_svg_emit_fill (ctx, fallback_paint, fill_opacity, object_bbox, current_color); |
| return; |
| } |
| |
| float fx = effective.has_fx ? effective.fx : effective.cx; |
| float fy = effective.has_fy ? effective.fy : effective.cy; |
| float fr = effective.has_fr ? effective.fr : 0.f; |
| if (!std::isfinite (fr) || fr < 0.f) |
| fr = 0.f; |
| fr = hb_min (fr, effective.r); |
| |
| hb_paint_radial_gradient (ctx->pfuncs, ctx->paint, &cl, |
| fx, fy, fr, |
| effective.cx, effective.cy, effective.r); |
| } |
| |
| if (pushed_gradient_transform) |
| hb_paint_pop_transform (ctx->pfuncs, ctx->paint); |
| if (pushed_bbox_transform) |
| hb_paint_pop_transform (ctx->pfuncs, ctx->paint); |
| |
| return; |
| } |
| |
| hb_color_t color = hb_raster_svg_parse_color (fill_str, |
| ctx->pfuncs, |
| ctx->paint, |
| current_color, |
| hb_font_get_face (ctx->font), |
| ctx->palette, |
| &is_none); |
| if (is_none) return; |
| |
| if (fill_opacity < 1.f) |
| color = HB_COLOR (hb_color_get_blue (color), |
| hb_color_get_green (color), |
| hb_color_get_red (color), |
| (uint8_t) (hb_color_get_alpha (color) * fill_opacity + 0.5f)); |
| |
| hb_paint_color (ctx->pfuncs, ctx->paint, false, color); |
| } |
| |
| #endif /* !HB_NO_RASTER_SVG */ |