Coverage Report

Created: 2026-03-31 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/harfbuzz/src/hb-raster-svg-gradient.cc
Line
Count
Source
1
/*
2
 * Copyright © 2026  Behdad Esfahbod
3
 *
4
 *  This is part of HarfBuzz, a text shaping library.
5
 *
6
 * Permission is hereby granted, without written agreement and without
7
 * license or royalty fees, to use, copy, modify, and distribute this
8
 * software and its documentation for any purpose, provided that the
9
 * above copyright notice and the following two paragraphs appear in
10
 * all copies of this software.
11
 *
12
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16
 * DAMAGE.
17
 *
18
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
21
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23
 *
24
 * Author(s): Behdad Esfahbod
25
 */
26
27
#ifndef HB_NO_RASTER_SVG
28
29
#include "hb.hh"
30
31
#include "hb-raster-svg-gradient.hh"
32
33
#include "hb-raster-svg-base.hh"
34
35
static bool
36
svg_parse_gradient_stop (hb_svg_xml_parser_t &parser,
37
                         hb_svg_gradient_t &grad,
38
                         hb_paint_funcs_t *pfuncs,
39
                         void *paint_data,
40
                         hb_color_t foreground,
41
                         hb_face_t *face,
42
                         unsigned palette)
43
6.32k
{
44
6.32k
  const unsigned SVG_MAX_GRADIENT_STOPS = 1024;
45
6.32k
  if (grad.stops.length >= SVG_MAX_GRADIENT_STOPS)
46
0
    return true;
47
48
6.32k
  hb_svg_attr_view_t attrs (parser);
49
6.32k
  hb_svg_str_t style = attrs.get ("style");
50
6.32k
  hb_svg_style_props_t style_props;
51
6.32k
  svg_parse_style_props (style, &style_props);
52
6.32k
  hb_svg_str_t offset_str = svg_pick_attr_or_style (parser, style_props.offset, "offset");
53
6.32k
  hb_svg_str_t color_str = svg_pick_attr_or_style (parser, style_props.stop_color, "stop-color");
54
6.32k
  hb_svg_str_t opacity_str = svg_pick_attr_or_style (parser, style_props.stop_opacity, "stop-opacity");
55
6.32k
  hb_svg_str_t display_str = svg_pick_attr_or_style (parser, style_props.display, "display");
56
6.32k
  hb_svg_str_t visibility_str = svg_pick_attr_or_style (parser, style_props.visibility, "visibility");
57
58
6.32k
  if (display_str.trim ().eq_ascii_ci ("none"))
59
0
    return true;
60
6.32k
  hb_svg_str_t visibility_trim = visibility_str.trim ();
61
6.32k
  if (visibility_trim.eq_ascii_ci ("hidden") ||
62
6.32k
      visibility_trim.eq_ascii_ci ("collapse"))
63
0
    return true;
64
65
6.32k
  float offset = 0;
66
6.32k
  if (offset_str.len)
67
6.15k
    offset = hb_clamp (svg_parse_number_or_percent (offset_str, nullptr), 0.f, 1.f);
68
6.32k
  if (grad.stops.length)
69
4.49k
    offset = hb_max (offset, grad.stops.arrayZ[grad.stops.length - 1].offset);
70
71
6.32k
  bool is_none = false;
72
6.32k
  hb_color_t color = HB_COLOR (0, 0, 0, 255);
73
6.32k
  bool is_current_color = false;
74
6.32k
  if (color_str.len && !svg_str_is_inherit (color_str))
75
5.96k
  {
76
5.96k
    is_current_color = color_str.trim ().eq_ascii_ci ("currentColor");
77
5.96k
    color = hb_raster_svg_parse_color (color_str, pfuncs, paint_data, foreground, face, palette, &is_none);
78
5.96k
  }
79
80
6.32k
  if (opacity_str.len && !svg_str_is_inherit (opacity_str))
81
0
  {
82
0
    float opacity = svg_parse_float_clamped01 (opacity_str);
83
0
    color = HB_COLOR (hb_color_get_blue (color),
84
0
                      hb_color_get_green (color),
85
0
                      hb_color_get_red (color),
86
0
                      (uint8_t) (hb_color_get_alpha (color) * opacity + 0.5f));
87
0
  }
88
89
6.32k
  hb_svg_gradient_stop_t stop;
90
6.32k
  stop.offset = offset;
91
6.32k
  stop.color = color;
92
6.32k
  stop.is_current_color = is_current_color;
93
6.32k
  return grad.stops.push_or_fail (stop);
94
6.32k
}
95
96
static void
97
svg_parse_gradient_attrs (hb_svg_xml_parser_t &parser,
98
                          hb_svg_gradient_t &grad)
