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 | |
37 | void 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 | |
87 | float 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 | |
107 | void 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 | |
221 | static void |
222 | hb_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 | |
233 | static void |
234 | hb_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 | |
245 | static void |
246 | hb_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 | |
259 | static void |
260 | hb_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 | |
275 | static void |
276 | hb_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 | |
286 | static inline void free_static_outline_recording_pen_funcs (); |
287 | |
288 | static 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 | |
308 | static inline |
309 | void free_static_outline_recording_pen_funcs () |
310 | { |
311 | static_outline_recording_pen_funcs.free_instance (); |
312 | } |
313 | |
314 | hb_draw_funcs_t * |
315 | hb_outline_recording_pen_get_funcs () |
316 | { |
317 | return static_outline_recording_pen_funcs.get_unconst (); |
318 | } |
319 | |
320 | |
321 | #endif |
322 | |