/src/harfbuzz/src/hb-vector-draw.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 | | #include "hb.hh" |
28 | | |
29 | | #include "hb-vector-draw.hh" |
30 | | #include "hb-vector-path.hh" |
31 | | |
32 | | #include <math.h> |
33 | | #include <string.h> |
34 | | |
35 | | HB_UNUSED static inline bool |
36 | | hb_vector_svg_buffer_contains (const hb_vector_buf_t &buf, const char *needle) |
37 | 0 | { |
38 | 0 | unsigned nlen = (unsigned) strlen (needle); |
39 | 0 | if (!nlen || buf.length < nlen) |
40 | 0 | return false; |
41 | 0 |
|
42 | 0 | for (unsigned i = 0; i + nlen <= buf.length; i++) |
43 | 0 | if (buf.arrayZ[i] == needle[0] && |
44 | 0 | !memcmp (buf.arrayZ + i, needle, nlen)) |
45 | 0 | return true; |
46 | 0 | return false; |
47 | 0 | } |
48 | | |
49 | | /* ---- SVG draw callbacks ---- */ |
50 | | |
51 | | static void hb_vector_draw_svg_move_to (hb_draw_funcs_t *, void *dd, hb_draw_state_t *, float x, float y, void *) |
52 | 2.36M | { |
53 | 2.36M | auto *d = (hb_vector_draw_t *) dd; |
54 | 2.36M | if (unlikely (!d->begin_path_command ())) return; |
55 | 2.20M | d->path.append_c ('M'); d->append_xy_svg (x, y); |
56 | 2.20M | d->end_path_command (); |
57 | 2.20M | } |
58 | | |
59 | | static void hb_vector_draw_svg_line_to (hb_draw_funcs_t *, void *dd, hb_draw_state_t *, float x, float y, void *) |
60 | 22.7M | { |
61 | 22.7M | auto *d = (hb_vector_draw_t *) dd; |
62 | 22.7M | if (unlikely (!d->begin_path_command ())) return; |
63 | 21.7M | d->path.append_c ('L'); d->append_xy_svg (x, y); |
64 | 21.7M | d->end_path_command (); |
65 | 21.7M | } |
66 | | |
67 | | static void hb_vector_draw_svg_quadratic_to (hb_draw_funcs_t *, void *dd, hb_draw_state_t *, float cx, float cy, float x, float y, void *) |
68 | 25.1M | { |
69 | 25.1M | auto *d = (hb_vector_draw_t *) dd; |
70 | 25.1M | if (unlikely (!d->begin_path_command ())) return; |
71 | 25.1M | d->path.append_c ('Q'); d->append_xy_svg (cx, cy); d->path.append_c (' '); d->append_xy_svg (x, y); |
72 | 25.1M | d->end_path_command (); |
73 | 25.1M | } |
74 | | |
75 | | static void hb_vector_draw_svg_cubic_to (hb_draw_funcs_t *, void *dd, hb_draw_state_t *, float c1x, float c1y, float c2x, float c2y, float x, float y, void *) |
76 | 1.20M | { |
77 | 1.20M | auto *d = (hb_vector_draw_t *) dd; |
78 | 1.20M | if (unlikely (!d->begin_path_command ())) return; |
79 | 840k | d->path.append_c ('C'); d->append_xy_svg (c1x, c1y); d->path.append_c (' '); d->append_xy_svg (c2x, c2y); d->path.append_c (' '); d->append_xy_svg (x, y); |
80 | 840k | d->end_path_command (); |
81 | 840k | } |
82 | | |
83 | | static void hb_vector_draw_svg_close_path (hb_draw_funcs_t *, void *dd, hb_draw_state_t *, void *) |
84 | 2.36M | { |
85 | 2.36M | auto *d = (hb_vector_draw_t *) dd; |
86 | 2.36M | if (unlikely (!d->begin_path_command ())) return; |
87 | 2.20M | d->path.append_c ('Z'); |
88 | 2.20M | d->end_path_command (); |
89 | 2.20M | } |
90 | | |
91 | | |
92 | | /* ---- PDF draw callbacks ---- */ |
93 | | |
94 | | static void hb_vector_draw_pdf_move_to (hb_draw_funcs_t *, void *dd, hb_draw_state_t *, float x, float y, void *) |
95 | 1.89M | { |
96 | 1.89M | auto *d = (hb_vector_draw_t *) dd; |
97 | 1.89M | if (unlikely (!d->begin_path_command ())) return; |
98 | 1.62M | d->append_xy_pdf (x, y); d->path.append_str (" m\n"); |
99 | 1.62M | d->end_path_command (); |
100 | 1.62M | } |
101 | | |
102 | | static void hb_vector_draw_pdf_line_to (hb_draw_funcs_t *, void *dd, hb_draw_state_t *, float x, float y, void *) |
103 | 17.9M | { |
104 | 17.9M | auto *d = (hb_vector_draw_t *) dd; |
105 | 17.9M | if (unlikely (!d->begin_path_command ())) return; |
106 | 16.7M | d->append_xy_pdf (x, y); d->path.append_str (" l\n"); |
107 | 16.7M | d->end_path_command (); |
108 | 16.7M | } |
109 | | |
110 | | /* No quadratic_to — null fallback auto-promotes to cubic. */ |
111 | | |
112 | | static void hb_vector_draw_pdf_cubic_to (hb_draw_funcs_t *, void *dd, hb_draw_state_t *, float c1x, float c1y, float c2x, float c2y, float x, float y, void *) |
113 | 19.6M | { |
114 | 19.6M | auto *d = (hb_vector_draw_t *) dd; |
115 | 19.6M | if (unlikely (!d->begin_path_command ())) return; |
116 | 18.1M | d->append_xy_pdf (c1x, c1y); d->path.append_c (' '); d->append_xy_pdf (c2x, c2y); d->path.append_c (' '); d->append_xy_pdf (x, y); d->path.append_str (" c\n"); |
117 | 18.1M | d->end_path_command (); |
118 | 18.1M | } |
119 | | |
120 | | static void hb_vector_draw_pdf_close_path (hb_draw_funcs_t *, void *dd, hb_draw_state_t *, void *) |
121 | 1.89M | { |
122 | 1.89M | auto *d = (hb_vector_draw_t *) dd; |
123 | 1.89M | if (unlikely (!d->begin_path_command ())) return; |
124 | 1.61M | d->path.append_str ("h\n"); |
125 | 1.61M | d->end_path_command (); |
126 | 1.61M | } |
127 | | |
128 | | static hb_bool_t |
129 | | hb_vector_draw_set_budget (hb_draw_funcs_t *, void *draw_data, |
130 | | int64_t budget, void *) |
131 | 0 | { |
132 | 0 | auto *draw = (hb_vector_draw_t *) draw_data; |
133 | 0 | draw->budget = budget; |
134 | 0 | draw->recharge_budget (); |
135 | 0 | return true; |
136 | 0 | } |
137 | | |
138 | | static int64_t |
139 | | hb_vector_draw_get_budget (hb_draw_funcs_t *, void *draw_data, void *) |
140 | 0 | { |
141 | 0 | return ((hb_vector_draw_t *) draw_data)->budget; |
142 | 0 | } |
143 | | |
144 | | static int64_t * |
145 | | hb_vector_draw_get_budget_remaining (hb_draw_funcs_t *, void *draw_data, void *) |
146 | 25.6k | { |
147 | 25.6k | return &((hb_vector_draw_t *) draw_data)->budget_remaining; |
148 | 25.6k | } |
149 | | |
150 | | |
151 | | /* ---- Lazy loaders ---- */ |
152 | | |
153 | | static inline void free_static_vector_draw_svg_funcs (); |
154 | | static struct hb_vector_draw_svg_funcs_lazy_loader_t |
155 | | : hb_draw_funcs_lazy_loader_t<hb_vector_draw_svg_funcs_lazy_loader_t> |
156 | | { |
157 | | static hb_draw_funcs_t *create () |
158 | 1 | { |
159 | 1 | hb_draw_funcs_t *funcs = hb_draw_funcs_create (); |
160 | 1 | hb_draw_funcs_set_move_to_func (funcs, (hb_draw_move_to_func_t) hb_vector_draw_svg_move_to, nullptr, nullptr); |
161 | 1 | hb_draw_funcs_set_line_to_func (funcs, (hb_draw_line_to_func_t) hb_vector_draw_svg_line_to, nullptr, nullptr); |
162 | 1 | hb_draw_funcs_set_quadratic_to_func (funcs, (hb_draw_quadratic_to_func_t) hb_vector_draw_svg_quadratic_to, nullptr, nullptr); |
163 | 1 | hb_draw_funcs_set_cubic_to_func (funcs, (hb_draw_cubic_to_func_t) hb_vector_draw_svg_cubic_to, nullptr, nullptr); |
164 | 1 | hb_draw_funcs_set_close_path_func (funcs, (hb_draw_close_path_func_t) hb_vector_draw_svg_close_path, nullptr, nullptr); |
165 | 1 | hb_draw_funcs_set_set_budget_func (funcs, hb_vector_draw_set_budget, nullptr, nullptr); |
166 | 1 | hb_draw_funcs_set_get_budget_func (funcs, hb_vector_draw_get_budget, nullptr, nullptr); |
167 | 1 | hb_draw_funcs_set_get_budget_remaining_func (funcs, hb_vector_draw_get_budget_remaining, nullptr, nullptr); |
168 | 1 | hb_draw_funcs_make_immutable (funcs); |
169 | 1 | hb_atexit (free_static_vector_draw_svg_funcs); |
170 | 1 | return funcs; |
171 | 1 | } |
172 | | } static_vector_draw_svg_funcs; |
173 | 1 | static inline void free_static_vector_draw_svg_funcs () { static_vector_draw_svg_funcs.free_instance (); } |
174 | | |
175 | | static inline void free_static_vector_draw_pdf_funcs (); |
176 | | static struct hb_vector_draw_pdf_funcs_lazy_loader_t |
177 | | : hb_draw_funcs_lazy_loader_t<hb_vector_draw_pdf_funcs_lazy_loader_t> |
178 | | { |
179 | | static hb_draw_funcs_t *create () |
180 | 1 | { |
181 | 1 | hb_draw_funcs_t *funcs = hb_draw_funcs_create (); |
182 | 1 | hb_draw_funcs_set_move_to_func (funcs, (hb_draw_move_to_func_t) hb_vector_draw_pdf_move_to, nullptr, nullptr); |
183 | 1 | hb_draw_funcs_set_line_to_func (funcs, (hb_draw_line_to_func_t) hb_vector_draw_pdf_line_to, nullptr, nullptr); |
184 | | /* No quadratic_to: null fallback auto-promotes to cubic. */ |
185 | 1 | hb_draw_funcs_set_cubic_to_func (funcs, (hb_draw_cubic_to_func_t) hb_vector_draw_pdf_cubic_to, nullptr, nullptr); |
186 | 1 | hb_draw_funcs_set_close_path_func (funcs, (hb_draw_close_path_func_t) hb_vector_draw_pdf_close_path, nullptr, nullptr); |
187 | 1 | hb_draw_funcs_set_set_budget_func (funcs, hb_vector_draw_set_budget, nullptr, nullptr); |
188 | 1 | hb_draw_funcs_set_get_budget_func (funcs, hb_vector_draw_get_budget, nullptr, nullptr); |
189 | 1 | hb_draw_funcs_set_get_budget_remaining_func (funcs, hb_vector_draw_get_budget_remaining, nullptr, nullptr); |
190 | 1 | hb_draw_funcs_make_immutable (funcs); |
191 | 1 | hb_atexit (free_static_vector_draw_pdf_funcs); |
192 | 1 | return funcs; |
193 | 1 | } |
194 | | } static_vector_draw_pdf_funcs; |
195 | 1 | static inline void free_static_vector_draw_pdf_funcs () { static_vector_draw_pdf_funcs.free_instance (); } |
196 | | |
197 | | |
198 | | |
199 | | /** |
200 | | * hb_vector_draw_create_or_fail: |
201 | | * @format: output format. |
202 | | * |
203 | | * Creates a new draw context for vector output. |
204 | | * |
205 | | * Return value: (nullable): a newly allocated #hb_vector_draw_t, or `NULL` on failure. |
206 | | * |
207 | | * Since: 13.0.0 |
208 | | */ |
209 | | hb_vector_draw_t * |
210 | | hb_vector_draw_create_or_fail (hb_vector_format_t format) |
211 | 32.1k | { |
212 | 32.1k | switch (format) |
213 | 32.1k | { |
214 | 16.0k | case HB_VECTOR_FORMAT_SVG: |
215 | 32.1k | case HB_VECTOR_FORMAT_PDF: |
216 | 32.1k | break; |
217 | 0 | case HB_VECTOR_FORMAT_INVALID: default: |
218 | 0 | return nullptr; |
219 | 32.1k | } |
220 | | |
221 | 32.1k | hb_vector_draw_t *draw = hb_object_create<hb_vector_draw_t> (); |
222 | 32.1k | if (unlikely (!draw)) |
223 | 1.35k | return nullptr; |
224 | 30.8k | draw->format = format; |
225 | 30.8k | draw->defs.alloc (2048); |
226 | 30.8k | draw->body.alloc (8192); |
227 | 30.8k | draw->path.alloc (2048); |
228 | 30.8k | return draw; |
229 | 32.1k | } |
230 | | |
231 | | /** |
232 | | * hb_vector_draw_reference: |
233 | | * @draw: a draw context. |
234 | | * |
235 | | * Increases the reference count of @draw. |
236 | | * |
237 | | * Return value: (transfer full): referenced @draw. |
238 | | * |
239 | | * Since: 13.0.0 |
240 | | */ |
241 | | hb_vector_draw_t * |
242 | | hb_vector_draw_reference (hb_vector_draw_t *draw) |
243 | 0 | { |
244 | 0 | return hb_object_reference (draw); |
245 | 0 | } |
246 | | |
247 | | /** |
248 | | * hb_vector_draw_destroy: |
249 | | * @draw: a draw context. |
250 | | * |
251 | | * Decreases the reference count of @draw and destroys it when it reaches zero. |
252 | | * |
253 | | * Since: 13.0.0 |
254 | | */ |
255 | | void |
256 | | hb_vector_draw_destroy (hb_vector_draw_t *draw) |
257 | 32.1k | { |
258 | 32.1k | if (!hb_object_should_destroy (draw)) |
259 | 1.35k | return; |
260 | | |
261 | 30.8k | hb_blob_destroy (draw->recycled_blob); |
262 | 30.8k | hb_object_actually_destroy (draw); |
263 | 30.8k | hb_free (draw); |
264 | 30.8k | } |
265 | | |
266 | | /** |
267 | | * hb_vector_draw_set_user_data: |
268 | | * @draw: a draw context. |
269 | | * @key: user-data key. |
270 | | * @data: user-data value. |
271 | | * @destroy: (nullable): destroy callback for @data. |
272 | | * @replace: whether to replace an existing value for @key. |
273 | | * |
274 | | * Attaches user data to @draw. |
275 | | * |
276 | | * Return value: `true` on success, `false` otherwise. |
277 | | * |
278 | | * Since: 13.0.0 |
279 | | */ |
280 | | hb_bool_t |
281 | | hb_vector_draw_set_user_data (hb_vector_draw_t *draw, |
282 | | hb_user_data_key_t *key, |
283 | | void *data, |
284 | | hb_destroy_func_t destroy, |
285 | | hb_bool_t replace) |
286 | 0 | { |
287 | 0 | return hb_object_set_user_data (draw, key, data, destroy, replace); |
288 | 0 | } |
289 | | |
290 | | /** |
291 | | * hb_vector_draw_get_user_data: |
292 | | * @draw: a draw context. |
293 | | * @key: user-data key. |
294 | | * |
295 | | * Gets previously attached user data from @draw. |
296 | | * |
297 | | * Return value: (nullable): user-data value associated with @key. |
298 | | * |
299 | | * Since: 13.0.0 |
300 | | */ |
301 | | void * |
302 | | hb_vector_draw_get_user_data (const hb_vector_draw_t *draw, |
303 | | hb_user_data_key_t *key) |
304 | 0 | { |
305 | 0 | return hb_object_get_user_data (draw, key); |
306 | 0 | } |
307 | | |
308 | | /** |
309 | | * hb_vector_draw_set_transform: |
310 | | * @draw: a draw context. |
311 | | * @xx: transform xx component. |
312 | | * @yx: transform yx component. |
313 | | * @xy: transform xy component. |
314 | | * @yy: transform yy component. |
315 | | * @dx: transform x translation. |
316 | | * @dy: transform y translation. |
317 | | * |
318 | | * Sets the affine transform used when drawing glyphs. |
319 | | * |
320 | | * Since: 13.0.0 |
321 | | */ |
322 | | void |
323 | | hb_vector_draw_set_transform (hb_vector_draw_t *draw, |
324 | | float xx, float yx, |
325 | | float xy, float yy, |
326 | | float dx, float dy) |
327 | 25.6k | { |
328 | 25.6k | draw->transform = {xx, yx, xy, yy, dx, dy}; |
329 | 25.6k | } |
330 | | |
331 | | /** |
332 | | * hb_vector_draw_get_transform: |
333 | | * @draw: a draw context. |
334 | | * @xx: (out) (nullable): transform xx component. |
335 | | * @yx: (out) (nullable): transform yx component. |
336 | | * @xy: (out) (nullable): transform xy component. |
337 | | * @yy: (out) (nullable): transform yy component. |
338 | | * @dx: (out) (nullable): transform x translation. |
339 | | * @dy: (out) (nullable): transform y translation. |
340 | | * |
341 | | * Gets the affine transform used when drawing glyphs. |
342 | | * |
343 | | * Since: 13.0.0 |
344 | | */ |
345 | | void |
346 | | hb_vector_draw_get_transform (const hb_vector_draw_t *draw, |
347 | | float *xx, float *yx, |
348 | | float *xy, float *yy, |
349 | | float *dx, float *dy) |
350 | 0 | { |
351 | 0 | if (xx) *xx = draw->transform.xx; |
352 | 0 | if (yx) *yx = draw->transform.yx; |
353 | 0 | if (xy) *xy = draw->transform.xy; |
354 | 0 | if (yy) *yy = draw->transform.yy; |
355 | 0 | if (dx) *dx = draw->transform.x0; |
356 | 0 | if (dy) *dy = draw->transform.y0; |
357 | 0 | } |
358 | | |
359 | | /** |
360 | | * hb_vector_draw_set_scale_factor: |
361 | | * @draw: a draw context. |
362 | | * @x_scale_factor: x scale factor. |
363 | | * @y_scale_factor: y scale factor. |
364 | | * |
365 | | * Sets additional output scaling factors. |
366 | | * |
367 | | * Since: 13.0.0 |
368 | | */ |
369 | | void |
370 | | hb_vector_draw_set_scale_factor (hb_vector_draw_t *draw, |
371 | | float x_scale_factor, |
372 | | float y_scale_factor) |
373 | 0 | { |
374 | 0 | draw->x_scale_factor = x_scale_factor > 0.f ? x_scale_factor : 1.f; |
375 | 0 | draw->y_scale_factor = y_scale_factor > 0.f ? y_scale_factor : 1.f; |
376 | 0 | } |
377 | | |
378 | | /** |
379 | | * hb_vector_draw_get_scale_factor: |
380 | | * @draw: a draw context. |
381 | | * @x_scale_factor: (out) (nullable): x scale factor. |
382 | | * @y_scale_factor: (out) (nullable): y scale factor. |
383 | | * |
384 | | * Gets additional output scaling factors. |
385 | | * |
386 | | * Since: 13.0.0 |
387 | | */ |
388 | | void |
389 | | hb_vector_draw_get_scale_factor (const hb_vector_draw_t *draw, |
390 | | float *x_scale_factor, |
391 | | float *y_scale_factor) |
392 | 0 | { |
393 | 0 | if (x_scale_factor) *x_scale_factor = draw->x_scale_factor; |
394 | 0 | if (y_scale_factor) *y_scale_factor = draw->y_scale_factor; |
395 | 0 | } |
396 | | |
397 | | /** |
398 | | * hb_vector_draw_set_extents: |
399 | | * @draw: a draw context. |
400 | | * @extents: (nullable): output extents to set or expand. |
401 | | * |
402 | | * Sets or expands output extents on @draw. Passing `NULL` clears extents. |
403 | | * |
404 | | * Since: 13.0.0 |
405 | | */ |
406 | | void |
407 | | hb_vector_draw_set_extents (hb_vector_draw_t *draw, |
408 | | const hb_vector_extents_t *extents) |
409 | 0 | { |
410 | 0 | if (!extents) |
411 | 0 | { |
412 | 0 | draw->extents = {0, 0, 0, 0}; |
413 | 0 | draw->has_extents = false; |
414 | 0 | return; |
415 | 0 | } |
416 | | |
417 | 0 | if (extents->width == 0.f || extents->height == 0.f) |
418 | 0 | return; |
419 | | |
420 | | /* Caller-supplied extents are in input-space; divide by |
421 | | * scale_factor so they end up in output-space, matching |
422 | | * the per-glyph extents accumulated via |
423 | | * hb_vector_set_glyph_extents_common (which applies the |
424 | | * same divide). Normalize so origin is the min corner and |
425 | | * width/height are positive — callers may pass a |
426 | | * glyph-extents-style box with negative height. */ |
427 | 0 | float x0 = extents->x / draw->x_scale_factor; |
428 | 0 | float y0 = extents->y / draw->y_scale_factor; |
429 | 0 | float x1 = x0 + extents->width / draw->x_scale_factor; |
430 | 0 | float y1 = y0 + extents->height / draw->y_scale_factor; |
431 | 0 | hb_vector_extents_t e = { |
432 | 0 | hb_min (x0, x1), hb_min (y0, y1), |
433 | 0 | fabsf (x1 - x0), fabsf (y1 - y0), |
434 | 0 | }; |
435 | |
|
436 | 0 | if (draw->has_extents) |
437 | 0 | { |
438 | 0 | float x0 = hb_min (draw->extents.x, e.x); |
439 | 0 | float y0 = hb_min (draw->extents.y, e.y); |
440 | 0 | float x1 = hb_max (draw->extents.x + draw->extents.width, |
441 | 0 | e.x + e.width); |
442 | 0 | float y1 = hb_max (draw->extents.y + draw->extents.height, |
443 | 0 | e.y + e.height); |
444 | 0 | draw->extents = {x0, y0, x1 - x0, y1 - y0}; |
445 | 0 | } |
446 | 0 | else |
447 | 0 | { |
448 | 0 | draw->extents = e; |
449 | 0 | draw->has_extents = true; |
450 | 0 | } |
451 | 0 | } |
452 | | |
453 | | /** |
454 | | * hb_vector_draw_get_extents: |
455 | | * @draw: a draw context. |
456 | | * @extents: (out) (nullable): where to store current output extents. |
457 | | * |
458 | | * Gets current output extents from @draw. |
459 | | * |
460 | | * Return value: `true` if extents are set, `false` otherwise. |
461 | | * |
462 | | * Since: 13.0.0 |
463 | | */ |
464 | | hb_bool_t |
465 | | hb_vector_draw_get_extents (const hb_vector_draw_t *draw, |
466 | | hb_vector_extents_t *extents) |
467 | 0 | { |
468 | 0 | if (!draw->has_extents) |
469 | 0 | return false; |
470 | | |
471 | 0 | if (extents) |
472 | 0 | *extents = draw->extents; |
473 | 0 | return true; |
474 | 0 | } |
475 | | |
476 | | /** |
477 | | * hb_vector_draw_set_glyph_extents: |
478 | | * @draw: a draw context. |
479 | | * @glyph_extents: glyph extents in font units. |
480 | | * |
481 | | * Expands @draw extents using @glyph_extents under the current transform. |
482 | | * |
483 | | * Return value: `true` on success, `false` otherwise. |
484 | | * |
485 | | * Since: 13.0.0 |
486 | | */ |
487 | | hb_bool_t |
488 | | hb_vector_draw_set_glyph_extents (hb_vector_draw_t *draw, |
489 | | const hb_glyph_extents_t *glyph_extents) |
490 | 0 | { |
491 | 0 | hb_bool_t has_extents = draw->has_extents; |
492 | 0 | hb_bool_t ret = hb_vector_set_glyph_extents_common (draw->transform, |
493 | 0 | draw->x_scale_factor, |
494 | 0 | draw->y_scale_factor, |
495 | 0 | glyph_extents, |
496 | 0 | &draw->extents, |
497 | 0 | &has_extents); |
498 | 0 | draw->has_extents = has_extents; |
499 | 0 | return ret; |
500 | 0 | } |
501 | | |
502 | | /** |
503 | | * hb_vector_draw_get_format: |
504 | | * @draw: a vector draw context. |
505 | | * |
506 | | * Gets the output format @draw was created with. |
507 | | * |
508 | | * Return value: the output format. |
509 | | * |
510 | | * Since: 14.2.0 |
511 | | */ |
512 | | hb_vector_format_t |
513 | | hb_vector_draw_get_format (const hb_vector_draw_t *draw) |
514 | 0 | { |
515 | 0 | return draw->format; |
516 | 0 | } |
517 | | |
518 | | /** |
519 | | * hb_vector_draw_get_funcs: |
520 | | * @draw: a vector draw context. |
521 | | * |
522 | | * Gets draw callbacks for feeding outline data into @draw. |
523 | | * Pass @draw as the @draw_data argument when calling them. |
524 | | * |
525 | | * Return value: (transfer none): immutable #hb_draw_funcs_t. |
526 | | * |
527 | | * Since: 14.2.0 |
528 | | */ |
529 | | hb_draw_funcs_t * |
530 | | hb_vector_draw_get_funcs (const hb_vector_draw_t *draw) |
531 | 25.6k | { |
532 | 25.6k | switch (draw ? draw->format : HB_VECTOR_FORMAT_INVALID) |
533 | 25.6k | { |
534 | 12.7k | case HB_VECTOR_FORMAT_SVG: return static_vector_draw_svg_funcs.get_unconst (); |
535 | 12.9k | case HB_VECTOR_FORMAT_PDF: return static_vector_draw_pdf_funcs.get_unconst (); |
536 | 0 | case HB_VECTOR_FORMAT_INVALID: default: return nullptr; |
537 | 25.6k | } |
538 | 25.6k | } |
539 | | |
540 | | /** |
541 | | * hb_vector_draw_new_path: |
542 | | * @draw: a draw context. |
543 | | * |
544 | | * Flushes any pending path and starts a new one. Call this |
545 | | * between glyphs to separate their outlines so fill rules |
546 | | * don't interact across glyphs. |
547 | | * |
548 | | * Since: 14.2.0 |
549 | | */ |
550 | | void |
551 | | hb_vector_draw_new_path (hb_vector_draw_t *draw) |
552 | 0 | { |
553 | 0 | draw->new_path (); |
554 | 0 | } |
555 | | |
556 | | /** |
557 | | * hb_vector_draw_glyph_or_fail: |
558 | | * @draw: a draw context. |
559 | | * @font: font object. |
560 | | * @glyph: glyph ID. |
561 | | * @extents_mode: extents update mode. |
562 | | * |
563 | | * Convenience to draw one glyph into @draw. Equivalent to: |
564 | | * |
565 | | * |[<!-- language="plain" --> |
566 | | * // extend extents if requested |
567 | | * hb_vector_draw_new_path (draw); |
568 | | * hb_font_draw_glyph_or_fail (font, glyph, |
569 | | * hb_vector_draw_get_funcs (draw), draw); |
570 | | * ]| |
571 | | * |
572 | | * Return value: `true` if glyph data was emitted, `false` otherwise. |
573 | | * |
574 | | * Since: 14.2.0 |
575 | | */ |
576 | | hb_bool_t |
577 | | hb_vector_draw_glyph_or_fail (hb_vector_draw_t *draw, |
578 | | hb_font_t *font, |
579 | | hb_codepoint_t glyph, |
580 | | hb_vector_extents_mode_t extents_mode) |
581 | 25.6k | { |
582 | | |
583 | 25.6k | if (extents_mode == HB_VECTOR_EXTENTS_MODE_EXPAND) |
584 | 25.6k | { |
585 | 25.6k | hb_glyph_extents_t ge; |
586 | 25.6k | if (hb_font_get_glyph_extents (font, glyph, &ge)) |
587 | 14.7k | { |
588 | 14.7k | hb_bool_t has_extents = draw->has_extents; |
589 | 14.7k | hb_vector_set_glyph_extents_common (draw->transform, |
590 | 14.7k | draw->x_scale_factor, |
591 | 14.7k | draw->y_scale_factor, |
592 | 14.7k | &ge, |
593 | 14.7k | &draw->extents, |
594 | 14.7k | &has_extents); |
595 | 14.7k | draw->has_extents = has_extents; |
596 | 14.7k | } |
597 | 25.6k | } |
598 | | |
599 | 25.6k | draw->new_path (); |
600 | 25.6k | return hb_font_draw_glyph_or_fail (font, glyph, hb_vector_draw_get_funcs (draw), draw); |
601 | 25.6k | } |
602 | | |
603 | | /** |
604 | | * hb_vector_draw_glyph: |
605 | | * @draw: a draw context. |
606 | | * @font: font object. |
607 | | * @glyph: glyph ID. |
608 | | * @extents_mode: extents update mode. |
609 | | * |
610 | | * Draws one glyph into @draw. Equivalent to |
611 | | * hb_vector_draw_glyph_or_fail() with the return value ignored. |
612 | | * |
613 | | * Since: 14.2.0 |
614 | | */ |
615 | | void |
616 | | hb_vector_draw_glyph (hb_vector_draw_t *draw, |
617 | | hb_font_t *font, |
618 | | hb_codepoint_t glyph, |
619 | | hb_vector_extents_mode_t extents_mode) |
620 | 25.6k | { |
621 | 25.6k | hb_vector_draw_glyph_or_fail (draw, font, glyph, extents_mode); |
622 | 25.6k | } |
623 | | |
624 | | |
625 | | /** |
626 | | * hb_vector_draw_set_precision: |
627 | | * @draw: a draw context. |
628 | | * @precision: decimal precision. |
629 | | * |
630 | | * Sets numeric output precision for draw output. |
631 | | * |
632 | | * Since: 14.2.0 |
633 | | */ |
634 | | void |
635 | | hb_vector_draw_set_precision (hb_vector_draw_t *draw, |
636 | | unsigned precision) |
637 | 29.2k | { |
638 | 29.2k | draw->set_precision (precision); |
639 | 29.2k | } |
640 | | |
641 | | /** |
642 | | * hb_vector_draw_get_precision: |
643 | | * @draw: a draw context. |
644 | | * |
645 | | * Returns the numeric output precision previously set on @draw, |
646 | | * or the default if none was set. |
647 | | * |
648 | | * Return value: the precision. |
649 | | * |
650 | | * Since: 14.2.0 |
651 | | */ |
652 | | unsigned |
653 | | hb_vector_draw_get_precision (const hb_vector_draw_t *draw) |
654 | 0 | { |
655 | 0 | return draw->get_precision (); |
656 | 0 | } |
657 | | |
658 | | /** |
659 | | * hb_vector_draw_set_foreground: |
660 | | * @draw: a draw context. |
661 | | * @foreground: foreground fill color. |
662 | | * |
663 | | * Sets the fill color for drawn glyph outlines. |
664 | | * Default is opaque black. |
665 | | * |
666 | | * Since: 14.2.0 |
667 | | */ |
668 | | void |
669 | | hb_vector_draw_set_foreground (hb_vector_draw_t *draw, |
670 | | hb_color_t foreground) |
671 | 0 | { |
672 | 0 | draw->flush_path (); |
673 | 0 | draw->foreground = foreground; |
674 | 0 | } |
675 | | |
676 | | /** |
677 | | * hb_vector_draw_get_foreground: |
678 | | * @draw: a draw context. |
679 | | * |
680 | | * Returns the foreground fill color. |
681 | | * |
682 | | * Return value: the foreground color. |
683 | | * |
684 | | * Since: 14.2.0 |
685 | | */ |
686 | | hb_color_t |
687 | | hb_vector_draw_get_foreground (const hb_vector_draw_t *draw) |
688 | 0 | { |
689 | 0 | return draw->foreground; |
690 | 0 | } |
691 | | |
692 | | /** |
693 | | * hb_vector_draw_set_background: |
694 | | * @draw: a draw context. |
695 | | * @background: background color. |
696 | | * |
697 | | * Sets the background color. If non-transparent, a filled |
698 | | * rectangle covering the extents is emitted behind all content. |
699 | | * Default is transparent (no background). |
700 | | * |
701 | | * Since: 14.2.0 |
702 | | */ |
703 | | void |
704 | | hb_vector_draw_set_background (hb_vector_draw_t *draw, |
705 | | hb_color_t background) |
706 | 0 | { |
707 | 0 | draw->background = background; |
708 | 0 | } |
709 | | |
710 | | /** |
711 | | * hb_vector_draw_get_background: |
712 | | * @draw: a draw context. |
713 | | * |
714 | | * Returns the background color. |
715 | | * |
716 | | * Return value: the background color. |
717 | | * |
718 | | * Since: 14.2.0 |
719 | | */ |
720 | | hb_color_t |
721 | | hb_vector_draw_get_background (const hb_vector_draw_t *draw) |
722 | 0 | { |
723 | 0 | return draw->background; |
724 | 0 | } |
725 | | |
726 | | static hb_blob_t * |
727 | | hb_vector_draw_render_pdf (hb_vector_draw_t *draw) |
728 | 12.9k | { |
729 | 12.9k | draw->flush_path (); |
730 | 12.9k | if (!draw->has_extents) |
731 | 9.54k | return nullptr; |
732 | | |
733 | | /* Collect the content stream. Extents and path coordinates are |
734 | | * both in Y-up (font) space, same as the PDF page. */ |
735 | 3.36k | float ex = draw->extents.x; |
736 | 3.36k | float ey = draw->extents.y; |
737 | 3.36k | float ew = draw->extents.width; |
738 | 3.36k | float eh = draw->extents.height; |
739 | | |
740 | 3.36k | hb_vector_buf_t stream; |
741 | 3.36k | stream.alloc (draw->body.length + draw->path.length + 256); |
742 | | |
743 | | /* Background rect. */ |
744 | 3.36k | if (hb_color_get_alpha (draw->background)) |
745 | 0 | { |
746 | 0 | float a = hb_color_get_alpha (draw->background) / 255.f; |
747 | 0 | if (a < 1.f - 1.f / 512.f) |
748 | 0 | { |
749 | 0 | stream.append_num (a, 4); |
750 | 0 | stream.append_str (" ca gs\n"); |
751 | 0 | } |
752 | 0 | stream.append_num (hb_color_get_red (draw->background) / 255.f, 4); |
753 | 0 | stream.append_c (' '); |
754 | 0 | stream.append_num (hb_color_get_green (draw->background) / 255.f, 4); |
755 | 0 | stream.append_c (' '); |
756 | 0 | stream.append_num (hb_color_get_blue (draw->background) / 255.f, 4); |
757 | 0 | stream.append_str (" rg\n"); |
758 | 0 | stream.append_num (ex); |
759 | 0 | stream.append_c (' '); |
760 | 0 | stream.append_num (ey); |
761 | 0 | stream.append_c (' '); |
762 | 0 | stream.append_num (ew); |
763 | 0 | stream.append_c (' '); |
764 | 0 | stream.append_num (eh); |
765 | 0 | stream.append_str (" re f\n"); |
766 | 0 | } |
767 | | |
768 | 3.36k | draw->flush_path (); |
769 | 3.36k | if (draw->body.length) |
770 | 1.28k | stream.append_len (draw->body.arrayZ, draw->body.length); |
771 | | |
772 | | /* Build PDF objects, tracking byte offsets for xref. */ |
773 | 3.36k | hb_vector_buf_t out; |
774 | 3.36k | hb_buf_recover_recycled (draw->recycled_blob, &out); |
775 | 3.36k | out.alloc (stream.length + 512); |
776 | | |
777 | 3.36k | unsigned offsets[5]; /* objects 1-4, plus end */ |
778 | | |
779 | 3.36k | out.append_str ("%PDF-1.4\n%\xC0\xC1\xC2\xC3\n"); |
780 | | |
781 | | /* Object 1: Catalog */ |
782 | 3.36k | offsets[0] = out.length; |
783 | 3.36k | out.append_str ("1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"); |
784 | | |
785 | | /* Object 2: Pages */ |
786 | 3.36k | offsets[1] = out.length; |
787 | 3.36k | out.append_str ("2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n"); |
788 | | |
789 | | /* Object 3: Page */ |
790 | 3.36k | offsets[2] = out.length; |
791 | 3.36k | out.append_str ("3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox ["); |
792 | 3.36k | out.append_num (ex); |
793 | 3.36k | out.append_c (' '); |
794 | 3.36k | out.append_num (ey); |
795 | 3.36k | out.append_c (' '); |
796 | 3.36k | out.append_num (ex + ew); |
797 | 3.36k | out.append_c (' '); |
798 | 3.36k | out.append_num (ey + eh); |
799 | 3.36k | out.append_str ("] /Contents 4 0 R"); |
800 | 3.36k | if (draw->pdf_extgstate_dict.length) |
801 | 0 | { |
802 | 0 | out.append_str (" /Resources << /ExtGState << "); |
803 | 0 | out.append_len (draw->pdf_extgstate_dict.arrayZ, draw->pdf_extgstate_dict.length); |
804 | 0 | out.append_str (">> >>"); |
805 | 0 | } |
806 | 3.36k | out.append_str (" >>\nendobj\n"); |
807 | | |
808 | | /* Object 4: Content stream */ |
809 | 3.36k | offsets[3] = out.length; |
810 | 3.36k | out.append_str ("4 0 obj\n<< /Length "); |
811 | 3.36k | out.append_unsigned (stream.length); |
812 | 3.36k | out.append_str (" >>\nstream\n"); |
813 | 3.36k | out.append_len (stream.arrayZ, stream.length); |
814 | 3.36k | out.append_str ("endstream\nendobj\n"); |
815 | | |
816 | | /* Cross-reference table */ |
817 | 3.36k | unsigned xref_offset = out.length; |
818 | 3.36k | out.append_str ("xref\n0 5\n"); |
819 | 3.36k | out.append_str ("0000000000 65535 f \n"); |
820 | 16.8k | for (unsigned i = 0; i < 4; i++) |
821 | 13.4k | { |
822 | 13.4k | char tmp[21]; |
823 | 13.4k | snprintf (tmp, sizeof (tmp), "%010u 00000 n \n", offsets[i]); |
824 | 13.4k | out.append_len (tmp, 20); |
825 | 13.4k | } |
826 | | |
827 | | /* Trailer */ |
828 | 3.36k | out.append_str ("trailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n"); |
829 | 3.36k | out.append_unsigned (xref_offset); |
830 | 3.36k | out.append_str ("\n%%EOF\n"); |
831 | | |
832 | 3.36k | hb_blob_t *blob = hb_buf_blob_from (&draw->recycled_blob, &out); |
833 | | |
834 | 3.36k | return blob; |
835 | 12.9k | } |
836 | | |
837 | | static hb_blob_t * |
838 | | hb_vector_draw_render_svg (hb_vector_draw_t *draw) |
839 | 12.7k | { |
840 | 12.7k | draw->flush_path (); |
841 | 12.7k | if (!draw->has_extents) |
842 | 9.54k | return nullptr; |
843 | | |
844 | 3.16k | hb_vector_buf_t out; |
845 | 3.16k | hb_buf_recover_recycled (draw->recycled_blob, &out); |
846 | 3.16k | unsigned estimated = draw->defs.length + |
847 | 3.16k | (draw->body.length ? draw->body.length : draw->path.length) + |
848 | 3.16k | 256; |
849 | 3.16k | out.alloc (estimated); |
850 | | /* Extents are in Y-up (font) space. SVG is Y-down. |
851 | | * The global scale(1,-1) wrapper flips content; the |
852 | | * viewBox maps the flipped range. */ |
853 | 3.16k | float vb_x = draw->extents.x; |
854 | 3.16k | float vb_y = -(draw->extents.y + draw->extents.height); |
855 | 3.16k | float vb_w = draw->extents.width; |
856 | 3.16k | float vb_h = draw->extents.height; |
857 | 3.16k | out.append_str ("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\""); |
858 | 3.16k | out.append_num (vb_x); |
859 | 3.16k | out.append_c (' '); |
860 | 3.16k | out.append_num (vb_y); |
861 | 3.16k | out.append_c (' '); |
862 | 3.16k | out.append_num (vb_w); |
863 | 3.16k | out.append_c (' '); |
864 | 3.16k | out.append_num (vb_h); |
865 | 3.16k | out.append_str ("\" width=\""); |
866 | 3.16k | out.append_num (vb_w); |
867 | 3.16k | out.append_str ("\" height=\""); |
868 | 3.16k | out.append_num (vb_h); |
869 | 3.16k | out.append_str ("\">\n"); |
870 | | |
871 | 3.16k | if (draw->defs.length) |
872 | 0 | { |
873 | 0 | out.append_str ("<defs>\n"); |
874 | 0 | out.append_len (draw->defs.arrayZ, draw->defs.length); |
875 | 0 | out.append_str ("</defs>\n"); |
876 | 0 | } |
877 | | |
878 | | /* Background rect. */ |
879 | 3.16k | if (hb_color_get_alpha (draw->background)) |
880 | 0 | { |
881 | 0 | out.append_str ("<rect x=\""); |
882 | 0 | out.append_num (vb_x); |
883 | 0 | out.append_str ("\" y=\""); |
884 | 0 | out.append_num (vb_y); |
885 | 0 | out.append_str ("\" width=\""); |
886 | 0 | out.append_num (vb_w); |
887 | 0 | out.append_str ("\" height=\""); |
888 | 0 | out.append_num (vb_h); |
889 | 0 | out.append_str ("\" fill=\"rgb("); |
890 | 0 | out.append_unsigned (hb_color_get_red (draw->background)); |
891 | 0 | out.append_c (','); |
892 | 0 | out.append_unsigned (hb_color_get_green (draw->background)); |
893 | 0 | out.append_c (','); |
894 | 0 | out.append_unsigned (hb_color_get_blue (draw->background)); |
895 | 0 | out.append_str (")\""); |
896 | 0 | if (hb_color_get_alpha (draw->background) < 255) |
897 | 0 | { |
898 | 0 | out.append_str (" fill-opacity=\""); |
899 | 0 | out.append_num (hb_color_get_alpha (draw->background) / 255.f, 4); |
900 | 0 | out.append_c ('"'); |
901 | 0 | } |
902 | 0 | out.append_str ("/>\n"); |
903 | 0 | } |
904 | | |
905 | 3.16k | draw->flush_path (); |
906 | 3.16k | if (draw->body.length) |
907 | 1.16k | { |
908 | 1.16k | out.append_str ("<g transform=\"scale(1,-1)\">\n"); |
909 | 1.16k | out.append_len (draw->body.arrayZ, draw->body.length); |
910 | 1.16k | out.append_str ("</g>\n"); |
911 | 1.16k | } |
912 | | |
913 | 3.16k | out.append_str ("</svg>\n"); |
914 | | |
915 | 3.16k | hb_blob_t *blob = hb_buf_blob_from (&draw->recycled_blob, &out); |
916 | | |
917 | 3.16k | return blob; |
918 | 12.7k | } |
919 | | |
920 | | /** |
921 | | * hb_vector_draw_render: |
922 | | * @draw: a draw context. |
923 | | * |
924 | | * Renders accumulated draw content to an output blob. |
925 | | * Accumulated content and extents are cleared on return, including |
926 | | * when rendering fails. User configuration is preserved. |
927 | | * |
928 | | * Return value: (transfer full) (nullable): output blob, or `NULL` if rendering cannot proceed. |
929 | | * |
930 | | * Since: 13.0.0 |
931 | | */ |
932 | | hb_blob_t * |
933 | | hb_vector_draw_render (hb_vector_draw_t *draw) |
934 | 25.6k | { |
935 | 25.6k | HB_SCOPE_GUARD (hb_vector_draw_clear (draw)); |
936 | 25.6k | switch (draw->format) |
937 | 25.6k | { |
938 | 12.7k | case HB_VECTOR_FORMAT_SVG: |
939 | 12.7k | return hb_vector_draw_render_svg (draw); |
940 | | |
941 | 12.9k | case HB_VECTOR_FORMAT_PDF: |
942 | 12.9k | return hb_vector_draw_render_pdf (draw); |
943 | | |
944 | 0 | case HB_VECTOR_FORMAT_INVALID: default: |
945 | 0 | return nullptr; |
946 | 25.6k | } |
947 | 25.6k | } |
948 | | |
949 | | /** |
950 | | * hb_vector_draw_clear: |
951 | | * @draw: a draw context. |
952 | | * |
953 | | * Discards accumulated draw output so @draw can be reused for |
954 | | * another render. User configuration (transform, scale factors, |
955 | | * precision, foreground, background, and budget) is preserved. Call |
956 | | * hb_vector_draw_reset() to also reset user configuration to defaults. |
957 | | * |
958 | | * Since: 14.2.0 |
959 | | */ |
960 | | void |
961 | | hb_vector_draw_clear (hb_vector_draw_t *draw) |
962 | 25.6k | { |
963 | 25.6k | draw->extents = {0, 0, 0, 0}; |
964 | 25.6k | draw->has_extents = false; |
965 | 25.6k | draw->defs.clear (); |
966 | 25.6k | draw->body.clear (); |
967 | 25.6k | draw->path.clear (); |
968 | 25.6k | draw->pdf_extgstate_dict.clear (); |
969 | 25.6k | draw->pdf_extgstate_count = 0; |
970 | 25.6k | draw->recharge_budget (); |
971 | 25.6k | } |
972 | | |
973 | | /** |
974 | | * hb_vector_draw_reset: |
975 | | * @draw: a draw context. |
976 | | * |
977 | | * Restores user configuration to defaults and clears accumulated content. |
978 | | * The output format and attached user data are preserved. |
979 | | * |
980 | | * Since: 13.0.0 |
981 | | */ |
982 | | void |
983 | | hb_vector_draw_reset (hb_vector_draw_t *draw) |
984 | 0 | { |
985 | 0 | draw->transform = {1, 0, 0, 1, 0, 0}; |
986 | 0 | draw->x_scale_factor = 1.f; |
987 | 0 | draw->y_scale_factor = 1.f; |
988 | 0 | draw->foreground = HB_COLOR (0, 0, 0, 255); |
989 | 0 | draw->background = HB_COLOR (0, 0, 0, 0); |
990 | 0 | draw->set_precision (2); |
991 | 0 | draw->budget = HB_BUDGET_DEFAULT; |
992 | 0 | hb_vector_draw_clear (draw); |
993 | 0 | } |
994 | | |
995 | | /** |
996 | | * hb_vector_draw_recycle_blob: |
997 | | * @draw: a draw context. |
998 | | * @blob: (transfer full) (nullable): previously rendered blob to recycle. |
999 | | * |
1000 | | * Provides a blob for internal buffer reuse by later render calls. |
1001 | | * The caller transfers ownership of one reference to @draw and must |
1002 | | * not destroy that reference after this call. Finish accessing @blob |
1003 | | * and its data before recycling: later renders may reuse the blob |
1004 | | * and overwrite its data. |
1005 | | * |
1006 | | * Since: 13.0.0 |
1007 | | */ |
1008 | | void |
1009 | | hb_vector_draw_recycle_blob (hb_vector_draw_t *draw, |
1010 | | hb_blob_t *blob) |
1011 | 5.21k | { |
1012 | 5.21k | hb_blob_destroy (draw->recycled_blob); |
1013 | 5.21k | draw->recycled_blob = nullptr; |
1014 | 5.21k | if (!blob || blob == hb_blob_get_empty ()) |
1015 | 0 | return; |
1016 | 5.21k | draw->recycled_blob = blob; |
1017 | 5.21k | } |