helper-cairo.hh 18 KB

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