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