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