99
2.02k
{
100
2.02k
  hb_svg_style_props_t style_props;
101
2.02k
  svg_parse_style_props (parser.find_attr ("style"), &style_props);
102
103
2.02k
  hb_svg_str_t spread_str = svg_pick_attr_or_style (parser, style_props.spread_method, "spreadMethod").trim ();
104
2.02k
  if (spread_str.eq_ascii_ci ("reflect"))
105
0
  {
106
0
    grad.spread = HB_PAINT_EXTEND_REFLECT;
107
0
    grad.has_spread = true;
108
0
  }
109
2.02k
  else if (spread_str.eq_ascii_ci ("repeat"))
110
0
  {
111
0
    grad.spread = HB_PAINT_EXTEND_REPEAT;
112
0
    grad.has_spread = true;
113
0
  }
114
2.02k
  else if (spread_str.eq_ascii_ci ("pad"))
115
0
  {
116
0
    grad.spread = HB_PAINT_EXTEND_PAD;
117
0
    grad.has_spread = true;
118
0
  }
119
120
2.02k
  hb_svg_str_t units_str = svg_pick_attr_or_style (parser, style_props.gradient_units, "gradientUnits").trim ();
121
2.02k
  if (units_str.eq_ascii_ci ("userSpaceOnUse"))
122
1.55k
  {
123
1.55k
    grad.units_user_space = true;
124
1.55k
    grad.has_units_user_space = true;
125
1.55k
  }
126
474
  else if (units_str.eq_ascii_ci ("objectBoundingBox"))
127
0
  {
128
0
    grad.units_user_space = false;
129
0
    grad.has_units_user_space = true;
130
0
  }
131
132
2.02k
  hb_svg_str_t transform_str = svg_pick_attr_or_style (parser, style_props.gradient_transform, "gradientTransform");
133
2.02k
  if (transform_str.len)
134
891
  {
135
891
    grad.has_gradient_transform = true;
136
891
    hb_raster_svg_parse_transform (transform_str, &grad.gradient_transform);
137
891
  }
138
139
2.02k
  hb_svg_str_t href = hb_raster_svg_find_href_attr (parser);
140
2.02k
  if (href.len)
141
0
  {
142
0
    hb_svg_str_t href_id;
143
0
    if (hb_raster_svg_parse_local_id_ref (href, &href_id, nullptr))
144
0
      grad.href_id = hb_bytes_t (href_id.data, href_id.len);
145
0
  }
146
2.02k
}
147
148
static void
149
svg_parse_gradient_geometry_attrs (hb_svg_xml_parser_t &parser,
150
                                   hb_svg_gradient_t &grad)
