1/*
2 * Copyright © 2023 Behdad Esfahbod
3 * Copyright © 1999 David Turner
4 * Copyright © 2005 Werner Lemberg
5 * Copyright © 2013-2015 Alexei Podtelezhnikov
6 *
7 * This is part of HarfBuzz, a text shaping library.
8 *
9 * Permission is hereby granted, without written agreement and without
10 * license or royalty fees, to use, copy, modify, and distribute this
11 * software and its documentation for any purpose, provided that the
12 * above copyright notice and the following two paragraphs appear in
13 * all copies of this software.
14 *
15 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
16 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
17 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
18 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
19 * DAMAGE.
20 *
21 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
22 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
23 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
24 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
25 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
26 */
27
28#include "hb.hh"
29
30#ifndef HB_NO_OUTLINE
31
32#include "hb-outline.hh"
33
34#include "hb-machinery.hh"
35
36
37void hb_outline_t::replay (hb_draw_funcs_t *pen, void *pen_data) const
38{
39 hb_draw_state_t st = HB_DRAW_STATE_DEFAULT;
40
41 unsigned first = 0;
42 for (unsigned contour : contours)
43 {
44 auto it = points.as_array ().sub_array (first, contour - first);
45 while (it)
46 {
47 hb_outline_point_t p1 = *it++;
48 switch (p1.type)
49 {
50 case hb_outline_point_t::type_t::MOVE_TO:
51 {
52 pen->move_to (pen_data, st,
53 p1.x, p1.y);
54 }
55 break;
56 case hb_outline_point_t::type_t::LINE_TO:
57 {
58 pen->line_to (pen_data, st,
59 p1.x, p1.y);
60 }
61 break;
62 case hb_outline_point_t::type_t::QUADRATIC_TO:
63 {
64 hb_outline_point_t p2 = *it++;
65 pen->quadratic_to (pen_data, st,
66 p1.x, p1.y,
67 p2.x, p2.y);
68 }
69 break;
70 case hb_outline_point_t::type_t::CUBIC_TO:
71 {
72 hb_outline_point_t p2 = *it++;
73 hb_outline_point_t p3 = *it++;
74 pen->cubic_to (pen_data, st,
75 p1.x, p1.y,
76 p2.x, p2.y,
77 p3.x, p3.y);
78 }
79 break;
80 }
81 }
82 pen->close_path (pen_data, st);
83 first = contour;
84 }
85}
86
87float hb_outline_t::control_area () const
88{
89 float a = 0;
90 unsigned first = 0;
91 for (unsigned contour : contours)
92 {
93 for (unsigned i = first; i < contour; i++)
94 {
95 unsigned j = i + 1 < contour ? i + 1 : first;
96
97 auto &pi = points[i];
98 auto &pj = points[j];
99 a += pi.x * pj.y - pi.y * pj.x;
100 }
101
102 first = contour;
103 }
104 return a * .5f;
105}
106
107void hb_outline_t::embolden (float x_strength, float y_strength,
108 float x_shift, float y_shift)
109{
110 /* This function is a straight port of FreeType's FT_Outline_EmboldenXY.
111 * Permission has been obtained from the FreeType authors of the code
112 * to relicense it under the HarfBuzz license. */
113
114 if (!x_strength && !y_strength) return;
115 if (!points) return;
116
117 x_strength /= 2.f;
118 y_strength /= 2.f;
119
120 bool orientation_negative = control_area () < 0;
121
122 signed first = 0;
123 for (unsigned c = 0; c < contours.length; c++)
124 {
125 hb_outline_vector_t in, out, anchor, shift;
126 float l_in, l_out, l_anchor = 0, l, q, d;
127
128 l_in = 0;
129 signed last = (int) contours[c] - 1;
130
131 /* pacify compiler */
132 in.x = in.y = anchor.x = anchor.y = 0;
133
134 /* Counter j cycles though the points; counter i advances only */
135 /* when points are moved; anchor k marks the first moved point. */
136 for ( signed i = last, j = first, k = -1;
137 j != i && i != k;
138 j = j < last ? j + 1 : first )
139 {
140 if ( j != k )
141 {
142 out.x = points[j].x - points[i].x;
143 out.y = points[j].y - points[i].y;
144 l_out = out.normalize_len ();
145
146 if ( l_out == 0 )
147 continue;
148 }
149 else
150 {
151 out = anchor;
152 l_out = l_anchor;
153 }
154
155 if ( l_in != 0 )
156 {
157 if ( k < 0 )
158 {
159 k = i;
160 anchor = in;
161 l_anchor = l_in;
162 }
163
164 d = in.x * out.x + in.y * out.y;
165
166 /* shift only if turn is less than ~160 degrees */
167 if ( d > -15.f/16.f )
168 {
169 d = d + 1.f;
170
171 /* shift components along lateral bisector in proper orientation */
172 shift.x = in.y + out.y;
173 shift.y = in.x + out.x;
174
175 if ( orientation_negative )
176 shift.x = -shift.x;
177 else
178 shift.y = -shift.y;
179
180 /* restrict shift magnitude to better handle collapsing segments */
181 q = out.x * in.y - out.y * in.x;
182 if ( orientation_negative )
183 q = -q;
184
185 l = hb_min (l_in, l_out);
186
187 /* non-strict inequalities avoid divide-by-zero when q == l == 0 */
188 if (x_strength * q <= l * d)
189 shift.x = shift.x * x_strength / d;
190 else
191 shift.x = shift.x * l / q;
192
193
194 if (y_strength * q <= l * d)
195 shift.y = shift.y * y_strength / d;
196 else
197 shift.y = shift.y * l / q;
198 }
199 else
200 shift.x = shift.y = 0;
201
202 for ( ;
203 i != j;
204 i = i < last ? i + 1 : first )
205 {
206 points[i].x += x_shift + shift.x;
207 points[i].y += y_shift + shift.y;
208 }
209 }
210 else
211 i = j;
212
213 in = out;
214 l_in = l_out;
215 }
216
217 first = last + 1;
218 }
219}
220
221static void
222hb_outline_recording_pen_move_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
223 void *data,
224 hb_draw_state_t *st,
225 float to_x, float to_y,
226 void *user_data HB_UNUSED)
227{
228 hb_outline_t *c = (hb_outline_t *) data;
229
230 c->points.push (hb_outline_point_t {to_x, to_y, hb_outline_point_t::type_t::MOVE_TO});
231}
232
233static void
234hb_outline_recording_pen_line_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
235 void *data,
236 hb_draw_state_t *st,
237 float to_x, float to_y,
238 void *user_data HB_UNUSED)
239{
240 hb_outline_t *c = (hb_outline_t *) data;
241
242 c->points.push (hb_outline_point_t {to_x, to_y, hb_outline_point_t::type_t::LINE_TO});
243}
244
245static void
246hb_outline_recording_pen_quadratic_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
247 void *data,
248 hb_draw_state_t *st,
249 float control_x, float control_y,
250 float to_x, float to_y,
251 void *user_data HB_UNUSED)
252{
253 hb_outline_t *c = (hb_outline_t *) data;
254
255 c->points.push (hb_outline_point_t {control_x, control_y, hb_outline_point_t::type_t::QUADRATIC_TO});
256 c->points.push (hb_outline_point_t {to_x, to_y, hb_outline_point_t::type_t::QUADRATIC_TO});
257}
258
259static void
260hb_outline_recording_pen_cubic_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
261 void *data,
262 hb_draw_state_t *st,
263 float control1_x, float control1_y,
264 float control2_x, float control2_y,
265 float to_x, float to_y,
266 void *user_data HB_UNUSED)
267{
268 hb_outline_t *c = (hb_outline_t *) data;
269
270 c->points.push (hb_outline_point_t {control1_x, control1_y, hb_outline_point_t::type_t::CUBIC_TO});
271 c->points.push (hb_outline_point_t {control2_x, control2_y, hb_outline_point_t::type_t::CUBIC_TO});
272 c->points.push (hb_outline_point_t {to_x, to_y, hb_outline_point_t::type_t::CUBIC_TO});
273}
274
275static void
276hb_outline_recording_pen_close_path (hb_draw_funcs_t *dfuncs HB_UNUSED,
277 void *data,
278 hb_draw_state_t *st,
279 void *user_data HB_UNUSED)
280{
281 hb_outline_t *c = (hb_outline_t *) data;
282
283 c->contours.push (c->points.length);
284}
285
286static inline void free_static_outline_recording_pen_funcs ();
287
288static struct hb_outline_recording_pen_funcs_lazy_loader_t : hb_draw_funcs_lazy_loader_t<hb_outline_recording_pen_funcs_lazy_loader_t>
289{
290 static hb_draw_funcs_t *create ()
291 {
292 hb_draw_funcs_t *funcs = hb_draw_funcs_create ();
293
294 hb_draw_funcs_set_move_to_func (funcs, hb_outline_recording_pen_move_to, nullptr, nullptr);
295 hb_draw_funcs_set_line_to_func (funcs, hb_outline_recording_pen_line_to, nullptr, nullptr);
296 hb_draw_funcs_set_quadratic_to_func (funcs, hb_outline_recording_pen_quadratic_to, nullptr, nullptr);
297 hb_draw_funcs_set_cubic_to_func (funcs, hb_outline_recording_pen_cubic_to, nullptr, nullptr);
298 hb_draw_funcs_set_close_path_func (funcs, hb_outline_recording_pen_close_path, nullptr, nullptr);
299
300 hb_draw_funcs_make_immutable (funcs);
301
302 hb_atexit (free_static_outline_recording_pen_funcs);
303
304 return funcs;
305 }
306} static_outline_recording_pen_funcs;
307
308static inline
309void free_static_outline_recording_pen_funcs ()
310{
311 static_outline_recording_pen_funcs.free_instance ();
312}
313
314hb_draw_funcs_t *
315hb_outline_recording_pen_get_funcs ()
316{
317 return static_outline_recording_pen_funcs.get_unconst ();
318}
319
320
321#endif
322