• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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