• 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 
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