151
2.02k
{
152
2.02k
  hb_svg_style_props_t style_props;
153
2.02k
  svg_parse_style_props (parser.find_attr ("style"), &style_props);
154
2.02k
  if (grad.type == SVG_GRADIENT_LINEAR)
155
1.13k
  {
156
1.13k
    hb_svg_str_t x1_str = svg_pick_attr_or_style (parser, style_props.x1, "x1");
157
1.13k
    hb_svg_str_t y1_str = svg_pick_attr_or_style (parser, style_props.y1, "y1");
158
1.13k
    hb_svg_str_t x2_str = svg_pick_attr_or_style (parser, style_props.x2, "x2");
159
1.13k
    hb_svg_str_t y2_str = svg_pick_attr_or_style (parser, style_props.y2, "y2");
160
1.13k
    if (x1_str.len) { grad.x1 = svg_parse_number_or_percent (x1_str, nullptr); grad.has_x1 = true; }
161
1.13k
    if (y1_str.len) { grad.y1 = svg_parse_number_or_percent (y1_str, nullptr); grad.has_y1 = true; }
162
1.13k
    if (x2_str.len) { grad.x2 = svg_parse_number_or_percent (x2_str, nullptr); grad.has_x2 = true; }
163
1.13k
    if (y2_str.len) { grad.y2 = svg_parse_number_or_percent (y2_str, nullptr); grad.has_y2 = true; }
164
165
1.13k
    if (!grad.has_x2)
166
230
      grad.x2 = 1.f;
167
1.13k
  }
168
894
  else
169
894
  {
170
894
    hb_svg_str_t cx_str = svg_pick_attr_or_style (parser, style_props.cx, "cx");
171
894
    hb_svg_str_t cy_str = svg_pick_attr_or_style (parser, style_props.cy, "cy");
172
894
    hb_svg_str_t r_str = svg_pick_attr_or_style (parser, style_props.r, "r");
173
894
    hb_svg_str_t fx_str = svg_pick_attr_or_style (parser, style_props.fx, "fx");
174
894
    hb_svg_str_t fy_str = svg_pick_attr_or_style (parser, style_props.fy, "fy");
175
894
    hb_svg_str_t fr_str = svg_pick_attr_or_style (parser, style_props.fr, "fr");
176
177
894
    if (cx_str.len) { grad.cx = svg_parse_number_or_percent (cx_str, nullptr); grad.has_cx = true; }
178
894
    if (cy_str.len) { grad.cy = svg_parse_number_or_percent (cy_str, nullptr); grad.has_cy = true; }
179
894
    if (r_str.len) { grad.r = svg_parse_number_or_percent (r_str, nullptr); grad.has_r = true; }
180
894
    if (fx_str.len) { grad.fx = svg_parse_number_or_percent (fx_str, nullptr); grad.has_fx = true; }
181
894
    if (fy_str.len) { grad.fy = svg_parse_number_or_percent (fy_str, nullptr); grad.has_fy = true; }
182
894
    if (fr_str.len) { grad.fr = svg_parse_number_or_percent (fr_str, nullptr); grad.has_fr = true; }
183
894
  }
184
2.02k
}
185
186
static void
187
svg_parse_gradient_children (hb_svg_defs_t *defs,
188
                             hb_svg_xml_parser_t &parser,
189
                             hb_svg_gradient_t &grad,
190
                             hb_svg_str_t *id,
191
                             hb_paint_funcs_t *pfuncs,
192
                             void *paint_data,
193
                             hb_color_t foreground,
194
                             hb_face_t *face,
195
                             unsigned palette)
196
1.83k
{
197
1.83k
  int gdepth = 1;
198
1.83k
  bool had_alloc_failure = false;
199
15.9k
  while (gdepth > 0)
200
14.1k
  {
201
14.1k
    hb_svg_token_type_t gt = parser.next ();
202
14.1k
    if (gt == SVG_TOKEN_EOF) break;
203
14.1k
    if (gt == SVG_TOKEN_CLOSE_TAG) { gdepth--; continue; }
204
10.8k
    if ((gt == SVG_TOKEN_OPEN_TAG || gt == SVG_TOKEN_SELF_CLOSE_TAG) &&
205
10.8k
        parser.tag_name.eq ("stop"))
206
6.32k
      if (unlikely (!svg_parse_gradient_stop (parser, grad,
207
6.32k
                                              pfuncs, paint_data,
208
6.32k
                                              foreground, face,
209
6.32k
                                              palette)))
210
2
        had_alloc_failure = true;
211
10.8k
    if (gt == SVG_TOKEN_OPEN_TAG && !parser.tag_name.eq ("stop"))
212
2.43k
      gdepth++;
213
10.8k
  }
214
1.83k
  if (had_alloc_failure || defs->gradients.in_error ())
215
1
    *id = {};
216
1.83k
}
217
218
void
219
hb_raster_svg_process_gradient_def (hb_svg_defs_t *defs,
220
                          hb_svg_xml_parser_t &parser,
221
                          hb_svg_token_type_t tok,
222
                          hb_svg_gradient_type_t type,
223
                          hb_paint_funcs_t *pfuncs,
224
                          void *paint_data,
225
                          hb_color_t foreground,
226
                          hb_face_t *face,
227
                          unsigned palette)
228
2.02k
{
229
2.02k
  hb_svg_gradient_t grad;
230
2.02k
  grad.type = type;
231
2.02k
  svg_parse_gradient_geometry_attrs (parser, grad);
232
2.02k
  svg_parse_gradient_attrs (parser, grad);
233
234
2.02k
  hb_svg_str_t id = parser.find_attr ("id");
235
2.02k
  if (tok == SVG_TOKEN_OPEN_TAG)
236
1.83k
    svg_parse_gradient_children (defs, parser, grad, &id,
237
1.83k
                                 pfuncs, paint_data,
238
1.83k
                                 foreground, face,
239
1.83k
                                 palette);
240
241
2.02k
  if (id.len)
242
1.78k
    (void) defs->add_gradient (hb_bytes_t (id.data, id.len), grad);
243
2.02k
}
244
245
#endif /* !HB_NO_RASTER_SVG */