forked from AuroraMiddleware/gtk
9b83116fcb
This is the equivalent snapshot function to pango_cairo_show_layout(). Not to be confused with gtk_snapshot_render_layout(), which is the equivalent to gtk_render_layout().
486 lines
15 KiB
C
486 lines
15 KiB
C
/*
|
|
* Copyright (C) 2017 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gsk/gsk.h"
|
|
#include "gskpango.h"
|
|
#include "gtksnapshotprivate.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include <pango/pango.h>
|
|
#include <cairo.h>
|
|
|
|
#define GSK_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_PANGO_RENDERER, GskPangoRendererClass))
|
|
#define GSK_IS_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_PANGO_RENDERER))
|
|
#define GSK_PANGO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_PANGO_RENDERER, GskPangoRendererClass))
|
|
|
|
/*
|
|
* This is a PangoRenderer implementation that translates all the draw calls to
|
|
* gsk render nodes, using the GtkSnapshot helper class. Glyphs are translated
|
|
* to text nodes, all other draw calls fall back to cairo nodes.
|
|
*/
|
|
|
|
struct _GskPangoRenderer
|
|
{
|
|
PangoRenderer parent_instance;
|
|
|
|
GtkSnapshot *snapshot;
|
|
GdkRGBA fg_color;
|
|
graphene_rect_t bounds;
|
|
char *name;
|
|
|
|
/* house-keeping options */
|
|
gboolean is_cached_renderer;
|
|
};
|
|
|
|
struct _GskPangoRendererClass
|
|
{
|
|
PangoRendererClass parent_class;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GskPangoRenderer, gsk_pango_renderer, PANGO_TYPE_RENDERER)
|
|
|
|
static void
|
|
get_color (GskPangoRenderer *crenderer,
|
|
PangoRenderPart part,
|
|
GdkRGBA *rgba)
|
|
{
|
|
PangoColor *color = pango_renderer_get_color ((PangoRenderer *) (crenderer), part);
|
|
guint16 a = pango_renderer_get_alpha ((PangoRenderer *) (crenderer), part);
|
|
gdouble red, green, blue, alpha;
|
|
|
|
red = crenderer->fg_color.red;
|
|
green = crenderer->fg_color.green;
|
|
blue = crenderer->fg_color.blue;
|
|
alpha = crenderer->fg_color.alpha;
|
|
|
|
if (color)
|
|
{
|
|
red = color->red / 65535.;
|
|
green = color->green / 65535.;
|
|
blue = color->blue / 65535.;
|
|
alpha = 1.;
|
|
}
|
|
|
|
if (a)
|
|
alpha = a / 65535.;
|
|
|
|
rgba->red = red;
|
|
rgba->green = green;
|
|
rgba->blue = blue;
|
|
rgba->alpha = alpha;
|
|
}
|
|
|
|
static void
|
|
set_color (GskPangoRenderer *crenderer,
|
|
PangoRenderPart part,
|
|
cairo_t *cr)
|
|
{
|
|
GdkRGBA rgba = { 0, 0, 0, 1 };
|
|
|
|
get_color (crenderer, part, &rgba);
|
|
gdk_cairo_set_source_rgba (cr, &rgba);
|
|
}
|
|
|
|
static void
|
|
gsk_pango_renderer_show_text_glyphs (PangoRenderer *renderer,
|
|
const char *text,
|
|
int text_len,
|
|
PangoGlyphString *glyphs,
|
|
cairo_text_cluster_t *clusters,
|
|
int num_clusters,
|
|
gboolean backward,
|
|
PangoFont *font,
|
|
int x,
|
|
int y)
|
|
{
|
|
GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
|
|
int x_offset, y_offset;
|
|
GskRenderNode *node;
|
|
GdkRGBA color;
|
|
graphene_rect_t node_bounds;
|
|
PangoRectangle ink_rect;
|
|
|
|
pango_glyph_string_extents (glyphs, font, &ink_rect, NULL);
|
|
pango_extents_to_pixels (&ink_rect, NULL);
|
|
|
|
/* Don't create empty nodes */
|
|
if (ink_rect.width == 0 || ink_rect.height == 0)
|
|
return;
|
|
|
|
gtk_snapshot_get_offset (crenderer->snapshot, &x_offset, &y_offset);
|
|
|
|
graphene_rect_init (&node_bounds,
|
|
x_offset + (float)x/PANGO_SCALE,
|
|
y_offset + (float)y/PANGO_SCALE + ink_rect.y,
|
|
ink_rect.x + ink_rect.width,
|
|
ink_rect.height);
|
|
|
|
/* Remove the snapshot offset from the position here again since
|
|
* gtk_snapshot_clips_rect will apply it to the given rect. */
|
|
if (gtk_snapshot_clips_rect (crenderer->snapshot,
|
|
&(cairo_rectangle_int_t){
|
|
node_bounds.origin.x - x_offset,
|
|
node_bounds.origin.y - y_offset,
|
|
ceil (node_bounds.size.width),
|
|
ceil (node_bounds.size.height)
|
|
}))
|
|
return;
|
|
|
|
get_color (crenderer, PANGO_RENDER_PART_FOREGROUND, &color);
|
|
|
|
node = gsk_text_node_new_with_bounds (font,
|
|
glyphs,
|
|
&color,
|
|
x_offset + (double)x/PANGO_SCALE,
|
|
y_offset + (double)y/PANGO_SCALE,
|
|
&node_bounds);
|
|
if (node == NULL)
|
|
return;
|
|
|
|
if (gtk_snapshot_get_record_names (crenderer->snapshot))
|
|
{
|
|
char *s = g_strdup_printf ("%s<%d>", crenderer->name, glyphs->num_glyphs);
|
|
gsk_render_node_set_name (node, s);
|
|
g_free (s);
|
|
}
|
|
|
|
gtk_snapshot_append_node_internal (crenderer->snapshot, node);
|
|
gsk_render_node_unref (node);
|
|
}
|
|
|
|
static void
|
|
gsk_pango_renderer_draw_glyphs (PangoRenderer *renderer,
|
|
PangoFont *font,
|
|
PangoGlyphString *glyphs,
|
|
int x,
|
|
int y)
|
|
{
|
|
gsk_pango_renderer_show_text_glyphs (renderer, NULL, 0, glyphs, NULL, 0, FALSE, font, x, y);
|
|
}
|
|
|
|
static void
|
|
gsk_pango_renderer_draw_glyph_item (PangoRenderer *renderer,
|
|
const char *text,
|
|
PangoGlyphItem *glyph_item,
|
|
int x,
|
|
int y)
|
|
{
|
|
PangoFont *font = glyph_item->item->analysis.font;
|
|
PangoGlyphString *glyphs = glyph_item->glyphs;
|
|
|
|
gsk_pango_renderer_show_text_glyphs (renderer, NULL, 0, glyphs, NULL, 0, FALSE, font, x, y);
|
|
}
|
|
|
|
static void
|
|
gsk_pango_renderer_draw_rectangle (PangoRenderer *renderer,
|
|
PangoRenderPart part,
|
|
int x,
|
|
int y,
|
|
int width,
|
|
int height)
|
|
{
|
|
GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
|
|
GdkRGBA rgba;
|
|
graphene_rect_t bounds;
|
|
|
|
get_color (crenderer, part, &rgba);
|
|
|
|
graphene_rect_init (&bounds,
|
|
(double)x / PANGO_SCALE, (double)y / PANGO_SCALE,
|
|
(double)width / PANGO_SCALE, (double)height / PANGO_SCALE);
|
|
|
|
gtk_snapshot_append_color (crenderer->snapshot, &rgba, &bounds, "DrawRectangle");
|
|
}
|
|
|
|
static void
|
|
gsk_pango_renderer_draw_trapezoid (PangoRenderer *renderer,
|
|
PangoRenderPart part,
|
|
double y1_,
|
|
double x11,
|
|
double x21,
|
|
double y2,
|
|
double x12,
|
|
double x22)
|
|
{
|
|
GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
|
|
cairo_t *cr;
|
|
gdouble x, y;
|
|
|
|
cr = gtk_snapshot_append_cairo (crenderer->snapshot, &crenderer->bounds, "DrawTrapezoid");
|
|
|
|
set_color (crenderer, part, cr);
|
|
|
|
x = y = 0;
|
|
cairo_user_to_device_distance (cr, &x, &y);
|
|
cairo_identity_matrix (cr);
|
|
cairo_translate (cr, x, y);
|
|
|
|
cairo_move_to (cr, x11, y1_);
|
|
cairo_line_to (cr, x21, y1_);
|
|
cairo_line_to (cr, x22, y2);
|
|
cairo_line_to (cr, x12, y2);
|
|
cairo_close_path (cr);
|
|
|
|
cairo_fill (cr);
|
|
|
|
cairo_destroy (cr);
|
|
}
|
|
|
|
/* Draws an error underline that looks like one of:
|
|
* H E H
|
|
* /\ /\ /\ /\ /\ -
|
|
* A/ \ / \ / \ A/ \ / \ |
|
|
* \ \ / \ / /D \ \ / \ |
|
|
* \ \/ C \/ / \ \/ C \ | height = HEIGHT_SQUARES * square
|
|
* \ /\ F / \ F /\ \ |
|
|
* \ / \ / \ / \ \G |
|
|
* \ / \ / \ / \ / |
|
|
* \/ \/ \/ \/ -
|
|
* B B
|
|
* |---|
|
|
* unit_width = (HEIGHT_SQUARES - 1) * square
|
|
*
|
|
* The x, y, width, height passed in give the desired bounding box;
|
|
* x/width are adjusted to make the underline a integer number of units
|
|
* wide.
|
|
*/
|
|
#define HEIGHT_SQUARES 2.5
|
|
|
|
static void
|
|
draw_error_underline (cairo_t *cr,
|
|
double x,
|
|
double y,
|
|
double width,
|
|
double height)
|
|
{
|
|
double square = height / HEIGHT_SQUARES;
|
|
double unit_width = (HEIGHT_SQUARES - 1) * square;
|
|
double double_width = 2 * unit_width;
|
|
int width_units = (width + unit_width / 2) / unit_width;
|
|
double y_top, y_bottom;
|
|
double x_left, x_middle, x_right;
|
|
int i;
|
|
|
|
x += (width - width_units * unit_width) / 2;
|
|
|
|
y_top = y;
|
|
y_bottom = y + height;
|
|
|
|
/* Bottom of squiggle */
|
|
x_middle = x + unit_width;
|
|
x_right = x + double_width;
|
|
cairo_move_to (cr, x - square / 2, y_top + square / 2); /* A */
|
|
for (i = 0; i < width_units-2; i += 2)
|
|
{
|
|
cairo_line_to (cr, x_middle, y_bottom); /* B */
|
|
cairo_line_to (cr, x_right, y_top + square); /* C */
|
|
|
|
x_middle += double_width;
|
|
x_right += double_width;
|
|
}
|
|
cairo_line_to (cr, x_middle, y_bottom); /* B */
|
|
|
|
if (i + 1 == width_units)
|
|
cairo_line_to (cr, x_middle + square / 2, y_bottom - square / 2); /* G */
|
|
else if (i + 2 == width_units) {
|
|
cairo_line_to (cr, x_right + square / 2, y_top + square / 2); /* D */
|
|
cairo_line_to (cr, x_right, y_top); /* E */
|
|
}
|
|
|
|
/* Top of squiggle */
|
|
x_left = x_middle - unit_width;
|
|
for (; i >= 0; i -= 2)
|
|
{
|
|
cairo_line_to (cr, x_middle, y_bottom - square); /* F */
|
|
cairo_line_to (cr, x_left, y_top); /* H */
|
|
|
|
x_left -= double_width;
|
|
x_middle -= double_width;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_pango_renderer_draw_error_underline (PangoRenderer *renderer,
|
|
int x,
|
|
int y,
|
|
int width,
|
|
int height)
|
|
{
|
|
GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
|
|
cairo_t *cr;
|
|
|
|
cr = gtk_snapshot_append_cairo (crenderer->snapshot, &crenderer->bounds, "DrawTrapezoid");
|
|
|
|
set_color (crenderer, PANGO_RENDER_PART_UNDERLINE, cr);
|
|
|
|
cairo_new_path (cr);
|
|
|
|
draw_error_underline (cr,
|
|
(double)x / PANGO_SCALE, (double)y / PANGO_SCALE,
|
|
(double)width / PANGO_SCALE, (double)height / PANGO_SCALE);
|
|
|
|
cairo_fill (cr);
|
|
|
|
cairo_destroy (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_pango_renderer_draw_shape (PangoRenderer *renderer,
|
|
PangoAttrShape *attr,
|
|
int x,
|
|
int y)
|
|
{
|
|
GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
|
|
cairo_t *cr;
|
|
PangoLayout *layout;
|
|
PangoCairoShapeRendererFunc shape_renderer;
|
|
gpointer shape_renderer_data;
|
|
double base_x = (double)x / PANGO_SCALE;
|
|
double base_y = (double)y / PANGO_SCALE;
|
|
|
|
cr = gtk_snapshot_append_cairo (crenderer->snapshot, &crenderer->bounds, "DrawShape");
|
|
|
|
layout = pango_renderer_get_layout (renderer);
|
|
if (!layout)
|
|
return;
|
|
|
|
shape_renderer = pango_cairo_context_get_shape_renderer (pango_layout_get_context (layout),
|
|
&shape_renderer_data);
|
|
|
|
if (!shape_renderer)
|
|
return;
|
|
|
|
set_color (crenderer, PANGO_RENDER_PART_FOREGROUND, cr);
|
|
|
|
cairo_move_to (cr, base_x, base_y);
|
|
|
|
shape_renderer (cr, attr, FALSE, shape_renderer_data);
|
|
|
|
cairo_destroy (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_pango_renderer_init (GskPangoRenderer *renderer G_GNUC_UNUSED)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gsk_pango_renderer_class_init (GskPangoRendererClass *klass)
|
|
{
|
|
PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass);
|
|
|
|
renderer_class->draw_glyphs = gsk_pango_renderer_draw_glyphs;
|
|
renderer_class->draw_glyph_item = gsk_pango_renderer_draw_glyph_item;
|
|
renderer_class->draw_rectangle = gsk_pango_renderer_draw_rectangle;
|
|
renderer_class->draw_trapezoid = gsk_pango_renderer_draw_trapezoid;
|
|
renderer_class->draw_error_underline = gsk_pango_renderer_draw_error_underline;
|
|
renderer_class->draw_shape = gsk_pango_renderer_draw_shape;
|
|
}
|
|
|
|
static GskPangoRenderer *cached_renderer = NULL; /* MT-safe */
|
|
G_LOCK_DEFINE_STATIC (cached_renderer);
|
|
|
|
static GskPangoRenderer *
|
|
acquire_renderer (void)
|
|
{
|
|
GskPangoRenderer *renderer;
|
|
|
|
if (G_LIKELY (G_TRYLOCK (cached_renderer)))
|
|
{
|
|
if (G_UNLIKELY (!cached_renderer))
|
|
{
|
|
cached_renderer = g_object_new (GSK_TYPE_PANGO_RENDERER, NULL);
|
|
cached_renderer->is_cached_renderer = TRUE;
|
|
}
|
|
|
|
renderer = cached_renderer;
|
|
}
|
|
else
|
|
{
|
|
renderer = g_object_new (GSK_TYPE_PANGO_RENDERER, NULL);
|
|
}
|
|
|
|
return renderer;
|
|
}
|
|
|
|
static void
|
|
release_renderer (GskPangoRenderer *renderer)
|
|
{
|
|
if (G_LIKELY (renderer->is_cached_renderer))
|
|
{
|
|
renderer->snapshot = NULL;
|
|
g_clear_pointer (&renderer->name, g_free);
|
|
|
|
G_UNLOCK (cached_renderer);
|
|
}
|
|
else
|
|
g_object_unref (renderer);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_layout:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @layout: the #PangoLayout to render
|
|
* @color: the foreground color to render the layout in
|
|
* @name: (transfer none): a printf() style format string for the name for the new node
|
|
* @...: arguments to insert into the format string
|
|
*
|
|
* Creates render nodes for rendering @layout in the given foregound @color
|
|
* and appends them to the current node of @snapshot without changing the
|
|
* current node.
|
|
**/
|
|
void
|
|
gtk_snapshot_append_layout (GtkSnapshot *snapshot,
|
|
PangoLayout *layout,
|
|
const GdkRGBA *color,
|
|
const char *name,
|
|
...)
|
|
{
|
|
GskPangoRenderer *crenderer;
|
|
PangoRectangle ink_rect;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (PANGO_IS_LAYOUT (layout));
|
|
|
|
crenderer = acquire_renderer ();
|
|
|
|
crenderer->snapshot = snapshot;
|
|
crenderer->fg_color = *color;
|
|
if (name && gtk_snapshot_get_record_names (crenderer->snapshot))
|
|
{
|
|
va_list args;
|
|
|
|
va_start (args, name);
|
|
crenderer->name = g_strdup_vprintf (name, args);
|
|
va_end (args);
|
|
}
|
|
else
|
|
crenderer->name = NULL;
|
|
|
|
pango_layout_get_pixel_extents (layout, &ink_rect, NULL);
|
|
graphene_rect_init (&crenderer->bounds, ink_rect.x, ink_rect.y, ink_rect.width, ink_rect.height);
|
|
|
|
pango_renderer_draw_layout (PANGO_RENDERER (crenderer), layout, 0, 0);
|
|
|
|
release_renderer (crenderer);
|
|
}
|