1 /*
2 * Copyright © 2011 Google, Inc.
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 * Google Author(s): Behdad Esfahbod
25 */
26
27 #ifndef HELPER_CAIRO_HH
28 #define HELPER_CAIRO_HH
29
30 #include "view-options.hh"
31 #include "output-options.hh"
32 #ifdef HAVE_CAIRO_FT
33 # include "helper-cairo-ft.hh"
34 #endif
35
36 #include <cairo.h>
37 #include <hb.h>
38 #include <hb-cairo.h>
39
40 #include "helper-cairo-ansi.hh"
41 #ifdef CAIRO_HAS_SVG_SURFACE
42 # include <cairo-svg.h>
43 #endif
44 #ifdef CAIRO_HAS_PDF_SURFACE
45 # include <cairo-pdf.h>
46 #endif
47 #ifdef CAIRO_HAS_PS_SURFACE
48 # include <cairo-ps.h>
49 # if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
50 # define HAS_EPS 1
51
52 static cairo_surface_t *
_cairo_eps_surface_create_for_stream(cairo_write_func_t write_func,void * closure,double width,double height)53 _cairo_eps_surface_create_for_stream (cairo_write_func_t write_func,
54 void *closure,
55 double width,
56 double height)
57 {
58 cairo_surface_t *surface;
59
60 surface = cairo_ps_surface_create_for_stream (write_func, closure, width, height);
61 cairo_ps_surface_set_eps (surface, true);
62
63 return surface;
64 }
65
66 # else
67 # undef HAS_EPS
68 # endif
69 #endif
70 #ifdef CAIRO_HAS_SCRIPT_SURFACE
71 # include <cairo-script.h>
72 #endif
73
74 static inline bool
helper_cairo_use_hb_draw(const font_options_t * font_opts)75 helper_cairo_use_hb_draw (const font_options_t *font_opts)
76 {
77 const char *env = getenv ("HB_DRAW");
78 if (!env)
79 /* Older cairo had a bug in rendering COLRv0 fonts in
80 * right-to-left direction as well as clipping issue
81 * with user-fonts.
82 *
83 * https://github.com/harfbuzz/harfbuzz/issues/4051 */
84 return cairo_version () >= CAIRO_VERSION_ENCODE (1, 17, 5);
85
86 return atoi (env);
87 }
88
89 static inline cairo_scaled_font_t *
helper_cairo_create_scaled_font(const font_options_t * font_opts,const view_options_t * view_opts)90 helper_cairo_create_scaled_font (const font_options_t *font_opts,
91 const view_options_t *view_opts)
92 {
93 hb_font_t *font = font_opts->font;
94 bool use_hb_draw = true;
95
96 #ifdef HAVE_CAIRO_FT
97 use_hb_draw = helper_cairo_use_hb_draw (font_opts);
98 #endif
99
100
101 cairo_font_face_t *cairo_face = nullptr;
102 if (use_hb_draw)
103 {
104 cairo_face = hb_cairo_font_face_create_for_font (font);
105 hb_cairo_font_face_set_scale_factor (cairo_face, 1 << font_opts->subpixel_bits);
106 }
107 #ifdef HAVE_CAIRO_FT
108 else
109 cairo_face = helper_cairo_create_ft_font_face (font_opts);
110 #endif
111
112 cairo_matrix_t ctm, font_matrix;
113 cairo_font_options_t *font_options;
114
115 cairo_matrix_init_identity (&ctm);
116 cairo_matrix_init_scale (&font_matrix,
117 font_opts->font_size_x,
118 font_opts->font_size_y);
119 if (!use_hb_draw)
120 font_matrix.xy = -font_opts->slant * font_opts->font_size_x;
121
122 font_options = cairo_font_options_create ();
123 cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
124 cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF);
125 #ifdef CAIRO_COLOR_PALETTE_DEFAULT
126 cairo_font_options_set_color_palette (font_options, view_opts->palette);
127 #endif
128 #ifdef HAVE_CAIRO_FONT_OPTIONS_GET_CUSTOM_PALETTE_COLOR
129 if (view_opts->custom_palette)
130 {
131 char **entries = g_strsplit (view_opts->custom_palette, ",", -1);
132 unsigned idx = 0;
133 for (unsigned i = 0; entries[i]; i++)
134 {
135 const char *p = strchr (entries[i], '=');
136 if (!p)
137 p = entries[i];
138 else
139 {
140 sscanf (entries[i], "%u", &idx);
141 p++;
142 }
143
144 unsigned fr, fg, fb, fa;
145 fr = fg = fb = fa = 0;
146 if (parse_color (p, fr, fg,fb, fa))
147 cairo_font_options_set_custom_palette_color (font_options, idx, fr / 255., fg / 255., fb / 255., fa / 255.);
148
149 idx++;
150 }
151 g_strfreev (entries);
152 }
153 #endif
154
155 cairo_scaled_font_t *scaled_font = cairo_scaled_font_create (cairo_face,
156 &font_matrix,
157 &ctm,
158 font_options);
159 if (cairo_scaled_font_status (scaled_font) == CAIRO_STATUS_INVALID_MATRIX)
160 {
161 // Set font matrix to 0, which *does* work with cairo_scaled_font_create()
162 font_matrix.xx = font_matrix.yy = 0;
163 font_matrix.xy = font_matrix.yx = 0;
164 font_matrix.x0 = font_matrix.y0 = 0;
165 scaled_font = cairo_scaled_font_create (cairo_face,
166 &font_matrix,
167 &ctm,
168 font_options);
169
170 }
171
172 cairo_font_options_destroy (font_options);
173 cairo_font_face_destroy (cairo_face);
174
175 return scaled_font;
176 }
177
178 static inline bool
helper_cairo_scaled_font_has_color(cairo_scaled_font_t * scaled_font)179 helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font)
180 {
181 hb_font_t *font = hb_cairo_font_face_get_font (cairo_scaled_font_get_font_face (scaled_font));
182
183 #ifdef HAVE_CAIRO_FT
184 if (!font)
185 return helper_cairo_ft_scaled_font_has_color (scaled_font);
186 #endif
187
188 hb_face_t *face = hb_font_get_face (font);
189
190 return hb_ot_color_has_png (face) ||
191 hb_ot_color_has_layers (face) ||
192 hb_ot_color_has_paint (face);
193 }
194
195
196 enum class image_protocol_t {
197 NONE = 0,
198 ITERM2,
199 KITTY,
200 };
201
202 struct finalize_closure_t {
203 void (*callback)(finalize_closure_t *);
204 cairo_surface_t *surface;
205 cairo_write_func_t write_func;
206 void *closure;
207 image_protocol_t protocol;
208 };
209 static cairo_user_data_key_t finalize_closure_key;
210
211
212 static void
finalize_ansi(finalize_closure_t * closure)213 finalize_ansi (finalize_closure_t *closure)
214 {
215 cairo_status_t status;
216 status = helper_cairo_surface_write_to_ansi_stream (closure->surface,
217 closure->write_func,
218 closure->closure);
219 if (status != CAIRO_STATUS_SUCCESS)
220 fail (false, "Failed to write output: %s",
221 cairo_status_to_string (status));
222 }
223
224 static cairo_surface_t *
_cairo_ansi_surface_create_for_stream(cairo_write_func_t write_func,void * closure,double width,double height,cairo_content_t content,image_protocol_t protocol HB_UNUSED)225 _cairo_ansi_surface_create_for_stream (cairo_write_func_t write_func,
226 void *closure,
227 double width,
228 double height,
229 cairo_content_t content,
230 image_protocol_t protocol HB_UNUSED)
231 {
232 cairo_surface_t *surface;
233 int w = ceil (width);
234 int h = ceil (height);
235
236 switch (content) {
237 case CAIRO_CONTENT_ALPHA:
238 surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
239 break;
240 default:
241 case CAIRO_CONTENT_COLOR:
242 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
243 break;
244 case CAIRO_CONTENT_COLOR_ALPHA:
245 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
246 break;
247 }
248 cairo_status_t status = cairo_surface_status (surface);
249 if (status != CAIRO_STATUS_SUCCESS)
250 fail (false, "Failed to create cairo surface: %s",
251 cairo_status_to_string (status));
252
253 finalize_closure_t *ansi_closure = g_new0 (finalize_closure_t, 1);
254 ansi_closure->callback = finalize_ansi;
255 ansi_closure->surface = surface;
256 ansi_closure->write_func = write_func;
257 ansi_closure->closure = closure;
258
259 if (cairo_surface_set_user_data (surface,
260 &finalize_closure_key,
261 (void *) ansi_closure,
262 (cairo_destroy_func_t) g_free))
263 g_free ((void *) closure);
264
265 return surface;
266 }
267
268
269 #ifdef CAIRO_HAS_PNG_FUNCTIONS
270
271 static cairo_status_t
byte_array_write_func(void * closure,const unsigned char * data,unsigned int size)272 byte_array_write_func (void *closure,
273 const unsigned char *data,
274 unsigned int size)
275 {
276 g_byte_array_append ((GByteArray *) closure, data, size);
277 return CAIRO_STATUS_SUCCESS;
278 }
279
280 static void
finalize_png(finalize_closure_t * closure)281 finalize_png (finalize_closure_t *closure)
282 {
283 cairo_status_t status;
284 GByteArray *bytes = nullptr;
285 GString *string;
286 gchar *base64;
287 size_t base64_len;
288
289 if (closure->protocol == image_protocol_t::NONE)
290 {
291 status = cairo_surface_write_to_png_stream (closure->surface,
292 closure->write_func,
293 closure->closure);
294 }
295 else
296 {
297 bytes = g_byte_array_new ();
298 status = cairo_surface_write_to_png_stream (closure->surface,
299 byte_array_write_func,
300 bytes);
301 }
302
303 if (status != CAIRO_STATUS_SUCCESS)
304 fail (false, "Failed to write output: %s",
305 cairo_status_to_string (status));
306
307 if (closure->protocol == image_protocol_t::NONE)
308 return;
309
310 base64 = g_base64_encode (bytes->data, bytes->len);
311 base64_len = strlen (base64);
312
313 string = g_string_new (NULL);
314 if (closure->protocol == image_protocol_t::ITERM2)
315 {
316 /* https://iterm2.com/documentation-images.html */
317 g_string_printf (string, "\033]1337;File=inline=1;size=%zu:%s\a\n",
318 base64_len, base64);
319 }
320 else if (closure->protocol == image_protocol_t::KITTY)
321 {
322 #define CHUNK_SIZE 4096
323 /* https://sw.kovidgoyal.net/kitty/graphics-protocol.html */
324 for (size_t pos = 0; pos < base64_len; pos += CHUNK_SIZE)
325 {
326 size_t len = base64_len - pos;
327
328 if (pos == 0)
329 g_string_append (string, "\033_Ga=T,f=100,m=");
330 else
331 g_string_append (string, "\033_Gm=");
332
333 if (len > CHUNK_SIZE)
334 {
335 g_string_append (string, "1;");
336 g_string_append_len (string, base64 + pos, CHUNK_SIZE);
337 }
338 else
339 {
340 g_string_append (string, "0;");
341 g_string_append_len (string, base64 + pos, len);
342 }
343
344 g_string_append (string, "\033\\");
345 }
346 g_string_append (string, "\n");
347 #undef CHUNK_SIZE
348 }
349
350 closure->write_func (closure->closure, (unsigned char *) string->str, string->len);
351
352 g_byte_array_unref (bytes);
353 g_free (base64);
354 g_string_free (string, TRUE);
355 }
356
357 static cairo_surface_t *
_cairo_png_surface_create_for_stream(cairo_write_func_t write_func,void * closure,double width,double height,cairo_content_t content,image_protocol_t protocol)358 _cairo_png_surface_create_for_stream (cairo_write_func_t write_func,
359 void *closure,
360 double width,
361 double height,
362 cairo_content_t content,
363 image_protocol_t protocol)
364 {
365 cairo_surface_t *surface;
366 int w = ceil (width);
367 int h = ceil (height);
368
369 switch (content) {
370 case CAIRO_CONTENT_ALPHA:
371 surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
372 break;
373 default:
374 case CAIRO_CONTENT_COLOR:
375 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
376 break;
377 case CAIRO_CONTENT_COLOR_ALPHA:
378 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
379 break;
380 }
381 cairo_status_t status = cairo_surface_status (surface);
382 if (status != CAIRO_STATUS_SUCCESS)
383 fail (false, "Failed to create cairo surface: %s",
384 cairo_status_to_string (status));
385
386 finalize_closure_t *png_closure = g_new0 (finalize_closure_t, 1);
387 png_closure->callback = finalize_png;
388 png_closure->surface = surface;
389 png_closure->write_func = write_func;
390 png_closure->closure = closure;
391 png_closure->protocol = protocol;
392
393 if (cairo_surface_set_user_data (surface,
394 &finalize_closure_key,
395 (void *) png_closure,
396 (cairo_destroy_func_t) g_free))
397 g_free ((void *) closure);
398
399 return surface;
400 }
401
402 #endif
403
404 #ifdef CAIRO_HAS_SCRIPT_SURFACE
405
406 static cairo_surface_t *
_cairo_script_surface_create_for_stream(cairo_write_func_t write_func,void * closure,double width,double height,cairo_content_t content,image_protocol_t protocol HB_UNUSED)407 _cairo_script_surface_create_for_stream (cairo_write_func_t write_func,
408 void *closure,
409 double width,
410 double height,
411 cairo_content_t content,
412 image_protocol_t protocol HB_UNUSED)
413 {
414 cairo_device_t *script = cairo_script_create_for_stream (write_func, closure);
415 cairo_surface_t *surface = cairo_script_surface_create (script, content, width, height);
416 cairo_device_destroy (script);
417 return surface;
418 }
419
420 #endif
421
422 static cairo_status_t
stdio_write_func(void * closure,const unsigned char * data,unsigned int size)423 stdio_write_func (void *closure,
424 const unsigned char *data,
425 unsigned int size)
426 {
427 FILE *fp = (FILE *) closure;
428
429 while (size) {
430 size_t ret = fwrite (data, 1, size, fp);
431 size -= ret;
432 data += ret;
433 if (size && ferror (fp))
434 fail (false, "Failed to write output: %s", strerror (errno));
435 }
436
437 return CAIRO_STATUS_SUCCESS;
438 }
439
440 static const char *helper_cairo_supported_formats[] =
441 {
442 "ansi",
443 #ifdef CAIRO_HAS_PNG_FUNCTIONS
444 "png",
445 #endif
446 #ifdef CAIRO_HAS_SVG_SURFACE
447 "svg",
448 #endif
449 #ifdef CAIRO_HAS_PDF_SURFACE
450 "pdf",
451 #endif
452 #ifdef CAIRO_HAS_PS_SURFACE
453 "ps",
454 #ifdef HAS_EPS
455 "eps",
456 #endif
457 #endif
458 #ifdef CAIRO_HAS_SCRIPT_SURFACE
459 "script",
460 #endif
461 nullptr
462 };
463
464 template <typename view_options_t,
465 typename output_options_type>
466 static inline cairo_t *
helper_cairo_create_context(double w,double h,view_options_t * view_opts,output_options_type * out_opts,cairo_content_t content)467 helper_cairo_create_context (double w, double h,
468 view_options_t *view_opts,
469 output_options_type *out_opts,
470 cairo_content_t content)
471 {
472 cairo_surface_t *(*constructor) (cairo_write_func_t write_func,
473 void *closure,
474 double width,
475 double height) = nullptr;
476 cairo_surface_t *(*constructor2) (cairo_write_func_t write_func,
477 void *closure,
478 double width,
479 double height,
480 cairo_content_t content,
481 image_protocol_t protocol) = nullptr;
482
483 image_protocol_t protocol = image_protocol_t::NONE;
484 const char *extension = out_opts->output_format;
485 if (!extension) {
486 #if HAVE_ISATTY
487 if (isatty (fileno (out_opts->out_fp)))
488 {
489 #ifdef CAIRO_HAS_PNG_FUNCTIONS
490 const char *name;
491 /* https://gitlab.com/gnachman/iterm2/-/issues/7154 */
492 if ((name = getenv ("LC_TERMINAL")) != nullptr &&
493 0 == g_ascii_strcasecmp (name, "iTerm2"))
494 {
495 extension = "png";
496 protocol = image_protocol_t::ITERM2;
497 }
498 else if ((name = getenv ("TERM_PROGRAM")) != nullptr &&
499 0 == g_ascii_strcasecmp (name, "WezTerm"))
500 {
501 extension = "png";
502 protocol = image_protocol_t::ITERM2;
503 }
504 else if ((name = getenv ("TERM")) != nullptr &&
505 0 == g_ascii_strcasecmp (name, "xterm-kitty"))
506 {
507 extension = "png";
508 protocol = image_protocol_t::KITTY;
509 }
510 else
511 extension = "ansi";
512 #else
513 extension = "ansi";
514 #endif
515 }
516 else
517 #endif
518 {
519 #ifdef CAIRO_HAS_PNG_FUNCTIONS
520 extension = "png";
521 #else
522 extension = "ansi";
523 #endif
524 }
525 }
526 if (0)
527 ;
528 else if (0 == g_ascii_strcasecmp (extension, "ansi"))
529 constructor2 = _cairo_ansi_surface_create_for_stream;
530 #ifdef CAIRO_HAS_PNG_FUNCTIONS
531 else if (0 == g_ascii_strcasecmp (extension, "png"))
532 constructor2 = _cairo_png_surface_create_for_stream;
533 #endif
534 #ifdef CAIRO_HAS_SVG_SURFACE
535 else if (0 == g_ascii_strcasecmp (extension, "svg"))
536 constructor = cairo_svg_surface_create_for_stream;
537 #endif
538 #ifdef CAIRO_HAS_PDF_SURFACE
539 else if (0 == g_ascii_strcasecmp (extension, "pdf"))
540 constructor = cairo_pdf_surface_create_for_stream;
541 #endif
542 #ifdef CAIRO_HAS_PS_SURFACE
543 else if (0 == g_ascii_strcasecmp (extension, "ps"))
544 constructor = cairo_ps_surface_create_for_stream;
545 #ifdef HAS_EPS
546 else if (0 == g_ascii_strcasecmp (extension, "eps"))
547 constructor = _cairo_eps_surface_create_for_stream;
548 #endif
549 #ifdef CAIRO_HAS_SCRIPT_SURFACE
550 else if (0 == g_ascii_strcasecmp (extension, "script"))
551 constructor2 = _cairo_script_surface_create_for_stream;
552 #endif
553 #endif
554
555
556 unsigned int fr, fg, fb, fa, br, bg, bb, ba;
557 const char *color;
558 br = bg = bb = ba = 255;
559 color = view_opts->back ? view_opts->back : DEFAULT_BACK;
560 parse_color (color, br, bg, bb, ba);
561 fr = fg = fb = 0; fa = 255;
562 color = view_opts->fore ? view_opts->fore : DEFAULT_FORE;
563 parse_color (color, fr, fg, fb, fa);
564
565 if (content == CAIRO_CONTENT_ALPHA)
566 {
567 if (view_opts->show_extents ||
568 br != bg || bg != bb ||
569 fr != fg || fg != fb)
570 content = CAIRO_CONTENT_COLOR;
571 }
572 if (ba != 255)
573 content = CAIRO_CONTENT_COLOR_ALPHA;
574
575 cairo_surface_t *surface;
576 FILE *f = out_opts->out_fp;
577 if (constructor)
578 surface = constructor (stdio_write_func, f, w, h);
579 else if (constructor2)
580 surface = constructor2 (stdio_write_func, f, w, h, content, protocol);
581 else
582 fail (false, "Unknown output format `%s'; supported formats are: %s%s",
583 extension,
584 g_strjoinv ("/", const_cast<char**> (helper_cairo_supported_formats)),
585 out_opts->explicit_output_format ? "" :
586 "\nTry setting format using --output-format");
587
588 cairo_t *cr = cairo_create (surface);
589 content = cairo_surface_get_content (surface);
590
591 switch (content) {
592 case CAIRO_CONTENT_ALPHA:
593 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
594 cairo_set_source_rgba (cr, 1., 1., 1., br / 255.);
595 cairo_paint (cr);
596 cairo_set_source_rgba (cr, 1., 1., 1.,
597 (fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.)));
598 break;
599 default:
600 case CAIRO_CONTENT_COLOR:
601 case CAIRO_CONTENT_COLOR_ALPHA:
602 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
603 cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.);
604 cairo_paint (cr);
605 cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.);
606 break;
607 }
608
609 cairo_surface_destroy (surface);
610 return cr;
611 }
612
613 static inline void
helper_cairo_destroy_context(cairo_t * cr)614 helper_cairo_destroy_context (cairo_t *cr)
615 {
616 finalize_closure_t *closure = (finalize_closure_t *)
617 cairo_surface_get_user_data (cairo_get_target (cr),
618 &finalize_closure_key);
619 if (closure)
620 closure->callback (closure);
621
622 cairo_status_t status = cairo_status (cr);
623 if (status != CAIRO_STATUS_SUCCESS)
624 fail (false, "Failed: %s",
625 cairo_status_to_string (status));
626 cairo_destroy (cr);
627 }
628
629
630 struct helper_cairo_line_t {
631 cairo_glyph_t *glyphs = nullptr;
632 unsigned int num_glyphs = 0;
633 char *utf8 = nullptr;
634 unsigned int utf8_len = 0;
635 cairo_text_cluster_t *clusters = nullptr;
636 unsigned int num_clusters = 0;
637 cairo_text_cluster_flags_t cluster_flags = (cairo_text_cluster_flags_t) 0;
638
helper_cairo_line_thelper_cairo_line_t639 helper_cairo_line_t (const char *utf8_,
640 unsigned utf8_len_,
641 hb_buffer_t *buffer,
642 hb_bool_t utf8_clusters,
643 unsigned subpixel_bits) :
644 utf8 (utf8_ ? g_strndup (utf8_, utf8_len_) : nullptr),
645 utf8_len (utf8_len_)
646 {
647 hb_cairo_glyphs_from_buffer (buffer,
648 utf8_clusters,
649 1 << subpixel_bits, 1 << subpixel_bits,
650 0., 0.,
651 utf8, utf8_len,
652 &glyphs, &num_glyphs,
653 &clusters, &num_clusters,
654 &cluster_flags);
655 }
656
finishhelper_cairo_line_t657 void finish ()
658 {
659 if (glyphs)
660 cairo_glyph_free (glyphs);
661 if (clusters)
662 cairo_text_cluster_free (clusters);
663 g_free (utf8);
664 }
665
get_advancehelper_cairo_line_t666 void get_advance (double *x_advance, double *y_advance)
667 {
668 *x_advance = glyphs[num_glyphs].x;
669 *y_advance = glyphs[num_glyphs].y;
670 }
671 };
672
673 #endif
674