1 /*
2 * Copyright (C) 2003,2008 Red Hat, Inc.
3 * Copyright © 2019, 2020 Christian Persch
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published
7 * by the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19 #include "config.h"
20
21 #include <cmath>
22
23 #include "bidi.hh"
24 #include "debug.h"
25 #include "drawing-cairo.hh"
26 #include "fonts-pangocairo.hh"
27
28 /* cairo_show_glyphs accepts runs up to 102 glyphs before it allocates a
29 * temporary array.
30 *
31 * Setting this to a large value can cause dramatic slow-downs for some
32 * xservers (notably fglrx), see bug #410534.
33 */
34 #define MAX_RUN_LENGTH 100
35
36 #define VTE_DRAW_NORMAL 0
37 #define VTE_DRAW_BOLD 1
38 #define VTE_DRAW_ITALIC 2
39 #define VTE_DRAW_BOLD_ITALIC 3
40
41 static unsigned
attr_to_style(uint32_t attr)42 attr_to_style(uint32_t attr)
43 {
44 auto style = unsigned{0};
45 if (attr & VTE_ATTR_BOLD)
46 style |= VTE_DRAW_BOLD;
47 if (attr & VTE_ATTR_ITALIC)
48 style |= VTE_DRAW_ITALIC;
49 return style;
50 }
51
52 static inline constexpr double
_vte_draw_get_undercurl_rad(gint width)53 _vte_draw_get_undercurl_rad(gint width)
54 {
55 return width / 2. / M_SQRT2;
56 }
57
58 static inline constexpr double
_vte_draw_get_undercurl_arc_height(gint width)59 _vte_draw_get_undercurl_arc_height(gint width)
60 {
61 return _vte_draw_get_undercurl_rad(width) * (1. - M_SQRT2 / 2.);
62 }
63
64 double
_vte_draw_get_undercurl_height(gint width,double line_width)65 _vte_draw_get_undercurl_height(gint width, double line_width)
66 {
67 return 2. * _vte_draw_get_undercurl_arc_height(width) + line_width;
68 }
69
70 namespace vte {
71 namespace view {
72
~DrawingContext()73 DrawingContext::~DrawingContext()
74 {
75 clear_font_cache();
76 }
77
78 void
clear_font_cache()79 DrawingContext::clear_font_cache()
80 {
81 // m_fonts = {};
82
83 for (auto style = int{0}; style < 4; ++style) {
84 if (m_fonts[style] != nullptr)
85 m_fonts[style]->unref();
86 m_fonts[style] = nullptr;
87 }
88 }
89
90 void
set_cairo(cairo_t * cr)91 DrawingContext::set_cairo(cairo_t* cr) noexcept
92 {
93 m_cr = cr;
94 }
95
96 void
clip(cairo_rectangle_int_t const * rect)97 DrawingContext::clip(cairo_rectangle_int_t const* rect)
98 {
99 cairo_save(m_cr);
100 cairo_rectangle(m_cr,
101 rect->x, rect->y, rect->width, rect->height);
102 cairo_clip(m_cr);
103 }
104
105 void
unclip()106 DrawingContext::unclip()
107 {
108 cairo_restore(m_cr);
109 }
110
111 void
set_source_color_alpha(vte::color::rgb const * color,double alpha)112 DrawingContext::set_source_color_alpha(vte::color::rgb const* color,
113 double alpha)
114 {
115 g_assert(m_cr);
116 cairo_set_source_rgba(m_cr,
117 color->red / 65535.,
118 color->green / 65535.,
119 color->blue / 65535.,
120 alpha);
121 }
122
123 void
clear(int x,int y,int width,int height,vte::color::rgb const * color,double alpha)124 DrawingContext::clear(int x,
125 int y,
126 int width,
127 int height,
128 vte::color::rgb const* color,
129 double alpha)
130 {
131 g_assert(m_cr);
132 cairo_rectangle(m_cr, x, y, width, height);
133 cairo_set_operator(m_cr, CAIRO_OPERATOR_SOURCE);
134 set_source_color_alpha(color, alpha);
135 cairo_fill(m_cr);
136 }
137
138 void
set_text_font(GtkWidget * widget,PangoFontDescription const * fontdesc,double cell_width_scale,double cell_height_scale)139 DrawingContext::set_text_font(GtkWidget* widget,
140 PangoFontDescription const* fontdesc,
141 double cell_width_scale,
142 double cell_height_scale)
143 {
144 PangoFontDescription *bolddesc = nullptr;
145 PangoFontDescription *italicdesc = nullptr;
146 PangoFontDescription *bolditalicdesc = nullptr;
147 gint normal, bold, ratio;
148
149 _vte_debug_print (VTE_DEBUG_DRAW, "draw_set_text_font\n");
150
151 clear_font_cache();
152
153 /* calculate bold font desc */
154 bolddesc = pango_font_description_copy (fontdesc);
155 pango_font_description_set_weight (bolddesc, PANGO_WEIGHT_BOLD);
156
157 /* calculate italic font desc */
158 italicdesc = pango_font_description_copy (fontdesc);
159 pango_font_description_set_style (italicdesc, PANGO_STYLE_ITALIC);
160
161 /* calculate bold italic font desc */
162 bolditalicdesc = pango_font_description_copy (bolddesc);
163 pango_font_description_set_style (bolditalicdesc, PANGO_STYLE_ITALIC);
164
165 m_fonts[VTE_DRAW_NORMAL] = FontInfo::create_for_widget(widget, fontdesc);
166 m_fonts[VTE_DRAW_BOLD] = FontInfo::create_for_widget(widget, bolddesc);
167 m_fonts[VTE_DRAW_ITALIC] = FontInfo::create_for_widget(widget, italicdesc);
168 m_fonts[VTE_DRAW_ITALIC | VTE_DRAW_BOLD] =
169 FontInfo::create_for_widget(widget, bolditalicdesc);
170 pango_font_description_free (bolddesc);
171 pango_font_description_free (italicdesc);
172 pango_font_description_free (bolditalicdesc);
173
174 /* Decide if we should keep this bold font face, per bug 54926:
175 * - reject bold font if it is not within 10% of normal font width
176 */
177 normal = VTE_DRAW_NORMAL;
178 bold = normal | VTE_DRAW_BOLD;
179 ratio = m_fonts[bold]->width() * 100 / m_fonts[normal]->width();
180 if (abs(ratio - 100) > 10) {
181 _vte_debug_print (VTE_DEBUG_DRAW,
182 "Rejecting bold font (%i%%).\n", ratio);
183 m_fonts[bold]->unref();
184 m_fonts[bold] = m_fonts[normal]->ref();
185 }
186 normal = VTE_DRAW_ITALIC;
187 bold = normal | VTE_DRAW_BOLD;
188 ratio = m_fonts[bold]->width() * 100 / m_fonts[normal]->width();
189 if (abs(ratio - 100) > 10) {
190 _vte_debug_print (VTE_DEBUG_DRAW,
191 "Rejecting italic bold font (%i%%).\n", ratio);
192 m_fonts[bold]->unref();
193 m_fonts[bold] = m_fonts[normal]->ref();
194 }
195
196 /* Apply letter spacing and line spacing. */
197 m_cell_width = m_fonts[VTE_DRAW_NORMAL]->width() * cell_width_scale;
198 m_char_spacing.left = (m_cell_width - m_fonts[VTE_DRAW_NORMAL]->width()) / 2;
199 m_char_spacing.right = (m_cell_width - m_fonts[VTE_DRAW_NORMAL]->width() + 1) / 2;
200 m_cell_height = m_fonts[VTE_DRAW_NORMAL]->height() * cell_height_scale;
201 m_char_spacing.top = (m_cell_height - m_fonts[VTE_DRAW_NORMAL]->height() + 1) / 2;
202 m_char_spacing.bottom = (m_cell_height - m_fonts[VTE_DRAW_NORMAL]->height()) / 2;
203
204 m_undercurl_surface.reset();
205 }
206
207 void
get_text_metrics(int * cell_width,int * cell_height,int * char_ascent,int * char_descent,GtkBorder * char_spacing)208 DrawingContext::get_text_metrics(int* cell_width,
209 int* cell_height,
210 int* char_ascent,
211 int* char_descent,
212 GtkBorder* char_spacing)
213 {
214 g_return_if_fail (m_fonts[VTE_DRAW_NORMAL] != nullptr);
215
216 if (cell_width)
217 *cell_width = m_cell_width;
218 if (cell_height)
219 *cell_height = m_cell_height;
220 if (char_ascent)
221 *char_ascent = m_fonts[VTE_DRAW_NORMAL]->ascent();
222 if (char_descent)
223 *char_descent = m_fonts[VTE_DRAW_NORMAL]->height() - m_fonts[VTE_DRAW_NORMAL]->ascent();
224 if (char_spacing)
225 *char_spacing = m_char_spacing;
226 }
227
228 /* Stores the left and right edges of the given glyph, relative to the cell's left edge. */
229 void
get_char_edges(vteunistr c,int columns,uint32_t attr,int & left,int & right)230 DrawingContext::get_char_edges(vteunistr c,
231 int columns,
232 uint32_t attr,
233 int& left,
234 int& right)
235 {
236 if (G_UNLIKELY(m_minifont.unistr_is_local_graphic (c))) {
237 left = 0;
238 right = m_cell_width * columns;
239 return;
240 }
241
242 int l, w, normal_width, fits_width;
243
244 if (G_UNLIKELY (m_fonts[VTE_DRAW_NORMAL] == nullptr)) {
245 left = 0;
246 right = 0;
247 return;
248 }
249
250 w = m_fonts[attr_to_style(attr)]->get_unistr_info(c)->width;
251 normal_width = m_fonts[VTE_DRAW_NORMAL]->width() * columns;
252 fits_width = m_cell_width * columns;
253
254 if (G_LIKELY (w <= normal_width)) {
255 /* The regular case: The glyph is not wider than one (CJK: two) regular character(s).
256 * Align to the left, after applying half (CJK: one) letter spacing. */
257 l = m_char_spacing.left + (columns == 2 ? m_char_spacing.right : 0);
258 } else if (G_UNLIKELY (w <= fits_width)) {
259 /* Slightly wider glyph, but still fits in the cell (spacing included). This case can
260 * only happen with nonzero letter spacing. Center the glyph in the cell(s). */
261 l = (fits_width - w) / 2;
262 } else {
263 /* Even wider glyph: doesn't fit in the cell. Align at left and overflow on the right. */
264 l = 0;
265 }
266
267 left = l;
268 right = l + w;
269 }
270
271 void
draw_text_internal(TextRequest * requests,gsize n_requests,uint32_t attr,vte::color::rgb const * color,double alpha)272 DrawingContext::draw_text_internal(TextRequest* requests,
273 gsize n_requests,
274 uint32_t attr,
275 vte::color::rgb const* color,
276 double alpha)
277 {
278 gsize i;
279 cairo_scaled_font_t *last_scaled_font = nullptr;
280 int n_cr_glyphs = 0;
281 cairo_glyph_t cr_glyphs[MAX_RUN_LENGTH];
282 auto font = m_fonts[attr_to_style(attr)];
283
284 g_return_if_fail (font != nullptr);
285
286 g_assert(m_cr);
287 set_source_color_alpha(color, alpha);
288 cairo_set_operator(m_cr, CAIRO_OPERATOR_OVER);
289
290 for (i = 0; i < n_requests; i++) {
291 vteunistr c = requests[i].c;
292
293 if (G_UNLIKELY (requests[i].mirror)) {
294 vte_bidi_get_mirror_char (c, requests[i].box_mirror, &c);
295 }
296
297 if (m_minifont.unistr_is_local_graphic(c)) {
298 m_minifont.draw_graphic(*this,
299 c,
300 attr,
301 color,
302 requests[i].x, requests[i].y,
303 font->width(), requests[i].columns, font->height());
304 continue;
305 }
306
307 auto uinfo = font->get_unistr_info(c);
308 auto ufi = &uinfo->m_ufi;
309 int x, y, ye;
310
311 get_char_edges(c, requests[i].columns, attr, x, ye /* unused */);
312 x += requests[i].x;
313 /* Bold/italic versions might have different ascents. In order to align their
314 * baselines, we offset by the normal font's ascent here. (Bug 137.) */
315 y = requests[i].y + m_char_spacing.top + m_fonts[VTE_DRAW_NORMAL]->ascent();
316
317 switch (uinfo->coverage()) {
318 default:
319 case FontInfo::UnistrInfo::Coverage::UNKNOWN:
320 g_assert_not_reached ();
321 break;
322 case FontInfo::UnistrInfo::Coverage::USE_PANGO_LAYOUT_LINE:
323 cairo_move_to(m_cr, x, y);
324 pango_cairo_show_layout_line(m_cr,
325 ufi->using_pango_layout_line.line);
326 break;
327 case FontInfo::UnistrInfo::Coverage::USE_PANGO_GLYPH_STRING:
328 cairo_move_to(m_cr, x, y);
329 pango_cairo_show_glyph_string(m_cr,
330 ufi->using_pango_glyph_string.font,
331 ufi->using_pango_glyph_string.glyph_string);
332 break;
333 case FontInfo::UnistrInfo::Coverage::USE_CAIRO_GLYPH:
334 if (last_scaled_font != ufi->using_cairo_glyph.scaled_font || n_cr_glyphs == MAX_RUN_LENGTH) {
335 if (n_cr_glyphs) {
336 cairo_set_scaled_font(m_cr, last_scaled_font);
337 cairo_show_glyphs(m_cr,
338 cr_glyphs,
339 n_cr_glyphs);
340 n_cr_glyphs = 0;
341 }
342 last_scaled_font = ufi->using_cairo_glyph.scaled_font;
343 }
344 cr_glyphs[n_cr_glyphs].index = ufi->using_cairo_glyph.glyph_index;
345 cr_glyphs[n_cr_glyphs].x = x;
346 cr_glyphs[n_cr_glyphs].y = y;
347 n_cr_glyphs++;
348 break;
349 }
350 }
351 if (n_cr_glyphs) {
352 cairo_set_scaled_font(m_cr, last_scaled_font);
353 cairo_show_glyphs(m_cr,
354 cr_glyphs,
355 n_cr_glyphs);
356 n_cr_glyphs = 0;
357 }
358 }
359
360 void
draw_text(TextRequest * requests,gsize n_requests,uint32_t attr,vte::color::rgb const * color,double alpha)361 DrawingContext::draw_text(TextRequest* requests,
362 gsize n_requests,
363 uint32_t attr,
364 vte::color::rgb const* color,
365 double alpha)
366 {
367 g_assert(m_cr);
368
369 if (_vte_debug_on (VTE_DEBUG_DRAW)) {
370 GString *string = g_string_new ("");
371 gchar *str;
372 gsize n;
373 for (n = 0; n < n_requests; n++) {
374 g_string_append_unichar (string, requests[n].c);
375 }
376 str = g_string_free (string, FALSE);
377 g_printerr ("draw_text (\"%s\", len=%" G_GSIZE_FORMAT ", color=(%d,%d,%d,%.3f), %s - %s)\n",
378 str, n_requests, color->red, color->green, color->blue, alpha,
379 (attr & VTE_ATTR_BOLD) ? "bold" : "normal",
380 (attr & VTE_ATTR_ITALIC) ? "italic" : "regular");
381 g_free (str);
382 }
383
384 draw_text_internal(requests, n_requests, attr, color, alpha);
385 }
386
387 /* The following two functions are unused since commit 154abade902850afb44115cccf8fcac51fc082f0,
388 * but let's keep them for now since they may become used again.
389 */
390 bool
has_char(vteunistr c,uint32_t attr)391 DrawingContext::has_char(vteunistr c,
392 uint32_t attr)
393 {
394 _vte_debug_print (VTE_DEBUG_DRAW, "draw_has_char ('0x%04X', %s - %s)\n", c,
395 (attr & VTE_ATTR_BOLD) ? "bold" : "normal",
396 (attr & VTE_ATTR_ITALIC) ? "italic" : "regular");
397
398 auto const style = attr_to_style(attr);
399 g_return_val_if_fail(m_fonts[style], false);
400
401 auto uinfo = m_fonts[style]->get_unistr_info(c);
402 return !uinfo->has_unknown_chars;
403 }
404
405 bool
draw_char(TextRequest * request,uint32_t attr,vte::color::rgb const * color,double alpha)406 DrawingContext::draw_char(TextRequest* request,
407 uint32_t attr,
408 vte::color::rgb const* color,
409 double alpha)
410 {
411 _vte_debug_print (VTE_DEBUG_DRAW,
412 "draw_char ('%c', color=(%d,%d,%d,%.3f), %s, %s)\n",
413 request->c,
414 color->red, color->green, color->blue,
415 alpha,
416 (attr & VTE_ATTR_BOLD) ? "bold" : "normal",
417 (attr & VTE_ATTR_ITALIC) ? "italic" : "regular");
418
419 auto const have_char = has_char(request->c, attr);
420 if (have_char)
421 draw_text(request, 1, attr, color, alpha);
422
423 return have_char;
424 }
425
426 void
draw_rectangle(int x,int y,int width,int height,vte::color::rgb const * color,double alpha)427 DrawingContext::draw_rectangle(int x,
428 int y,
429 int width,
430 int height,
431 vte::color::rgb const* color,
432 double alpha)
433 {
434 g_assert(m_cr);
435
436 _vte_debug_print (VTE_DEBUG_DRAW,
437 "draw_rectangle (%d, %d, %d, %d, color=(%d,%d,%d,%.3f))\n",
438 x,y,width,height,
439 color->red, color->green, color->blue,
440 alpha);
441
442 cairo_set_operator(m_cr, CAIRO_OPERATOR_OVER);
443 cairo_rectangle(m_cr, x+VTE_LINE_WIDTH/2., y+VTE_LINE_WIDTH/2., width-VTE_LINE_WIDTH, height-VTE_LINE_WIDTH);
444 set_source_color_alpha(color, alpha);
445 cairo_set_line_width(m_cr, VTE_LINE_WIDTH);
446 cairo_stroke (m_cr);
447 }
448
449 void
fill_rectangle(int x,int y,int width,int height,vte::color::rgb const * color,double alpha)450 DrawingContext::fill_rectangle(int x,
451 int y,
452 int width,
453 int height,
454 vte::color::rgb const* color,
455 double alpha)
456 {
457 g_assert(m_cr);
458
459 _vte_debug_print (VTE_DEBUG_DRAW,
460 "draw_fill_rectangle (%d, %d, %d, %d, color=(%d,%d,%d,%.3f))\n",
461 x,y,width,height,
462 color->red, color->green, color->blue,
463 alpha);
464
465 cairo_set_operator(m_cr, CAIRO_OPERATOR_OVER);
466 cairo_rectangle(m_cr, x, y, width, height);
467 set_source_color_alpha(color, alpha);
468 cairo_fill (m_cr);
469 }
470
471 void
draw_line(int x,int y,int xp,int yp,int line_width,vte::color::rgb const * color,double alpha)472 DrawingContext::draw_line(int x,
473 int y,
474 int xp,
475 int yp,
476 int line_width,
477 vte::color::rgb const *color,
478 double alpha)
479 {
480 fill_rectangle(
481 x, y,
482 MAX(line_width, xp - x + 1), MAX(line_width, yp - y + 1),
483 color, alpha);
484 }
485
486 void
draw_undercurl(int x,double y,double line_width,int count,vte::color::rgb const * color,double alpha)487 DrawingContext::draw_undercurl(int x,
488 double y,
489 double line_width,
490 int count,
491 vte::color::rgb const *color,
492 double alpha)
493 {
494 /* The end of the curly line slightly overflows to the next cell, so the canvas
495 * caching the rendered look has to be wider not to chop this off. */
496 gint x_padding = line_width + 1; /* ceil, kind of */
497
498 gint surface_top = y; /* floor */
499
500 g_assert(m_cr);
501
502 _vte_debug_print (VTE_DEBUG_DRAW,
503 "draw_undercurl (x=%d, y=%f, count=%d, color=(%d,%d,%d,%.3f))\n",
504 x, y, count,
505 color->red, color->green, color->blue,
506 alpha);
507
508 if (G_UNLIKELY (!m_undercurl_surface)) {
509 /* Cache the undercurl's look. The design assumes that until the cached look is
510 * invalidated (the font is changed), this method is always called with the "y"
511 * parameter having the same fractional part, and the same "line_width" parameter.
512 * For caching, only the fractional part of "y" is used.
513 */
514 double rad = _vte_draw_get_undercurl_rad(m_cell_width);
515 double y_bottom = y + _vte_draw_get_undercurl_height(m_cell_width, line_width);
516 double y_center = (y + y_bottom) / 2.;
517 gint surface_bottom = y_bottom + 1; /* ceil, kind of */
518
519 _vte_debug_print (VTE_DEBUG_DRAW,
520 "caching undercurl shape\n");
521
522 /* Add a line_width of margin horizontally on both sides, for nice antialias overflowing. */
523 m_undercurl_surface = vte::take_freeable
524 (cairo_surface_create_similar(cairo_get_target(m_cr),
525 CAIRO_CONTENT_ALPHA,
526 m_cell_width + 2 * x_padding,
527 surface_bottom - surface_top));
528 auto undercurl_cr = vte::take_freeable(cairo_create(m_undercurl_surface.get()));
529 cairo_set_operator(undercurl_cr.get(), CAIRO_OPERATOR_OVER);
530 /* First quarter circle, similar to the left half of the tilde symbol. */
531 cairo_arc(undercurl_cr.get(), x_padding + m_cell_width / 4., y_center - surface_top + m_cell_width / 4., rad, M_PI * 5 / 4, M_PI * 7 / 4);
532 /* Second quarter circle, similar to the right half of the tilde symbol. */
533 cairo_arc_negative(undercurl_cr.get(), x_padding + m_cell_width * 3 / 4., y_center - surface_top - m_cell_width / 4., rad, M_PI * 3 / 4, M_PI / 4);
534 cairo_set_line_width (undercurl_cr.get(), line_width);
535 cairo_stroke(undercurl_cr.get());
536 }
537
538 /* Paint the cached look of the undercurl using the desired look.
539 * The cached look takes the fractional part of "y" into account,
540 * here we only offset by its integer part. */
541 cairo_save (m_cr);
542 cairo_set_operator(m_cr, CAIRO_OPERATOR_OVER);
543 set_source_color_alpha(color, alpha);
544 for (int i = 0; i < count; i++) {
545 cairo_mask_surface(m_cr, m_undercurl_surface.get(), x - x_padding + i * m_cell_width, surface_top);
546 }
547 cairo_restore (m_cr);
548 }
549
550 } // namespace view
551 } // namespace vte
552