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