/* * 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-gradient.hh" #include "hb-raster-svg-base.hh" static bool svg_parse_gradient_stop (hb_svg_xml_parser_t &parser, hb_svg_gradient_t &grad, hb_paint_funcs_t *pfuncs, void *paint_data, hb_color_t foreground, hb_face_t *face, unsigned palette) { const unsigned SVG_MAX_GRADIENT_STOPS = 1024; if (grad.stops.length >= SVG_MAX_GRADIENT_STOPS) return true; hb_svg_attr_view_t attrs (parser); hb_svg_str_t style = attrs.get ("style"); hb_svg_style_props_t style_props; svg_parse_style_props (style, &style_props); hb_svg_str_t offset_str = svg_pick_attr_or_style (parser, style_props.offset, "offset"); hb_svg_str_t color_str = svg_pick_attr_or_style (parser, style_props.stop_color, "stop-color"); hb_svg_str_t opacity_str = svg_pick_attr_or_style (parser, style_props.stop_opacity, "stop-opacity"); hb_svg_str_t display_str = svg_pick_attr_or_style (parser, style_props.display, "display"); hb_svg_str_t visibility_str = svg_pick_attr_or_style (parser, style_props.visibility, "visibility"); if (display_str.trim ().eq_ascii_ci ("none")) return true; hb_svg_str_t visibility_trim = visibility_str.trim (); if (visibility_trim.eq_ascii_ci ("hidden") || visibility_trim.eq_ascii_ci ("collapse")) return true; float offset = 0; if (offset_str.len) offset = hb_clamp (svg_parse_number_or_percent (offset_str, nullptr), 0.f, 1.f); if (grad.stops.length) offset = hb_max (offset, grad.stops.arrayZ[grad.stops.length - 1].offset); bool is_none = false; hb_color_t color = HB_COLOR (0, 0, 0, 255); bool is_current_color = false; if (color_str.len && !svg_str_is_inherit (color_str)) { is_current_color = color_str.trim ().eq_ascii_ci ("currentColor"); color = hb_raster_svg_parse_color (color_str, pfuncs, paint_data, foreground, face, palette, &is_none); } if (opacity_str.len && !svg_str_is_inherit (opacity_str)) { float opacity = svg_parse_float_clamped01 (opacity_str); 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) * opacity + 0.5f)); } hb_svg_gradient_stop_t stop; stop.offset = offset; stop.color = color; stop.is_current_color = is_current_color; grad.stops.push (stop); return !grad.stops.in_error (); } static void svg_parse_gradient_attrs (hb_svg_xml_parser_t &parser, hb_svg_gradient_t &grad) { hb_svg_style_props_t style_props; svg_parse_style_props (parser.find_attr ("style"), &style_props); hb_svg_str_t spread_str = svg_pick_attr_or_style (parser, style_props.spread_method, "spreadMethod").trim (); if (spread_str.eq_ascii_ci ("reflect")) { grad.spread = HB_PAINT_EXTEND_REFLECT; grad.has_spread = true; } else if (spread_str.eq_ascii_ci ("repeat")) { grad.spread = HB_PAINT_EXTEND_REPEAT; grad.has_spread = true; } else if (spread_str.eq_ascii_ci ("pad")) { grad.spread = HB_PAINT_EXTEND_PAD; grad.has_spread = true; } hb_svg_str_t units_str = svg_pick_attr_or_style (parser, style_props.gradient_units, "gradientUnits").trim (); if (units_str.eq_ascii_ci ("userSpaceOnUse")) { grad.units_user_space = true; grad.has_units_user_space = true; } else if (units_str.eq_ascii_ci ("objectBoundingBox")) { grad.units_user_space = false; grad.has_units_user_space = true; } hb_svg_str_t transform_str = svg_pick_attr_or_style (parser, style_props.gradient_transform, "gradientTransform"); if (transform_str.len) { grad.has_gradient_transform = true; hb_raster_svg_parse_transform (transform_str, &grad.gradient_transform); } hb_svg_str_t href = hb_raster_svg_find_href_attr (parser); if (href.len) { hb_svg_str_t href_id; if (hb_raster_svg_parse_local_id_ref (href, &href_id, nullptr)) grad.href_id = hb_bytes_t (href_id.data, href_id.len); } } static void svg_parse_gradient_geometry_attrs (hb_svg_xml_parser_t &parser, hb_svg_gradient_t &grad) { hb_svg_style_props_t style_props; svg_parse_style_props (parser.find_attr ("style"), &style_props); if (grad.type == SVG_GRADIENT_LINEAR) { hb_svg_str_t x1_str = svg_pick_attr_or_style (parser, style_props.x1, "x1"); hb_svg_str_t y1_str = svg_pick_attr_or_style (parser, style_props.y1, "y1"); hb_svg_str_t x2_str = svg_pick_attr_or_style (parser, style_props.x2, "x2"); hb_svg_str_t y2_str = svg_pick_attr_or_style (parser, style_props.y2, "y2"); if (x1_str.len) { grad.x1 = svg_parse_number_or_percent (x1_str, nullptr); grad.has_x1 = true; } if (y1_str.len) { grad.y1 = svg_parse_number_or_percent (y1_str, nullptr); grad.has_y1 = true; } if (x2_str.len) { grad.x2 = svg_parse_number_or_percent (x2_str, nullptr); grad.has_x2 = true; } if (y2_str.len) { grad.y2 = svg_parse_number_or_percent (y2_str, nullptr); grad.has_y2 = true; } if (!grad.has_x2) grad.x2 = 1.f; } else { hb_svg_str_t cx_str = svg_pick_attr_or_style (parser, style_props.cx, "cx"); hb_svg_str_t cy_str = svg_pick_attr_or_style (parser, style_props.cy, "cy"); hb_svg_str_t r_str = svg_pick_attr_or_style (parser, style_props.r, "r"); hb_svg_str_t fx_str = svg_pick_attr_or_style (parser, style_props.fx, "fx"); hb_svg_str_t fy_str = svg_pick_attr_or_style (parser, style_props.fy, "fy"); hb_svg_str_t fr_str = svg_pick_attr_or_style (parser, style_props.fr, "fr"); if (cx_str.len) { grad.cx = svg_parse_number_or_percent (cx_str, nullptr); grad.has_cx = true; } if (cy_str.len) { grad.cy = svg_parse_number_or_percent (cy_str, nullptr); grad.has_cy = true; } if (r_str.len) { grad.r = svg_parse_number_or_percent (r_str, nullptr); grad.has_r = true; } if (fx_str.len) { grad.fx = svg_parse_number_or_percent (fx_str, nullptr); grad.has_fx = true; } if (fy_str.len) { grad.fy = svg_parse_number_or_percent (fy_str, nullptr); grad.has_fy = true; } if (fr_str.len) { grad.fr = svg_parse_number_or_percent (fr_str, nullptr); grad.has_fr = true; } } } static void svg_parse_gradient_children (hb_svg_defs_t *defs, hb_svg_xml_parser_t &parser, hb_svg_gradient_t &grad, hb_svg_str_t *id, hb_paint_funcs_t *pfuncs, void *paint_data, hb_color_t foreground, hb_face_t *face, unsigned palette) { int gdepth = 1; bool had_alloc_failure = false; while (gdepth > 0) { hb_svg_token_type_t gt = parser.next (); if (gt == SVG_TOKEN_EOF) break; if (gt == SVG_TOKEN_CLOSE_TAG) { gdepth--; continue; } if ((gt == SVG_TOKEN_OPEN_TAG || gt == SVG_TOKEN_SELF_CLOSE_TAG) && parser.tag_name.eq ("stop")) if (unlikely (!svg_parse_gradient_stop (parser, grad, pfuncs, paint_data, foreground, face, palette))) had_alloc_failure = true; if (gt == SVG_TOKEN_OPEN_TAG && !parser.tag_name.eq ("stop")) gdepth++; } if (had_alloc_failure || defs->gradients.in_error ()) *id = {}; } void hb_raster_svg_process_gradient_def (hb_svg_defs_t *defs, hb_svg_xml_parser_t &parser, hb_svg_token_type_t tok, hb_svg_gradient_type_t type, hb_paint_funcs_t *pfuncs, void *paint_data, hb_color_t foreground, hb_face_t *face, unsigned palette) { hb_svg_gradient_t grad; grad.type = type; svg_parse_gradient_geometry_attrs (parser, grad); svg_parse_gradient_attrs (parser, grad); hb_svg_str_t id = parser.find_attr ("id"); if (tok == SVG_TOKEN_OPEN_TAG) svg_parse_gradient_children (defs, parser, grad, &id, pfuncs, paint_data, foreground, face, palette); if (id.len) (void) defs->add_gradient (hb_bytes_t (id.data, id.len), grad); } #endif /* !HB_NO_RASTER_SVG */