/* * Copyright (C) 2003,2008 Red Hat, Inc. * Copyright © 2019, 2020 Christian Persch * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . */ #include "config.h" #include #include "bidi.hh" #include "debug.h" #include "drawing-cairo.hh" #include "fonts-pangocairo.hh" /* cairo_show_glyphs accepts runs up to 102 glyphs before it allocates a * temporary array. * * Setting this to a large value can cause dramatic slow-downs for some * xservers (notably fglrx), see bug #410534. */ #define MAX_RUN_LENGTH 100 #define VTE_DRAW_NORMAL 0 #define VTE_DRAW_BOLD 1 #define VTE_DRAW_ITALIC 2 #define VTE_DRAW_BOLD_ITALIC 3 static unsigned attr_to_style(uint32_t attr) { auto style = unsigned{0}; if (attr & VTE_ATTR_BOLD) style |= VTE_DRAW_BOLD; if (attr & VTE_ATTR_ITALIC) style |= VTE_DRAW_ITALIC; return style; } static inline constexpr double _vte_draw_get_undercurl_rad(gint width) { return width / 2. / M_SQRT2; } static inline constexpr double _vte_draw_get_undercurl_arc_height(gint width) { return _vte_draw_get_undercurl_rad(width) * (1. - M_SQRT2 / 2.); } double _vte_draw_get_undercurl_height(gint width, double line_width) { return 2. * _vte_draw_get_undercurl_arc_height(width) + line_width; } namespace vte { namespace view { DrawingContext::~DrawingContext() { clear_font_cache(); } void DrawingContext::clear_font_cache() { // m_fonts = {}; for (auto style = int{0}; style < 4; ++style) { if (m_fonts[style] != nullptr) m_fonts[style]->unref(); m_fonts[style] = nullptr; } } void DrawingContext::set_cairo(cairo_t* cr) noexcept { m_cr = cr; } void DrawingContext::clip(cairo_rectangle_int_t const* rect) { cairo_save(m_cr); cairo_rectangle(m_cr, rect->x, rect->y, rect->width, rect->height); cairo_clip(m_cr); } void DrawingContext::unclip() { cairo_restore(m_cr); } void DrawingContext::set_source_color_alpha(vte::color::rgb const* color, double alpha) { g_assert(m_cr); cairo_set_source_rgba(m_cr, color->red / 65535., color->green / 65535., color->blue / 65535., alpha); } void DrawingContext::clear(int x, int y, int width, int height, vte::color::rgb const* color, double alpha) { g_assert(m_cr); cairo_rectangle(m_cr, x, y, width, height); cairo_set_operator(m_cr, CAIRO_OPERATOR_SOURCE); set_source_color_alpha(color, alpha); cairo_fill(m_cr); } void DrawingContext::set_text_font(GtkWidget* widget, PangoFontDescription const* fontdesc, double cell_width_scale, double cell_height_scale) { PangoFontDescription *bolddesc = nullptr; PangoFontDescription *italicdesc = nullptr; PangoFontDescription *bolditalicdesc = nullptr; gint normal, bold, ratio; _vte_debug_print (VTE_DEBUG_DRAW, "draw_set_text_font\n"); clear_font_cache(); /* calculate bold font desc */ bolddesc = pango_font_description_copy (fontdesc); if (pango_font_description_get_set_fields(bolddesc) & PANGO_FONT_MASK_WEIGHT) { auto const weight = pango_font_description_get_weight(bolddesc); auto const bold_weight = std::min(1000, weight + VTE_FONT_WEIGHT_BOLDENING); pango_font_description_set_weight(bolddesc, PangoWeight(bold_weight)); } else { pango_font_description_set_weight (bolddesc, PANGO_WEIGHT_BOLD); } /* calculate italic font desc */ italicdesc = pango_font_description_copy (fontdesc); pango_font_description_set_style (italicdesc, PANGO_STYLE_ITALIC); /* calculate bold italic font desc */ bolditalicdesc = pango_font_description_copy (bolddesc); pango_font_description_set_style (bolditalicdesc, PANGO_STYLE_ITALIC); m_fonts[VTE_DRAW_NORMAL] = FontInfo::create_for_widget(widget, fontdesc); m_fonts[VTE_DRAW_BOLD] = FontInfo::create_for_widget(widget, bolddesc); m_fonts[VTE_DRAW_ITALIC] = FontInfo::create_for_widget(widget, italicdesc); m_fonts[VTE_DRAW_ITALIC | VTE_DRAW_BOLD] = FontInfo::create_for_widget(widget, bolditalicdesc); pango_font_description_free (bolddesc); pango_font_description_free (italicdesc); pango_font_description_free (bolditalicdesc); /* Decide if we should keep this bold font face, per bug 54926: * - reject bold font if it is not within 10% of normal font width */ normal = VTE_DRAW_NORMAL; bold = normal | VTE_DRAW_BOLD; ratio = m_fonts[bold]->width() * 100 / m_fonts[normal]->width(); if (abs(ratio - 100) > 10) { _vte_debug_print (VTE_DEBUG_DRAW, "Rejecting bold font (%i%%).\n", ratio); m_fonts[bold]->unref(); m_fonts[bold] = m_fonts[normal]->ref(); } normal = VTE_DRAW_ITALIC; bold = normal | VTE_DRAW_BOLD; ratio = m_fonts[bold]->width() * 100 / m_fonts[normal]->width(); if (abs(ratio - 100) > 10) { _vte_debug_print (VTE_DEBUG_DRAW, "Rejecting italic bold font (%i%%).\n", ratio); m_fonts[bold]->unref(); m_fonts[bold] = m_fonts[normal]->ref(); } /* Apply letter spacing and line spacing. */ m_cell_width = m_fonts[VTE_DRAW_NORMAL]->width() * cell_width_scale; m_char_spacing.left = (m_cell_width - m_fonts[VTE_DRAW_NORMAL]->width()) / 2; m_char_spacing.right = (m_cell_width - m_fonts[VTE_DRAW_NORMAL]->width() + 1) / 2; m_cell_height = m_fonts[VTE_DRAW_NORMAL]->height() * cell_height_scale; m_char_spacing.top = (m_cell_height - m_fonts[VTE_DRAW_NORMAL]->height() + 1) / 2; m_char_spacing.bottom = (m_cell_height - m_fonts[VTE_DRAW_NORMAL]->height()) / 2; m_undercurl_surface.reset(); } void DrawingContext::get_text_metrics(int* cell_width, int* cell_height, int* char_ascent, int* char_descent, GtkBorder* char_spacing) { g_return_if_fail (m_fonts[VTE_DRAW_NORMAL] != nullptr); if (cell_width) *cell_width = m_cell_width; if (cell_height) *cell_height = m_cell_height; if (char_ascent) *char_ascent = m_fonts[VTE_DRAW_NORMAL]->ascent(); if (char_descent) *char_descent = m_fonts[VTE_DRAW_NORMAL]->height() - m_fonts[VTE_DRAW_NORMAL]->ascent(); if (char_spacing) *char_spacing = m_char_spacing; } /* Stores the left and right edges of the given glyph, relative to the cell's left edge. */ void DrawingContext::get_char_edges(vteunistr c, int columns, uint32_t attr, int& left, int& right) { if (G_UNLIKELY(m_minifont.unistr_is_local_graphic (c))) { left = 0; right = m_cell_width * columns; return; } int l, w, normal_width, fits_width; if (G_UNLIKELY (m_fonts[VTE_DRAW_NORMAL] == nullptr)) { left = 0; right = 0; return; } w = m_fonts[attr_to_style(attr)]->get_unistr_info(c)->width; normal_width = m_fonts[VTE_DRAW_NORMAL]->width() * columns; fits_width = m_cell_width * columns; if (G_LIKELY (w <= normal_width)) { /* The regular case: The glyph is not wider than one (CJK: two) regular character(s). * Align to the left, after applying half (CJK: one) letter spacing. */ l = m_char_spacing.left + (columns == 2 ? m_char_spacing.right : 0); } else if (G_UNLIKELY (w <= fits_width)) { /* Slightly wider glyph, but still fits in the cell (spacing included). This case can * only happen with nonzero letter spacing. Center the glyph in the cell(s). */ l = (fits_width - w) / 2; } else { /* Even wider glyph: doesn't fit in the cell. Align at left and overflow on the right. */ l = 0; } left = l; right = l + w; } void DrawingContext::draw_text_internal(TextRequest* requests, gsize n_requests, uint32_t attr, vte::color::rgb const* color, double alpha) { gsize i; cairo_scaled_font_t *last_scaled_font = nullptr; int n_cr_glyphs = 0; cairo_glyph_t cr_glyphs[MAX_RUN_LENGTH]; auto font = m_fonts[attr_to_style(attr)]; g_return_if_fail (font != nullptr); g_assert(m_cr); set_source_color_alpha(color, alpha); cairo_set_operator(m_cr, CAIRO_OPERATOR_OVER); for (i = 0; i < n_requests; i++) { vteunistr c = requests[i].c; if (G_UNLIKELY (requests[i].mirror)) { vte_bidi_get_mirror_char (c, requests[i].box_mirror, &c); } if (m_minifont.unistr_is_local_graphic(c)) { m_minifont.draw_graphic(*this, c, attr, color, requests[i].x, requests[i].y, font->width(), requests[i].columns, font->height()); continue; } auto uinfo = font->get_unistr_info(c); auto ufi = &uinfo->m_ufi; int x, y, ye; get_char_edges(c, requests[i].columns, attr, x, ye /* unused */); x += requests[i].x; /* Bold/italic versions might have different ascents. In order to align their * baselines, we offset by the normal font's ascent here. (Bug 137.) */ y = requests[i].y + m_char_spacing.top + m_fonts[VTE_DRAW_NORMAL]->ascent(); switch (uinfo->coverage()) { default: case FontInfo::UnistrInfo::Coverage::UNKNOWN: g_assert_not_reached (); break; case FontInfo::UnistrInfo::Coverage::USE_PANGO_LAYOUT_LINE: cairo_move_to(m_cr, x, y); pango_cairo_show_layout_line(m_cr, ufi->using_pango_layout_line.line); break; case FontInfo::UnistrInfo::Coverage::USE_PANGO_GLYPH_STRING: cairo_move_to(m_cr, x, y); pango_cairo_show_glyph_string(m_cr, ufi->using_pango_glyph_string.font, ufi->using_pango_glyph_string.glyph_string); break; case FontInfo::UnistrInfo::Coverage::USE_CAIRO_GLYPH: if (last_scaled_font != ufi->using_cairo_glyph.scaled_font || n_cr_glyphs == MAX_RUN_LENGTH) { if (n_cr_glyphs) { cairo_set_scaled_font(m_cr, last_scaled_font); cairo_show_glyphs(m_cr, cr_glyphs, n_cr_glyphs); n_cr_glyphs = 0; } last_scaled_font = ufi->using_cairo_glyph.scaled_font; } cr_glyphs[n_cr_glyphs].index = ufi->using_cairo_glyph.glyph_index; cr_glyphs[n_cr_glyphs].x = x; cr_glyphs[n_cr_glyphs].y = y; n_cr_glyphs++; break; } } if (n_cr_glyphs) { cairo_set_scaled_font(m_cr, last_scaled_font); cairo_show_glyphs(m_cr, cr_glyphs, n_cr_glyphs); n_cr_glyphs = 0; } } void DrawingContext::draw_text(TextRequest* requests, gsize n_requests, uint32_t attr, vte::color::rgb const* color, double alpha) { g_assert(m_cr); if (_vte_debug_on (VTE_DEBUG_DRAW)) { GString *string = g_string_new (""); gchar *str; gsize n; for (n = 0; n < n_requests; n++) { g_string_append_unichar (string, requests[n].c); } str = g_string_free (string, FALSE); g_printerr ("draw_text (\"%s\", len=%" G_GSIZE_FORMAT ", color=(%d,%d,%d,%.3f), %s - %s)\n", str, n_requests, color->red, color->green, color->blue, alpha, (attr & VTE_ATTR_BOLD) ? "bold" : "normal", (attr & VTE_ATTR_ITALIC) ? "italic" : "regular"); g_free (str); } draw_text_internal(requests, n_requests, attr, color, alpha); } /* The following two functions are unused since commit 154abade902850afb44115cccf8fcac51fc082f0, * but let's keep them for now since they may become used again. */ bool DrawingContext::has_char(vteunistr c, uint32_t attr) { _vte_debug_print (VTE_DEBUG_DRAW, "draw_has_char ('0x%04X', %s - %s)\n", c, (attr & VTE_ATTR_BOLD) ? "bold" : "normal", (attr & VTE_ATTR_ITALIC) ? "italic" : "regular"); auto const style = attr_to_style(attr); g_return_val_if_fail(m_fonts[style], false); auto uinfo = m_fonts[style]->get_unistr_info(c); return !uinfo->has_unknown_chars; } bool DrawingContext::draw_char(TextRequest* request, uint32_t attr, vte::color::rgb const* color, double alpha) { _vte_debug_print (VTE_DEBUG_DRAW, "draw_char ('%c', color=(%d,%d,%d,%.3f), %s, %s)\n", request->c, color->red, color->green, color->blue, alpha, (attr & VTE_ATTR_BOLD) ? "bold" : "normal", (attr & VTE_ATTR_ITALIC) ? "italic" : "regular"); auto const have_char = has_char(request->c, attr); if (have_char) draw_text(request, 1, attr, color, alpha); return have_char; } void DrawingContext::draw_rectangle(int x, int y, int width, int height, vte::color::rgb const* color, double alpha) { g_assert(m_cr); _vte_debug_print (VTE_DEBUG_DRAW, "draw_rectangle (%d, %d, %d, %d, color=(%d,%d,%d,%.3f))\n", x,y,width,height, color->red, color->green, color->blue, alpha); cairo_set_operator(m_cr, CAIRO_OPERATOR_OVER); cairo_rectangle(m_cr, x+VTE_LINE_WIDTH/2., y+VTE_LINE_WIDTH/2., width-VTE_LINE_WIDTH, height-VTE_LINE_WIDTH); set_source_color_alpha(color, alpha); cairo_set_line_width(m_cr, VTE_LINE_WIDTH); cairo_stroke (m_cr); } void DrawingContext::fill_rectangle(int x, int y, int width, int height, vte::color::rgb const* color, double alpha) { g_assert(m_cr); _vte_debug_print (VTE_DEBUG_DRAW, "draw_fill_rectangle (%d, %d, %d, %d, color=(%d,%d,%d,%.3f))\n", x,y,width,height, color->red, color->green, color->blue, alpha); cairo_set_operator(m_cr, CAIRO_OPERATOR_OVER); cairo_rectangle(m_cr, x, y, width, height); set_source_color_alpha(color, alpha); cairo_fill (m_cr); } void DrawingContext::draw_line(int x, int y, int xp, int yp, int line_width, vte::color::rgb const *color, double alpha) { fill_rectangle( x, y, MAX(line_width, xp - x + 1), MAX(line_width, yp - y + 1), color, alpha); } void DrawingContext::draw_undercurl(int x, double y, double line_width, int count, vte::color::rgb const *color, double alpha) { /* The end of the curly line slightly overflows to the next cell, so the canvas * caching the rendered look has to be wider not to chop this off. */ gint x_padding = line_width + 1; /* ceil, kind of */ gint surface_top = y; /* floor */ g_assert(m_cr); _vte_debug_print (VTE_DEBUG_DRAW, "draw_undercurl (x=%d, y=%f, count=%d, color=(%d,%d,%d,%.3f))\n", x, y, count, color->red, color->green, color->blue, alpha); if (G_UNLIKELY (!m_undercurl_surface)) { /* Cache the undercurl's look. The design assumes that until the cached look is * invalidated (the font is changed), this method is always called with the "y" * parameter having the same fractional part, and the same "line_width" parameter. * For caching, only the fractional part of "y" is used. */ double rad = _vte_draw_get_undercurl_rad(m_cell_width); double y_bottom = y + _vte_draw_get_undercurl_height(m_cell_width, line_width); double y_center = (y + y_bottom) / 2.; gint surface_bottom = y_bottom + 1; /* ceil, kind of */ _vte_debug_print (VTE_DEBUG_DRAW, "caching undercurl shape\n"); /* Add a line_width of margin horizontally on both sides, for nice antialias overflowing. */ m_undercurl_surface = vte::take_freeable (cairo_surface_create_similar(cairo_get_target(m_cr), CAIRO_CONTENT_ALPHA, m_cell_width + 2 * x_padding, surface_bottom - surface_top)); auto undercurl_cr = vte::take_freeable(cairo_create(m_undercurl_surface.get())); cairo_set_operator(undercurl_cr.get(), CAIRO_OPERATOR_OVER); /* First quarter circle, similar to the left half of the tilde symbol. */ 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); /* Second quarter circle, similar to the right half of the tilde symbol. */ 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); cairo_set_line_width (undercurl_cr.get(), line_width); cairo_stroke(undercurl_cr.get()); } /* Paint the cached look of the undercurl using the desired look. * The cached look takes the fractional part of "y" into account, * here we only offset by its integer part. */ cairo_save (m_cr); cairo_set_operator(m_cr, CAIRO_OPERATOR_OVER); set_source_color_alpha(color, alpha); for (int i = 0; i < count; i++) { cairo_mask_surface(m_cr, m_undercurl_surface.get(), x - x_padding + i * m_cell_width, surface_top); } cairo_restore (m_cr); } } // namespace view } // namespace vte