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