xref: /aosp_15_r20/external/harfbuzz_ng/util/helper-cairo.hh (revision 2d1272b857b1f7575e6e246373e1cb218663db8a)
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 
160   cairo_font_options_destroy (font_options);
161   cairo_font_face_destroy (cairo_face);
162 
163   return scaled_font;
164 }
165 
166 static inline bool
helper_cairo_scaled_font_has_color(cairo_scaled_font_t * scaled_font)167 helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font)
168 {
169   hb_font_t *font = hb_cairo_font_face_get_font (cairo_scaled_font_get_font_face (scaled_font));
170 
171 #ifdef HAVE_CAIRO_FT
172   if (!font)
173     return helper_cairo_ft_scaled_font_has_color (scaled_font);
174 #endif
175 
176   hb_face_t *face = hb_font_get_face (font);
177 
178   return hb_ot_color_has_png (face) ||
179          hb_ot_color_has_layers (face) ||
180          hb_ot_color_has_paint (face);
181 }
182 
183 
184 enum class image_protocol_t {
185   NONE = 0,
186   ITERM2,
187   KITTY,
188 };
189 
190 struct finalize_closure_t {
191   void (*callback)(finalize_closure_t *);
192   cairo_surface_t *surface;
193   cairo_write_func_t write_func;
194   void *closure;
195   image_protocol_t protocol;
196 };
197 static cairo_user_data_key_t finalize_closure_key;
198 
199 
200 static void
finalize_ansi(finalize_closure_t * closure)201 finalize_ansi (finalize_closure_t *closure)
202 {
203   cairo_status_t status;
204   status = helper_cairo_surface_write_to_ansi_stream (closure->surface,
205 						      closure->write_func,
206 						      closure->closure);
207   if (status != CAIRO_STATUS_SUCCESS)
208     fail (false, "Failed to write output: %s",
209 	  cairo_status_to_string (status));
210 }
211 
212 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)213 _cairo_ansi_surface_create_for_stream (cairo_write_func_t write_func,
214 				       void *closure,
215 				       double width,
216 				       double height,
217 				       cairo_content_t content,
218 				       image_protocol_t protocol HB_UNUSED)
219 {
220   cairo_surface_t *surface;
221   int w = ceil (width);
222   int h = ceil (height);
223 
224   switch (content) {
225     case CAIRO_CONTENT_ALPHA:
226       surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
227       break;
228     default:
229     case CAIRO_CONTENT_COLOR:
230       surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
231       break;
232     case CAIRO_CONTENT_COLOR_ALPHA:
233       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
234       break;
235   }
236   cairo_status_t status = cairo_surface_status (surface);
237   if (status != CAIRO_STATUS_SUCCESS)
238     fail (false, "Failed to create cairo surface: %s",
239 	  cairo_status_to_string (status));
240 
241   finalize_closure_t *ansi_closure = g_new0 (finalize_closure_t, 1);
242   ansi_closure->callback = finalize_ansi;
243   ansi_closure->surface = surface;
244   ansi_closure->write_func = write_func;
245   ansi_closure->closure = closure;
246 
247   if (cairo_surface_set_user_data (surface,
248 				   &finalize_closure_key,
249 				   (void *) ansi_closure,
250 				   (cairo_destroy_func_t) g_free))
251     g_free ((void *) closure);
252 
253   return surface;
254 }
255 
256 
257 #ifdef CAIRO_HAS_PNG_FUNCTIONS
258 
259 static cairo_status_t
byte_array_write_func(void * closure,const unsigned char * data,unsigned int size)260 byte_array_write_func (void                *closure,
261 		       const unsigned char *data,
262 		       unsigned int         size)
263 {
264   g_byte_array_append ((GByteArray *) closure, data, size);
265   return CAIRO_STATUS_SUCCESS;
266 }
267 
268 static void
finalize_png(finalize_closure_t * closure)269 finalize_png (finalize_closure_t *closure)
270 {
271   cairo_status_t status;
272   GByteArray *bytes = nullptr;
273   GString *string;
274   gchar *base64;
275   size_t base64_len;
276 
277   if (closure->protocol == image_protocol_t::NONE)
278   {
279     status = cairo_surface_write_to_png_stream (closure->surface,
280 						closure->write_func,
281 						closure->closure);
282   }
283   else
284   {
285     bytes = g_byte_array_new ();
286     status = cairo_surface_write_to_png_stream (closure->surface,
287 						byte_array_write_func,
288 						bytes);
289   }
290 
291   if (status != CAIRO_STATUS_SUCCESS)
292     fail (false, "Failed to write output: %s",
293 	  cairo_status_to_string (status));
294 
295   if (closure->protocol == image_protocol_t::NONE)
296     return;
297 
298   base64 = g_base64_encode (bytes->data, bytes->len);
299   base64_len = strlen (base64);
300 
301   string = g_string_new (NULL);
302   if (closure->protocol == image_protocol_t::ITERM2)
303   {
304     /* https://iterm2.com/documentation-images.html */
305     g_string_printf (string, "\033]1337;File=inline=1;size=%zu:%s\a\n",
306 		     base64_len, base64);
307   }
308   else if (closure->protocol == image_protocol_t::KITTY)
309   {
310 #define CHUNK_SIZE 4096
311     /* https://sw.kovidgoyal.net/kitty/graphics-protocol.html */
312     for (size_t pos = 0; pos < base64_len; pos += CHUNK_SIZE)
313     {
314       size_t len = base64_len - pos;
315 
316       if (pos == 0)
317 	g_string_append (string, "\033_Ga=T,f=100,m=");
318       else
319 	g_string_append (string, "\033_Gm=");
320 
321       if (len > CHUNK_SIZE)
322       {
323 	g_string_append (string, "1;");
324 	g_string_append_len (string, base64 + pos, CHUNK_SIZE);
325       }
326       else
327       {
328 	g_string_append (string, "0;");
329 	g_string_append_len (string, base64 + pos, len);
330       }
331 
332       g_string_append (string, "\033\\");
333     }
334     g_string_append (string, "\n");
335 #undef CHUNK_SIZE
336   }
337 
338   closure->write_func (closure->closure, (unsigned char *) string->str, string->len);
339 
340   g_byte_array_unref (bytes);
341   g_free (base64);
342   g_string_free (string, TRUE);
343 }
344 
345 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)346 _cairo_png_surface_create_for_stream (cairo_write_func_t write_func,
347 				      void *closure,
348 				      double width,
349 				      double height,
350 				      cairo_content_t content,
351 				      image_protocol_t protocol)
352 {
353   cairo_surface_t *surface;
354   int w = ceil (width);
355   int h = ceil (height);
356 
357   switch (content) {
358     case CAIRO_CONTENT_ALPHA:
359       surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
360       break;
361     default:
362     case CAIRO_CONTENT_COLOR:
363       surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
364       break;
365     case CAIRO_CONTENT_COLOR_ALPHA:
366       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
367       break;
368   }
369   cairo_status_t status = cairo_surface_status (surface);
370   if (status != CAIRO_STATUS_SUCCESS)
371     fail (false, "Failed to create cairo surface: %s",
372 	  cairo_status_to_string (status));
373 
374   finalize_closure_t *png_closure = g_new0 (finalize_closure_t, 1);
375   png_closure->callback = finalize_png;
376   png_closure->surface = surface;
377   png_closure->write_func = write_func;
378   png_closure->closure = closure;
379   png_closure->protocol = protocol;
380 
381   if (cairo_surface_set_user_data (surface,
382 				   &finalize_closure_key,
383 				   (void *) png_closure,
384 				   (cairo_destroy_func_t) g_free))
385     g_free ((void *) closure);
386 
387   return surface;
388 }
389 
390 #endif
391 
392 #ifdef CAIRO_HAS_SCRIPT_SURFACE
393 
394 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)395 _cairo_script_surface_create_for_stream (cairo_write_func_t write_func,
396 				         void *closure,
397 				         double width,
398 				         double height,
399 				         cairo_content_t content,
400 				         image_protocol_t protocol HB_UNUSED)
401 {
402   cairo_device_t *script = cairo_script_create_for_stream (write_func, closure);
403   cairo_surface_t *surface = cairo_script_surface_create (script, content, width, height);
404   cairo_device_destroy (script);
405   return surface;
406 }
407 
408 #endif
409 
410 static cairo_status_t
stdio_write_func(void * closure,const unsigned char * data,unsigned int size)411 stdio_write_func (void                *closure,
412 		  const unsigned char *data,
413 		  unsigned int         size)
414 {
415   FILE *fp = (FILE *) closure;
416 
417   while (size) {
418     size_t ret = fwrite (data, 1, size, fp);
419     size -= ret;
420     data += ret;
421     if (size && ferror (fp))
422       fail (false, "Failed to write output: %s", strerror (errno));
423   }
424 
425   return CAIRO_STATUS_SUCCESS;
426 }
427 
428 static const char *helper_cairo_supported_formats[] =
429 {
430   "ansi",
431   #ifdef CAIRO_HAS_PNG_FUNCTIONS
432   "png",
433   #endif
434   #ifdef CAIRO_HAS_SVG_SURFACE
435   "svg",
436   #endif
437   #ifdef CAIRO_HAS_PDF_SURFACE
438   "pdf",
439   #endif
440   #ifdef CAIRO_HAS_PS_SURFACE
441   "ps",
442    #ifdef HAS_EPS
443     "eps",
444    #endif
445   #endif
446   #ifdef CAIRO_HAS_SCRIPT_SURFACE
447   "script",
448   #endif
449   nullptr
450 };
451 
452 template <typename view_options_t,
453 	 typename output_options_type>
454 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)455 helper_cairo_create_context (double w, double h,
456 			     view_options_t *view_opts,
457 			     output_options_type *out_opts,
458 			     cairo_content_t content)
459 {
460   cairo_surface_t *(*constructor) (cairo_write_func_t write_func,
461 				   void *closure,
462 				   double width,
463 				   double height) = nullptr;
464   cairo_surface_t *(*constructor2) (cairo_write_func_t write_func,
465 				    void *closure,
466 				    double width,
467 				    double height,
468 				    cairo_content_t content,
469 				    image_protocol_t protocol) = nullptr;
470 
471   image_protocol_t protocol = image_protocol_t::NONE;
472   const char *extension = out_opts->output_format;
473   if (!extension) {
474 #if HAVE_ISATTY
475     if (isatty (fileno (out_opts->out_fp)))
476     {
477 #ifdef CAIRO_HAS_PNG_FUNCTIONS
478       const char *name;
479       /* https://gitlab.com/gnachman/iterm2/-/issues/7154 */
480       if ((name = getenv ("LC_TERMINAL")) != nullptr &&
481 	  0 == g_ascii_strcasecmp (name, "iTerm2"))
482       {
483 	extension = "png";
484 	protocol = image_protocol_t::ITERM2;
485       }
486       else if ((name = getenv ("TERM_PROGRAM")) != nullptr &&
487 	  0 == g_ascii_strcasecmp (name, "WezTerm"))
488       {
489 	extension = "png";
490 	protocol = image_protocol_t::ITERM2;
491       }
492       else if ((name = getenv ("TERM")) != nullptr &&
493 	       0 == g_ascii_strcasecmp (name, "xterm-kitty"))
494       {
495 	extension = "png";
496 	protocol = image_protocol_t::KITTY;
497       }
498       else
499 	extension = "ansi";
500 #else
501       extension = "ansi";
502 #endif
503     }
504     else
505 #endif
506     {
507 #ifdef CAIRO_HAS_PNG_FUNCTIONS
508       extension = "png";
509 #else
510       extension = "ansi";
511 #endif
512     }
513   }
514   if (0)
515     ;
516     else if (0 == g_ascii_strcasecmp (extension, "ansi"))
517       constructor2 = _cairo_ansi_surface_create_for_stream;
518   #ifdef CAIRO_HAS_PNG_FUNCTIONS
519     else if (0 == g_ascii_strcasecmp (extension, "png"))
520       constructor2 = _cairo_png_surface_create_for_stream;
521   #endif
522   #ifdef CAIRO_HAS_SVG_SURFACE
523     else if (0 == g_ascii_strcasecmp (extension, "svg"))
524       constructor = cairo_svg_surface_create_for_stream;
525   #endif
526   #ifdef CAIRO_HAS_PDF_SURFACE
527     else if (0 == g_ascii_strcasecmp (extension, "pdf"))
528       constructor = cairo_pdf_surface_create_for_stream;
529   #endif
530   #ifdef CAIRO_HAS_PS_SURFACE
531     else if (0 == g_ascii_strcasecmp (extension, "ps"))
532       constructor = cairo_ps_surface_create_for_stream;
533    #ifdef HAS_EPS
534     else if (0 == g_ascii_strcasecmp (extension, "eps"))
535       constructor = _cairo_eps_surface_create_for_stream;
536    #endif
537    #ifdef CAIRO_HAS_SCRIPT_SURFACE
538     else if (0 == g_ascii_strcasecmp (extension, "script"))
539       constructor2 = _cairo_script_surface_create_for_stream;
540    #endif
541   #endif
542 
543 
544   unsigned int fr, fg, fb, fa, br, bg, bb, ba;
545   const char *color;
546   br = bg = bb = ba = 255;
547   color = view_opts->back ? view_opts->back : DEFAULT_BACK;
548   parse_color (color, br, bg, bb, ba);
549   fr = fg = fb = 0; fa = 255;
550   color = view_opts->fore ? view_opts->fore : DEFAULT_FORE;
551   parse_color (color, fr, fg, fb, fa);
552 
553   if (content == CAIRO_CONTENT_ALPHA)
554   {
555     if (view_opts->show_extents ||
556 	br != bg || bg != bb ||
557 	fr != fg || fg != fb)
558       content = CAIRO_CONTENT_COLOR;
559   }
560   if (ba != 255)
561     content = CAIRO_CONTENT_COLOR_ALPHA;
562 
563   cairo_surface_t *surface;
564   FILE *f = out_opts->out_fp;
565   if (constructor)
566     surface = constructor (stdio_write_func, f, w, h);
567   else if (constructor2)
568     surface = constructor2 (stdio_write_func, f, w, h, content, protocol);
569   else
570     fail (false, "Unknown output format `%s'; supported formats are: %s%s",
571 	  extension,
572 	  g_strjoinv ("/", const_cast<char**> (helper_cairo_supported_formats)),
573 	  out_opts->explicit_output_format ? "" :
574 	  "\nTry setting format using --output-format");
575 
576   cairo_t *cr = cairo_create (surface);
577   content = cairo_surface_get_content (surface);
578 
579   switch (content) {
580     case CAIRO_CONTENT_ALPHA:
581       cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
582       cairo_set_source_rgba (cr, 1., 1., 1., br / 255.);
583       cairo_paint (cr);
584       cairo_set_source_rgba (cr, 1., 1., 1.,
585 			     (fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.)));
586       break;
587     default:
588     case CAIRO_CONTENT_COLOR:
589     case CAIRO_CONTENT_COLOR_ALPHA:
590       cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
591       cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.);
592       cairo_paint (cr);
593       cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.);
594       break;
595   }
596 
597   cairo_surface_destroy (surface);
598   return cr;
599 }
600 
601 static inline void
helper_cairo_destroy_context(cairo_t * cr)602 helper_cairo_destroy_context (cairo_t *cr)
603 {
604   finalize_closure_t *closure = (finalize_closure_t *)
605 				cairo_surface_get_user_data (cairo_get_target (cr),
606 							     &finalize_closure_key);
607   if (closure)
608     closure->callback (closure);
609 
610   cairo_status_t status = cairo_status (cr);
611   if (status != CAIRO_STATUS_SUCCESS)
612     fail (false, "Failed: %s",
613 	  cairo_status_to_string (status));
614   cairo_destroy (cr);
615 }
616 
617 
618 struct helper_cairo_line_t {
619   cairo_glyph_t *glyphs = nullptr;
620   unsigned int num_glyphs = 0;
621   char *utf8 = nullptr;
622   unsigned int utf8_len = 0;
623   cairo_text_cluster_t *clusters = nullptr;
624   unsigned int num_clusters = 0;
625   cairo_text_cluster_flags_t cluster_flags = (cairo_text_cluster_flags_t) 0;
626 
helper_cairo_line_thelper_cairo_line_t627   helper_cairo_line_t (const char          *utf8_,
628 		       unsigned             utf8_len_,
629 		       hb_buffer_t         *buffer,
630 		       hb_bool_t            utf8_clusters,
631 		       unsigned             subpixel_bits) :
632     utf8 (utf8_ ? g_strndup (utf8_, utf8_len_) : nullptr),
633     utf8_len (utf8_len_)
634   {
635     hb_cairo_glyphs_from_buffer (buffer,
636 				 utf8_clusters,
637 				 1 << subpixel_bits, 1 << subpixel_bits,
638 				 0., 0.,
639 				 utf8, utf8_len,
640 				 &glyphs, &num_glyphs,
641 				 &clusters, &num_clusters,
642 				 &cluster_flags);
643   }
644 
finishhelper_cairo_line_t645   void finish ()
646   {
647     if (glyphs)
648       cairo_glyph_free (glyphs);
649     if (clusters)
650       cairo_text_cluster_free (clusters);
651     g_free (utf8);
652   }
653 
get_advancehelper_cairo_line_t654   void get_advance (double *x_advance, double *y_advance)
655   {
656     *x_advance = glyphs[num_glyphs].x;
657     *y_advance = glyphs[num_glyphs].y;
658   }
659 };
660 
661 #endif
662