| /* |
| * 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; |
| return grad.stops.push_or_fail (stop); |
| } |
| |
| 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 */ |