Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 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 | |
Behdad Esfahbod | 17f40b7 | 2017-10-27 09:22:30 -0600 | [diff] [blame] | 27 | #ifndef HELPER_CAIRO_HH |
| 28 | #define HELPER_CAIRO_HH |
| 29 | |
Behdad Esfahbod | 93bc62e | 2021-08-07 13:13:58 -0600 | [diff] [blame] | 30 | #include "view-options.hh" |
| 31 | #include "output-options.hh" |
Khaled Hosny | 6add69a | 2022-12-16 19:54:00 +0200 | [diff] [blame] | 32 | #ifdef HAVE_CAIRO_FT |
| 33 | # include "helper-cairo-ft.hh" |
| 34 | #endif |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 35 | |
Behdad Esfahbod | 5d2df12 | 2022-02-03 17:18:54 -0600 | [diff] [blame] | 36 | #include <cairo.h> |
| 37 | #include <hb.h> |
Matthias Clasen | 0d6ee46 | 2022-12-25 10:50:56 -0500 | [diff] [blame] | 38 | #include <hb-cairo.h> |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 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 * |
| 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 |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 70 | |
Behdad Esfahbod | 5d2df12 | 2022-02-03 17:18:54 -0600 | [diff] [blame] | 71 | static inline bool |
| 72 | helper_cairo_use_hb_draw (const font_options_t *font_opts) |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 73 | { |
Behdad Esfahbod | 6a3dec3 | 2022-02-08 17:39:16 -0600 | [diff] [blame] | 74 | const char *env = getenv ("HB_DRAW"); |
Behdad Esfahbod | 9cc9ffe | 2022-02-08 18:18:47 -0600 | [diff] [blame] | 75 | if (!env) |
Behdad Esfahbod | 0e4f579 | 2022-10-31 13:51:24 -0600 | [diff] [blame] | 76 | /* Older cairo had a bug in rendering COLRv0 fonts in |
Behdad Esfahbod | c54a702 | 2023-01-21 14:07:41 -0700 | [diff] [blame] | 77 | * right-to-left direction as well as clipping issue |
| 78 | * with user-fonts. |
| 79 | * |
| 80 | * https://github.com/harfbuzz/harfbuzz/issues/4051 */ |
Behdad Esfahbod | 9cc9ffe | 2022-02-08 18:18:47 -0600 | [diff] [blame] | 81 | return cairo_version () >= CAIRO_VERSION_ENCODE (1, 17, 5); |
Behdad Esfahbod | 0e4f579 | 2022-10-31 13:51:24 -0600 | [diff] [blame] | 82 | |
Behdad Esfahbod | 9cc9ffe | 2022-02-08 18:18:47 -0600 | [diff] [blame] | 83 | return atoi (env); |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 84 | } |
Behdad Esfahbod | 6c0ebd0 | 2015-11-05 11:37:48 -0800 | [diff] [blame] | 85 | |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 86 | static inline cairo_scaled_font_t * |
Behdad Esfahbod | e998cec | 2023-01-18 23:33:21 -0700 | [diff] [blame] | 87 | helper_cairo_create_scaled_font (const font_options_t *font_opts, |
| 88 | const view_options_t *view_opts) |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 89 | { |
Matthias Clasen | 0d6ee46 | 2022-12-25 10:50:56 -0500 | [diff] [blame] | 90 | hb_font_t *font = font_opts->font; |
| 91 | bool use_hb_draw = true; |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 92 | |
Khaled Hosny | 6add69a | 2022-12-16 19:54:00 +0200 | [diff] [blame] | 93 | #ifdef HAVE_CAIRO_FT |
Matthias Clasen | 0d6ee46 | 2022-12-25 10:50:56 -0500 | [diff] [blame] | 94 | use_hb_draw = helper_cairo_use_hb_draw (font_opts); |
Khaled Hosny | 6add69a | 2022-12-16 19:54:00 +0200 | [diff] [blame] | 95 | #endif |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 96 | |
Matthias Clasen | 0d6ee46 | 2022-12-25 10:50:56 -0500 | [diff] [blame] | 97 | |
Khaled Hosny | 00060d9 | 2022-12-30 22:55:56 +0200 | [diff] [blame] | 98 | cairo_font_face_t *cairo_face = nullptr; |
Behdad Esfahbod | 9f7538c | 2022-12-25 13:46:37 -0700 | [diff] [blame] | 99 | if (use_hb_draw) |
Behdad Esfahbod | 5efb3bc | 2022-12-27 17:47:46 -0700 | [diff] [blame] | 100 | { |
Behdad Esfahbod | cf001f6 | 2022-12-25 19:01:28 -0700 | [diff] [blame] | 101 | cairo_face = hb_cairo_font_face_create_for_font (font); |
Behdad Esfahbod | 5efb3bc | 2022-12-27 17:47:46 -0700 | [diff] [blame] | 102 | hb_cairo_font_face_set_scale_factor (cairo_face, 1 << font_opts->subpixel_bits); |
| 103 | } |
Behdad Esfahbod | 9f7538c | 2022-12-25 13:46:37 -0700 | [diff] [blame] | 104 | #ifdef HAVE_CAIRO_FT |
| 105 | else |
| 106 | cairo_face = helper_cairo_create_ft_font_face (font_opts); |
| 107 | #endif |
Matthias Clasen | 0d6ee46 | 2022-12-25 10:50:56 -0500 | [diff] [blame] | 108 | |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 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); |
Behdad Esfahbod | ea993af | 2022-12-25 19:17:18 -0700 | [diff] [blame] | 116 | if (!use_hb_draw) |
| 117 | font_matrix.xy = -font_opts->slant * font_opts->font_size_x; |
Behdad Esfahbod | 5c55858 | 2022-02-07 18:54:16 -0600 | [diff] [blame] | 118 | |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 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); |
Behdad Esfahbod | 5847ec2 | 2023-01-18 22:37:54 -0700 | [diff] [blame] | 122 | #ifdef CAIRO_COLOR_PALETTE_DEFAULT |
Behdad Esfahbod | 638e0ed | 2023-01-20 13:01:22 -0700 | [diff] [blame] | 123 | cairo_font_options_set_color_palette (font_options, view_opts->palette); |
| 124 | #endif |
Behdad Esfahbod | f21b15d | 2023-01-20 13:38:22 -0700 | [diff] [blame] | 125 | #ifdef HAVE_CAIRO_FONT_OPTIONS_GET_CUSTOM_PALETTE_COLOR |
Behdad Esfahbod | e998cec | 2023-01-18 23:33:21 -0700 | [diff] [blame] | 126 | if (view_opts->custom_palette) |
Matthias Clasen | c41892a | 2023-01-18 23:45:53 -0500 | [diff] [blame] | 127 | { |
Behdad Esfahbod | e998cec | 2023-01-18 23:33:21 -0700 | [diff] [blame] | 128 | char **entries = g_strsplit (view_opts->custom_palette, ",", -1); |
Behdad Esfahbod | 61719a8 | 2023-01-20 15:52:09 -0700 | [diff] [blame] | 129 | unsigned idx = 0; |
Behdad Esfahbod | 03e2e58 | 2023-01-20 11:24:35 -0700 | [diff] [blame] | 130 | for (unsigned i = 0; entries[i]; i++) |
Matthias Clasen | c41892a | 2023-01-18 23:45:53 -0500 | [diff] [blame] | 131 | { |
Behdad Esfahbod | 61719a8 | 2023-01-20 15:52:09 -0700 | [diff] [blame] | 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 | |
Behdad Esfahbod | 03e2e58 | 2023-01-20 11:24:35 -0700 | [diff] [blame] | 141 | unsigned fr, fg, fb, fa; |
| 142 | fr = fg = fb = fa = 0; |
Behdad Esfahbod | 61719a8 | 2023-01-20 15:52:09 -0700 | [diff] [blame] | 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++; |
Matthias Clasen | c41892a | 2023-01-18 23:45:53 -0500 | [diff] [blame] | 147 | } |
Behdad Esfahbod | 52b78d5 | 2023-01-18 23:06:08 -0700 | [diff] [blame] | 148 | g_strfreev (entries); |
Matthias Clasen | c41892a | 2023-01-18 23:45:53 -0500 | [diff] [blame] | 149 | } |
| 150 | #endif |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 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 | |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 160 | return scaled_font; |
| 161 | } |
| 162 | |
| 163 | static inline bool |
| 164 | helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font) |
| 165 | { |
Behdad Esfahbod | 5c3da76 | 2022-12-25 15:04:13 -0700 | [diff] [blame] | 166 | hb_font_t *font = hb_cairo_font_face_get_font (cairo_scaled_font_get_font_face (scaled_font)); |
Behdad Esfahbod | 9f7538c | 2022-12-25 13:46:37 -0700 | [diff] [blame] | 167 | |
Khaled Hosny | 6add69a | 2022-12-16 19:54:00 +0200 | [diff] [blame] | 168 | #ifdef HAVE_CAIRO_FT |
Behdad Esfahbod | 5c3da76 | 2022-12-25 15:04:13 -0700 | [diff] [blame] | 169 | if (!font) |
Behdad Esfahbod | 5d2df12 | 2022-02-03 17:18:54 -0600 | [diff] [blame] | 170 | return helper_cairo_ft_scaled_font_has_color (scaled_font); |
Khaled Hosny | 6add69a | 2022-12-16 19:54:00 +0200 | [diff] [blame] | 171 | #endif |
Matthias Clasen | 0d6ee46 | 2022-12-25 10:50:56 -0500 | [diff] [blame] | 172 | |
Behdad Esfahbod | 5c3da76 | 2022-12-25 15:04:13 -0700 | [diff] [blame] | 173 | hb_face_t *face = hb_font_get_face (font); |
| 174 | |
Matthias Clasen | 0d6ee46 | 2022-12-25 10:50:56 -0500 | [diff] [blame] | 175 | return hb_ot_color_has_png (face) || |
| 176 | hb_ot_color_has_layers (face) || |
| 177 | hb_ot_color_has_paint (face); |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 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 |
| 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 * |
| 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 |
| 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 |
| 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 * |
| 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 |
| 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 | |
Behdad Esfahbod | c98773e | 2021-08-11 20:22:03 -0600 | [diff] [blame] | 428 | template <typename view_options_t, |
Behdad Esfahbod | c0b2f50 | 2022-02-18 12:29:14 -0600 | [diff] [blame] | 429 | typename output_options_type> |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 430 | static inline cairo_t * |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 431 | helper_cairo_create_context (double w, double h, |
| 432 | view_options_t *view_opts, |
Behdad Esfahbod | c0b2f50 | 2022-02-18 12:29:14 -0600 | [diff] [blame] | 433 | output_options_type *out_opts, |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 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; |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 446 | |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 447 | image_protocol_t protocol = image_protocol_t::NONE; |
| 448 | const char *extension = out_opts->output_format; |
| 449 | if (!extension) { |
| 450 | #if HAVE_ISATTY |
Behdad Esfahbod | 58bfe40 | 2021-08-11 19:48:28 -0600 | [diff] [blame] | 451 | if (isatty (fileno (out_opts->out_fp))) |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 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 | } |
Wez Furlong | e3548c2 | 2022-09-03 08:15:03 -0700 | [diff] [blame] | 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 | } |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 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; |
Behdad Esfahbod | dc4af47 | 2023-01-20 11:11:02 -0700 | [diff] [blame] | 518 | br = bg = bb = ba = 255; |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 519 | color = view_opts->back ? view_opts->back : DEFAULT_BACK; |
Behdad Esfahbod | 03e2e58 | 2023-01-20 11:24:35 -0700 | [diff] [blame] | 520 | parse_color (color, br, bg, bb, ba); |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 521 | fr = fg = fb = 0; fa = 255; |
| 522 | color = view_opts->fore ? view_opts->fore : DEFAULT_FORE; |
Behdad Esfahbod | 03e2e58 | 2023-01-20 11:24:35 -0700 | [diff] [blame] | 523 | parse_color (color, fr, fg, fb, fa); |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 524 | |
| 525 | if (content == CAIRO_CONTENT_ALPHA) |
| 526 | { |
Behdad Esfahbod | 91a174f | 2023-01-24 11:27:35 -0700 | [diff] [blame] | 527 | if (view_opts->show_extents || |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 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; |
Behdad Esfahbod | 58bfe40 | 2021-08-11 19:48:28 -0600 | [diff] [blame] | 536 | FILE *f = out_opts->out_fp; |
Behdad Esfahbod | c5337c4 | 2021-08-06 19:19:50 -0600 | [diff] [blame] | 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 |
| 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 | } |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 589 | |
| 590 | |
| 591 | struct helper_cairo_line_t { |
Behdad Esfahbod | 74d29cd | 2022-12-29 18:11:41 -0700 | [diff] [blame] | 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; |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 599 | |
Behdad Esfahbod | 74d29cd | 2022-12-29 18:11:41 -0700 | [diff] [blame] | 600 | helper_cairo_line_t (const char *utf8_, |
| 601 | unsigned utf8_len_, |
| 602 | hb_buffer_t *buffer, |
| 603 | hb_bool_t utf8_clusters, |
Behdad Esfahbod | 228a415 | 2022-12-29 18:19:06 -0700 | [diff] [blame] | 604 | unsigned subpixel_bits) : |
Behdad Esfahbod | 74d29cd | 2022-12-29 18:11:41 -0700 | [diff] [blame] | 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, |
Behdad Esfahbod | 228a415 | 2022-12-29 18:19:06 -0700 | [diff] [blame] | 610 | 1 << subpixel_bits, 1 << subpixel_bits, |
Behdad Esfahbod | 74d29cd | 2022-12-29 18:11:41 -0700 | [diff] [blame] | 611 | 0., 0., |
| 612 | utf8, utf8_len, |
| 613 | &glyphs, &num_glyphs, |
| 614 | &clusters, &num_clusters, |
| 615 | &cluster_flags); |
| 616 | } |
| 617 | |
| 618 | void finish () |
| 619 | { |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 620 | if (glyphs) |
| 621 | cairo_glyph_free (glyphs); |
| 622 | if (clusters) |
| 623 | cairo_text_cluster_free (clusters); |
Behdad Esfahbod | 74d29cd | 2022-12-29 18:11:41 -0700 | [diff] [blame] | 624 | g_free (utf8); |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 625 | } |
| 626 | |
Behdad Esfahbod | 74d29cd | 2022-12-29 18:11:41 -0700 | [diff] [blame] | 627 | void get_advance (double *x_advance, double *y_advance) |
| 628 | { |
Behdad Esfahbod | 69b84a8 | 2012-04-12 15:50:40 -0400 | [diff] [blame] | 629 | *x_advance = glyphs[num_glyphs].x; |
| 630 | *y_advance = glyphs[num_glyphs].y; |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 631 | } |
| 632 | }; |
| 633 | |
Behdad Esfahbod | 8b8b190 | 2011-09-19 16:41:17 -0400 | [diff] [blame] | 634 | #endif |