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