/* Pango
* pangocairo-render.c: Rendering routines to Cairo surfaces
*
* Copyright (C) 2004 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.
*/
/**
* SECTION:pangocairo
* @short_description:Font handling and rendering with Cairo
* @title:Cairo Fonts and Rendering
*
* The Cairo library is a
* vector graphics library with a powerful rendering model. It has such
* features as anti-aliased primitives, alpha-compositing, and
* gradients. Multiple backends for Cairo are available, to allow
* rendering to images, to PDF files, and to the screen on X and on other
* windowing systems. The functions in this section allow using Pango
* to render to Cairo surfaces.
*
* Using Pango with Cairo is straightforward. A #PangoContext created
* with pango_cairo_font_map_create_context() can be used on any
* Cairo context (cairo_t), but needs to be updated to match the
* current transformation matrix and target surface of the Cairo context
* using pango_cairo_update_context(). The convenience functions
* pango_cairo_create_layout() and pango_cairo_update_layout() handle
* the common case where the program doesn't need to manipulate the
* properties of the #PangoContext.
*
* When you get the metrics of a layout or of a piece of a layout using
* functions such as pango_layout_get_extents(), the reported metrics
* are in user-space coordinates. If a piece of text is 10 units long,
* and you call cairo_scale (cr, 2.0), it still is more-or-less 10
* units long. However, the results will be affected by hinting
* (that is, the process of adjusting the text to look good on the
* pixel grid), so you shouldn't assume they are completely independent
* of the current transformation matrix. Note that the basic metrics
* functions in Pango report results in integer Pango units. To get
* to the floating point units used in Cairo divide by %PANGO_SCALE.
*
* ## Using Pango with Cairo ## {#rotated-example}
*
* |[
* #include
* #include
*
* static void
* draw_text (cairo_t *cr)
* {
* #define RADIUS 150
* #define N_WORDS 10
* #define FONT "Sans Bold 27"
*
* PangoLayout *layout;
* PangoFontDescription *desc;
* int i;
*
* /* Center coordinates on the middle of the region we are drawing
* */
* cairo_translate (cr, RADIUS, RADIUS);
*
* /* Create a PangoLayout, set the font and text */
* layout = pango_cairo_create_layout (cr);
*
* pango_layout_set_text (layout, "Text", -1);
* desc = pango_font_description_from_string (FONT);
* pango_layout_set_font_description (layout, desc);
* pango_font_description_free (desc);
*
* /* Draw the layout N_WORDS times in a circle */
* for (i = 0; i < N_WORDS; i++)
* {
* int width, height;
* double angle = (360. * i) / N_WORDS;
* double red;
*
* cairo_save (cr);
*
* /* Gradient from red at angle == 60 to blue at angle == 240 */
* red = (1 + cos ((angle - 60) * G_PI / 180.)) / 2;
* cairo_set_source_rgb (cr, red, 0, 1.0 - red);
*
* cairo_rotate (cr, angle * G_PI / 180.);
*
* /* Inform Pango to re-layout the text with the new transformation */
* pango_cairo_update_layout (cr, layout);
*
* pango_layout_get_size (layout, &width, &height);
* cairo_move_to (cr, - ((double)width / PANGO_SCALE) / 2, - RADIUS);
* pango_cairo_show_layout (cr, layout);
*
* cairo_restore (cr);
* }
*
* /* free the layout object */
* g_object_unref (layout);
* }
*
* int main (int argc, char **argv)
* {
* cairo_t *cr;
* char *filename;
* cairo_status_t status;
* cairo_surface_t *surface;
*
* if (argc != 2)
* {
* g_printerr ("Usage: cairosimple OUTPUT_FILENAME\n");
* return 1;
* }
*
* filename = argv[1];
*
* surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
* 2 * RADIUS, 2 * RADIUS);
* cr = cairo_create (surface);
*
* cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
* cairo_paint (cr);
* draw_text (cr);
* cairo_destroy (cr);
*
* status = cairo_surface_write_to_png (surface, filename);
* cairo_surface_destroy (surface);
*
* if (status != CAIRO_STATUS_SUCCESS)
* {
* g_printerr ("Could not save png to '%s'\n", filename);
* return 1;
* }
*
* return 0;
* }
* ]|
*
* Once you build and run the example code above, you should see the
* following result:
*
* ![Output of rotated-example](rotated-text.png)
*/
#include "config.h"
#include
#include "pango-font-private.h"
#include "pangocairo-private.h"
#include "pango-glyph-item.h"
typedef struct _PangoCairoRendererClass PangoCairoRendererClass;
#define PANGO_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO_TYPE_CAIRO_RENDERER, PangoCairoRendererClass))
#define PANGO_IS_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO_TYPE_CAIRO_RENDERER))
#define PANGO_CAIRO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO_TYPE_CAIRO_RENDERER, PangoCairoRendererClass))
struct _PangoCairoRenderer
{
PangoRenderer parent_instance;
cairo_t *cr;
gboolean do_path;
gboolean has_show_text_glyphs;
double x_offset, y_offset;
/* house-keeping options */
gboolean is_cached_renderer;
gboolean cr_had_current_point;
};
struct _PangoCairoRendererClass
{
PangoRendererClass parent_class;
};
G_DEFINE_TYPE (PangoCairoRenderer, pango_cairo_renderer, PANGO_TYPE_RENDERER)
static void
set_color (PangoCairoRenderer *crenderer,
PangoRenderPart part)
{
PangoColor *color = pango_renderer_get_color ((PangoRenderer *) (crenderer), part);
guint16 a = pango_renderer_get_alpha ((PangoRenderer *) (crenderer), part);
gdouble red, green, blue, alpha;
if (!a && !color)
return;
if (color)
{
red = color->red / 65535.;
green = color->green / 65535.;
blue = color->blue / 65535.;
alpha = 1.;
}
else
{
cairo_pattern_t *pattern = cairo_get_source (crenderer->cr);
if (pattern && cairo_pattern_get_type (pattern) == CAIRO_PATTERN_TYPE_SOLID)
cairo_pattern_get_rgba (pattern, &red, &green, &blue, &alpha);
else
{
red = 0.;
green = 0.;
blue = 0.;
alpha = 1.;
}
}
if (a)
alpha = a / 65535.;
cairo_set_source_rgba (crenderer->cr, red, green, blue, alpha);
}
/* note: modifies crenderer->cr without doing cairo_save/restore() */
static void
_pango_cairo_renderer_draw_frame (PangoCairoRenderer *crenderer,
double x,
double y,
double width,
double height,
double line_width,
gboolean invalid)
{
cairo_t *cr = crenderer->cr;
if (crenderer->do_path)
{
double d2 = line_width * .5, d = line_width;
/* we draw an outer box in one winding direction and an inner one in the
* opposite direction. This works for both cairo windings rules.
*
* what we really want is cairo_stroke_to_path(), but that's not
* implemented in cairo yet.
*/
/* outer */
cairo_rectangle (cr, x-d2, y-d2, width+d, height+d);
/* inner */
if (invalid)
{
/* delicacies of computing the joint... this is REALLY slow */
double alpha, tan_alpha2, cos_alpha;
double sx, sy;
alpha = atan2 (height, width);
tan_alpha2 = tan (alpha * .5);
if (tan_alpha2 < 1e-5 || (sx = d2 / tan_alpha2, 2. * sx > width - d))
sx = (width - d) * .5;
cos_alpha = cos (alpha);
if (cos_alpha < 1e-5 || (sy = d2 / cos_alpha, 2. * sy > height - d))
sy = (height - d) * .5;
/* top triangle */
cairo_new_sub_path (cr);
cairo_line_to (cr, x+width-sx, y+d2);
cairo_line_to (cr, x+sx, y+d2);
cairo_line_to (cr, x+.5*width, y+.5*height-sy);
cairo_close_path (cr);
/* bottom triangle */
cairo_new_sub_path (cr);
cairo_line_to (cr, x+width-sx, y+height-d2);
cairo_line_to (cr, x+.5*width, y+.5*height+sy);
cairo_line_to (cr, x+sx, y+height-d2);
cairo_close_path (cr);
alpha = G_PI_2 - alpha;
tan_alpha2 = tan (alpha * .5);
if (tan_alpha2 < 1e-5 || (sy = d2 / tan_alpha2, 2. * sy > height - d))
sy = (height - d) * .5;
cos_alpha = cos (alpha);
if (cos_alpha < 1e-5 || (sx = d2 / cos_alpha, 2. * sx > width - d))
sx = (width - d) * .5;
/* left triangle */
cairo_new_sub_path (cr);
cairo_line_to (cr, x+d2, y+sy);
cairo_line_to (cr, x+d2, y+height-sy);
cairo_line_to (cr, x+.5*width-sx, y+.5*height);
cairo_close_path (cr);
/* right triangle */
cairo_new_sub_path (cr);
cairo_line_to (cr, x+width-d2, y+sy);
cairo_line_to (cr, x+.5*width+sx, y+.5*height);
cairo_line_to (cr, x+width-d2, y+height-sy);
cairo_close_path (cr);
}
else
cairo_rectangle (cr, x+width-d2, y+d2, - (width-d), height-d);
}
else
{
cairo_rectangle (cr, x, y, width, height);
if (invalid)
{
/* draw an X */
cairo_new_sub_path (cr);
cairo_move_to (cr, x, y);
cairo_rel_line_to (cr, width, height);
cairo_new_sub_path (cr);
cairo_move_to (cr, x + width, y);
cairo_rel_line_to (cr, -width, height);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
}
cairo_set_line_width (cr, line_width);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
cairo_set_miter_limit (cr, 2.);
cairo_stroke (cr);
}
}
static void
_pango_cairo_renderer_draw_box_glyph (PangoCairoRenderer *crenderer,
PangoGlyphInfo *gi,
double cx,
double cy,
gboolean invalid)
{
cairo_save (crenderer->cr);
_pango_cairo_renderer_draw_frame (crenderer,
cx + 1.5,
cy + 1.5 - PANGO_UNKNOWN_GLYPH_HEIGHT,
(double)gi->geometry.width / PANGO_SCALE - 3.0,
PANGO_UNKNOWN_GLYPH_HEIGHT - 3.0,
1.0,
invalid);
cairo_restore (crenderer->cr);
}
static void
_pango_cairo_renderer_draw_unknown_glyph (PangoCairoRenderer *crenderer,
PangoFont *font,
PangoGlyphInfo *gi,
double cx,
double cy)
{
char buf[7];
double x0, y0;
int row, col;
int rows, cols;
double width, lsb;
char hexbox_string[2] = {0, 0};
PangoCairoFontHexBoxInfo *hbi;
gunichar ch;
gboolean invalid_input;
cairo_save (crenderer->cr);
ch = gi->glyph & ~PANGO_GLYPH_UNKNOWN_FLAG;
invalid_input = G_UNLIKELY (gi->glyph == PANGO_GLYPH_INVALID_INPUT || ch > 0x10FFFF);
hbi = _pango_cairo_font_get_hex_box_info ((PangoCairoFont *)font);
if (!hbi || !_pango_cairo_font_install ((PangoFont *)(hbi->font), crenderer->cr))
{
_pango_cairo_renderer_draw_box_glyph (crenderer, gi, cx, cy, invalid_input);
goto done;
}
rows = hbi->rows;
if (G_UNLIKELY (invalid_input))
{
cols = 1;
}
else
{
cols = (ch > 0xffff ? 6 : 4) / rows;
g_snprintf (buf, sizeof(buf), (ch > 0xffff) ? "%06X" : "%04X", ch);
}
width = (3 * hbi->pad_x + cols * (hbi->digit_width + hbi->pad_x));
lsb = ((double)gi->geometry.width / PANGO_SCALE - width) * .5;
lsb = floor (lsb / hbi->pad_x) * hbi->pad_x;
_pango_cairo_renderer_draw_frame (crenderer,
cx + lsb + .5 * hbi->pad_x,
cy + hbi->box_descent - hbi->box_height + hbi->pad_y * 0.5,
width - hbi->pad_x,
(hbi->box_height - hbi->pad_y),
hbi->line_width,
invalid_input);
if (invalid_input)
goto done;
x0 = cx + lsb + hbi->pad_x * 2;
y0 = cy + hbi->box_descent - hbi->pad_y * 2;
for (row = 0; row < rows; row++)
{
double y = y0 - (rows - 1 - row) * (hbi->digit_height + hbi->pad_y);
for (col = 0; col < cols; col++)
{
double x = x0 + col * (hbi->digit_width + hbi->pad_x);
cairo_move_to (crenderer->cr, x, y);
hexbox_string[0] = buf[row * cols + col];
if (crenderer->do_path)
cairo_text_path (crenderer->cr, hexbox_string);
else
cairo_show_text (crenderer->cr, hexbox_string);
}
}
done:
cairo_restore (crenderer->cr);
}
#ifndef STACK_BUFFER_SIZE
#define STACK_BUFFER_SIZE (512 * sizeof (int))
#endif
#define STACK_ARRAY_LENGTH(T) (STACK_BUFFER_SIZE / sizeof(T))
static void
pango_cairo_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)
{
PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
int i, count;
int x_position = 0;
cairo_glyph_t *cairo_glyphs;
cairo_glyph_t stack_glyphs[STACK_ARRAY_LENGTH (cairo_glyph_t)];
double base_x = crenderer->x_offset + (double)x / PANGO_SCALE;
double base_y = crenderer->y_offset + (double)y / PANGO_SCALE;
cairo_save (crenderer->cr);
if (!crenderer->do_path)
set_color (crenderer, PANGO_RENDER_PART_FOREGROUND);
if (!_pango_cairo_font_install (font, crenderer->cr))
{
for (i = 0; i < glyphs->num_glyphs; i++)
{
PangoGlyphInfo *gi = &glyphs->glyphs[i];
if (gi->glyph != PANGO_GLYPH_EMPTY)
{
double cx = base_x + (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
double cy = gi->geometry.y_offset == 0 ?
base_y :
base_y + (double)(gi->geometry.y_offset) / PANGO_SCALE;
_pango_cairo_renderer_draw_unknown_glyph (crenderer, font, gi, cx, cy);
}
x_position += gi->geometry.width;
}
goto done;
}
if (glyphs->num_glyphs > (int) G_N_ELEMENTS (stack_glyphs))
cairo_glyphs = g_new (cairo_glyph_t, glyphs->num_glyphs);
else
cairo_glyphs = stack_glyphs;
count = 0;
for (i = 0; i < glyphs->num_glyphs; i++)
{
PangoGlyphInfo *gi = &glyphs->glyphs[i];
if (gi->glyph != PANGO_GLYPH_EMPTY)
{
double cx = base_x + (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
double cy = gi->geometry.y_offset == 0 ?
base_y :
base_y + (double)(gi->geometry.y_offset) / PANGO_SCALE;
if (gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG)
{
if (gi->glyph == (0x20 | PANGO_GLYPH_UNKNOWN_FLAG))
; /* no hex boxes for space, please */
else
_pango_cairo_renderer_draw_unknown_glyph (crenderer, font, gi, cx, cy);
}
else
{
cairo_glyphs[count].index = gi->glyph;
cairo_glyphs[count].x = cx;
cairo_glyphs[count].y = cy;
count++;
}
}
x_position += gi->geometry.width;
}
if (G_UNLIKELY (crenderer->do_path))
cairo_glyph_path (crenderer->cr, cairo_glyphs, count);
else
if (G_UNLIKELY (clusters))
cairo_show_text_glyphs (crenderer->cr,
text, text_len,
cairo_glyphs, count,
clusters, num_clusters,
backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : 0);
else
cairo_show_glyphs (crenderer->cr, cairo_glyphs, count);
if (cairo_glyphs != stack_glyphs)
g_free (cairo_glyphs);
done:
cairo_restore (crenderer->cr);
}
static void
pango_cairo_renderer_draw_glyphs (PangoRenderer *renderer,
PangoFont *font,
PangoGlyphString *glyphs,
int x,
int y)
{
pango_cairo_renderer_show_text_glyphs (renderer,
NULL, 0,
glyphs,
NULL, 0,
FALSE,
font,
x, y);
}
static void
pango_cairo_renderer_draw_glyph_item (PangoRenderer *renderer,
const char *text,
PangoGlyphItem *glyph_item,
int x,
int y)
{
PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
PangoFont *font = glyph_item->item->analysis.font;
PangoGlyphString *glyphs = glyph_item->glyphs;
PangoItem *item = glyph_item->item;
gboolean backward = (item->analysis.level & 1) != 0;
PangoGlyphItemIter iter;
cairo_text_cluster_t *cairo_clusters;
cairo_text_cluster_t stack_clusters[STACK_ARRAY_LENGTH (cairo_text_cluster_t)];
int num_clusters;
if (!crenderer->has_show_text_glyphs || crenderer->do_path)
{
pango_cairo_renderer_show_text_glyphs (renderer,
NULL, 0,
glyphs,
NULL, 0,
FALSE,
font,
x, y);
return;
}
if (glyphs->num_glyphs > (int) G_N_ELEMENTS (stack_clusters))
cairo_clusters = g_new (cairo_text_cluster_t, glyphs->num_glyphs);
else
cairo_clusters = stack_clusters;
num_clusters = 0;
if (pango_glyph_item_iter_init_start (&iter, glyph_item, text))
{
do {
int num_bytes, num_glyphs, i;
num_bytes = iter.end_index - iter.start_index;
num_glyphs = backward ? iter.start_glyph - iter.end_glyph : iter.end_glyph - iter.start_glyph;
if (num_bytes < 1)
g_warning ("pango_cairo_renderer_draw_glyph_item: bad cluster has num_bytes %d", num_bytes);
if (num_glyphs < 1)
g_warning ("pango_cairo_renderer_draw_glyph_item: bad cluster has num_glyphs %d", num_glyphs);
/* Discount empty and unknown glyphs */
for (i = MIN (iter.start_glyph, iter.end_glyph+1);
i < MAX (iter.start_glyph+1, iter.end_glyph);
i++)
{
PangoGlyphInfo *gi = &glyphs->glyphs[i];
if (gi->glyph == PANGO_GLYPH_EMPTY ||
gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG)
num_glyphs--;
}
cairo_clusters[num_clusters].num_bytes = num_bytes;
cairo_clusters[num_clusters].num_glyphs = num_glyphs;
num_clusters++;
} while (pango_glyph_item_iter_next_cluster (&iter));
}
pango_cairo_renderer_show_text_glyphs (renderer,
text + item->offset, item->length,
glyphs,
cairo_clusters, num_clusters,
backward,
font,
x, y);
if (cairo_clusters != stack_clusters)
g_free (cairo_clusters);
}
static void
pango_cairo_renderer_draw_rectangle (PangoRenderer *renderer,
PangoRenderPart part,
int x,
int y,
int width,
int height)
{
PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
if (!crenderer->do_path)
{
cairo_save (crenderer->cr);
set_color (crenderer, part);
}
cairo_rectangle (crenderer->cr,
crenderer->x_offset + (double)x / PANGO_SCALE,
crenderer->y_offset + (double)y / PANGO_SCALE,
(double)width / PANGO_SCALE, (double)height / PANGO_SCALE);
if (!crenderer->do_path)
{
cairo_fill (crenderer->cr);
cairo_restore (crenderer->cr);
}
}
static void
pango_cairo_renderer_draw_trapezoid (PangoRenderer *renderer,
PangoRenderPart part,
double y1_,
double x11,
double x21,
double y2,
double x12,
double x22)
{
PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
cairo_t *cr;
double x, y;
cr = crenderer->cr;
cairo_save (cr);
if (!crenderer->do_path)
set_color (crenderer, part);
x = crenderer->x_offset,
y = crenderer->y_offset;
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);
if (!crenderer->do_path)
cairo_fill (cr);
cairo_restore (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
pango_cairo_renderer_draw_error_underline (PangoRenderer *renderer,
int x,
int y,
int width,
int height)
{
PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
cairo_t *cr = crenderer->cr;
if (!crenderer->do_path)
{
cairo_save (cr);
set_color (crenderer, PANGO_RENDER_PART_UNDERLINE);
cairo_new_path (cr);
}
draw_error_underline (cr,
crenderer->x_offset + (double)x / PANGO_SCALE,
crenderer->y_offset + (double)y / PANGO_SCALE,
(double)width / PANGO_SCALE, (double)height / PANGO_SCALE);
if (!crenderer->do_path)
{
cairo_fill (cr);
cairo_restore (cr);
}
}
static void
pango_cairo_renderer_draw_shape (PangoRenderer *renderer,
PangoAttrShape *attr,
int x,
int y)
{
PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
cairo_t *cr = crenderer->cr;
PangoLayout *layout;
PangoCairoShapeRendererFunc shape_renderer;
gpointer shape_renderer_data;
double base_x, base_y;
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;
base_x = crenderer->x_offset + (double)x / PANGO_SCALE;
base_y = crenderer->y_offset + (double)y / PANGO_SCALE;
cairo_save (cr);
if (!crenderer->do_path)
set_color (crenderer, PANGO_RENDER_PART_FOREGROUND);
cairo_move_to (cr, base_x, base_y);
shape_renderer (cr, attr, crenderer->do_path, shape_renderer_data);
cairo_restore (cr);
}
static void
pango_cairo_renderer_init (PangoCairoRenderer *renderer G_GNUC_UNUSED)
{
}
static void
pango_cairo_renderer_class_init (PangoCairoRendererClass *klass)
{
PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass);
renderer_class->draw_glyphs = pango_cairo_renderer_draw_glyphs;
renderer_class->draw_glyph_item = pango_cairo_renderer_draw_glyph_item;
renderer_class->draw_rectangle = pango_cairo_renderer_draw_rectangle;
renderer_class->draw_trapezoid = pango_cairo_renderer_draw_trapezoid;
renderer_class->draw_error_underline = pango_cairo_renderer_draw_error_underline;
renderer_class->draw_shape = pango_cairo_renderer_draw_shape;
}
static PangoCairoRenderer *cached_renderer = NULL; /* MT-safe */
G_LOCK_DEFINE_STATIC (cached_renderer);
static PangoCairoRenderer *
acquire_renderer (void)
{
PangoCairoRenderer *renderer;
if (G_LIKELY (G_TRYLOCK (cached_renderer)))
{
if (G_UNLIKELY (!cached_renderer))
{
cached_renderer = g_object_new (PANGO_TYPE_CAIRO_RENDERER, NULL);
cached_renderer->is_cached_renderer = TRUE;
}
renderer = cached_renderer;
}
else
{
renderer = g_object_new (PANGO_TYPE_CAIRO_RENDERER, NULL);
}
return renderer;
}
static void
release_renderer (PangoCairoRenderer *renderer)
{
if (G_LIKELY (renderer->is_cached_renderer))
{
renderer->cr = NULL;
renderer->do_path = FALSE;
renderer->has_show_text_glyphs = FALSE;
renderer->x_offset = 0.;
renderer->y_offset = 0.;
G_UNLOCK (cached_renderer);
}
else
g_object_unref (renderer);
}
static void
save_current_point (PangoCairoRenderer *renderer)
{
renderer->cr_had_current_point = cairo_has_current_point (renderer->cr);
cairo_get_current_point (renderer->cr, &renderer->x_offset, &renderer->y_offset);
/* abuse save_current_point() to cache cairo_has_show_text_glyphs() result */
renderer->has_show_text_glyphs = cairo_surface_has_show_text_glyphs (cairo_get_target (renderer->cr));
}
static void
restore_current_point (PangoCairoRenderer *renderer)
{
if (renderer->cr_had_current_point)
/* XXX should do cairo_set_current_point() when we have that function */
cairo_move_to (renderer->cr, renderer->x_offset, renderer->y_offset);
else
cairo_new_sub_path (renderer->cr);
}
/* convenience wrappers using the default renderer */
static void
_pango_cairo_do_glyph_string (cairo_t *cr,
PangoFont *font,
PangoGlyphString *glyphs,
gboolean do_path)
{
PangoCairoRenderer *crenderer = acquire_renderer ();
PangoRenderer *renderer = (PangoRenderer *) crenderer;
crenderer->cr = cr;
crenderer->do_path = do_path;
save_current_point (crenderer);
if (!do_path)
{
/* unset all part colors, since when drawing just a glyph string,
* prepare_run() isn't called.
*/
pango_renderer_activate (renderer);
pango_renderer_set_color (renderer, PANGO_RENDER_PART_FOREGROUND, NULL);
pango_renderer_set_color (renderer, PANGO_RENDER_PART_BACKGROUND, NULL);
pango_renderer_set_color (renderer, PANGO_RENDER_PART_UNDERLINE, NULL);
pango_renderer_set_color (renderer, PANGO_RENDER_PART_STRIKETHROUGH, NULL);
}
pango_renderer_draw_glyphs (renderer, font, glyphs, 0, 0);
if (!do_path)
{
pango_renderer_deactivate (renderer);
}
restore_current_point (crenderer);
release_renderer (crenderer);
}
static void
_pango_cairo_do_glyph_item (cairo_t *cr,
const char *text,
PangoGlyphItem *glyph_item,
gboolean do_path)
{
PangoCairoRenderer *crenderer = acquire_renderer ();
PangoRenderer *renderer = (PangoRenderer *) crenderer;
crenderer->cr = cr;
crenderer->do_path = do_path;
save_current_point (crenderer);
if (!do_path)
{
/* unset all part colors, since when drawing just a glyph string,
* prepare_run() isn't called.
*/
pango_renderer_activate (renderer);
pango_renderer_set_color (renderer, PANGO_RENDER_PART_FOREGROUND, NULL);
pango_renderer_set_color (renderer, PANGO_RENDER_PART_BACKGROUND, NULL);
pango_renderer_set_color (renderer, PANGO_RENDER_PART_UNDERLINE, NULL);
pango_renderer_set_color (renderer, PANGO_RENDER_PART_STRIKETHROUGH, NULL);
}
pango_renderer_draw_glyph_item (renderer, text, glyph_item, 0, 0);
if (!do_path)
{
pango_renderer_deactivate (renderer);
}
restore_current_point (crenderer);
release_renderer (crenderer);
}
static void
_pango_cairo_do_layout_line (cairo_t *cr,
PangoLayoutLine *line,
gboolean do_path)
{
PangoCairoRenderer *crenderer = acquire_renderer ();
PangoRenderer *renderer = (PangoRenderer *) crenderer;
crenderer->cr = cr;
crenderer->do_path = do_path;
save_current_point (crenderer);
pango_renderer_draw_layout_line (renderer, line, 0, 0);
restore_current_point (crenderer);
release_renderer (crenderer);
}
static void
_pango_cairo_do_layout (cairo_t *cr,
PangoLayout *layout,
gboolean do_path)
{
PangoCairoRenderer *crenderer = acquire_renderer ();
PangoRenderer *renderer = (PangoRenderer *) crenderer;
crenderer->cr = cr;
crenderer->do_path = do_path;
save_current_point (crenderer);
pango_renderer_draw_layout (renderer, layout, 0, 0);
restore_current_point (crenderer);
release_renderer (crenderer);
}
static void
_pango_cairo_do_error_underline (cairo_t *cr,
double x,
double y,
double width,
double height,
gboolean do_path)
{
/* We don't use a renderer here, for a simple reason:
* the only renderer we can get is the default renderer, that
* is all implemented here, so we shortcircuit and make our
* life way easier.
*/
if (!do_path)
cairo_new_path (cr);
draw_error_underline (cr, x, y, width, height);
if (!do_path)
cairo_fill (cr);
}
/* public wrapper of above to show or append path */
/**
* pango_cairo_show_glyph_string:
* @cr: a Cairo context
* @font: a #PangoFont from a #PangoCairoFontMap
* @glyphs: a #PangoGlyphString
*
* Draws the glyphs in @glyphs in the specified cairo context.
* The origin of the glyphs (the left edge of the baseline) will
* be drawn at the current point of the cairo context.
*
* Since: 1.10
**/
void
pango_cairo_show_glyph_string (cairo_t *cr,
PangoFont *font,
PangoGlyphString *glyphs)
{
g_return_if_fail (cr != NULL);
g_return_if_fail (glyphs != NULL);
_pango_cairo_do_glyph_string (cr, font, glyphs, FALSE);
}
/**
* pango_cairo_show_glyph_item:
* @cr: a Cairo context
* @text: the UTF-8 text that @glyph_item refers to
* @glyph_item: a #PangoGlyphItem
*
* Draws the glyphs in @glyph_item in the specified cairo context,
* embedding the text associated with the glyphs in the output if the
* output format supports it (PDF for example), otherwise it acts
* similar to pango_cairo_show_glyph_string().
*
* The origin of the glyphs (the left edge of the baseline) will
* be drawn at the current point of the cairo context.
*
* Note that @text is the start of the text for layout, which is then
* indexed by @glyph_item->item->offset.
*
* Since: 1.22
**/
void
pango_cairo_show_glyph_item (cairo_t *cr,
const char *text,
PangoGlyphItem *glyph_item)
{
g_return_if_fail (cr != NULL);
g_return_if_fail (text != NULL);
g_return_if_fail (glyph_item != NULL);
_pango_cairo_do_glyph_item (cr, text, glyph_item, FALSE);
}
/**
* pango_cairo_show_layout_line:
* @cr: a Cairo context
* @line: a #PangoLayoutLine
*
* Draws a #PangoLayoutLine in the specified cairo context.
* The origin of the glyphs (the left edge of the line) will
* be drawn at the current point of the cairo context.
*
* Since: 1.10
**/
void
pango_cairo_show_layout_line (cairo_t *cr,
PangoLayoutLine *line)
{
g_return_if_fail (cr != NULL);
g_return_if_fail (line != NULL);
_pango_cairo_do_layout_line (cr, line, FALSE);
}
/**
* pango_cairo_show_layout:
* @cr: a Cairo context
* @layout: a Pango layout
*
* Draws a #PangoLayout in the specified cairo context.
* The top-left corner of the #PangoLayout will be drawn
* at the current point of the cairo context.
*
* Since: 1.10
**/
void
pango_cairo_show_layout (cairo_t *cr,
PangoLayout *layout)
{
g_return_if_fail (cr != NULL);
g_return_if_fail (PANGO_IS_LAYOUT (layout));
_pango_cairo_do_layout (cr, layout, FALSE);
}
/**
* pango_cairo_show_error_underline:
* @cr: a Cairo context
* @x: The X coordinate of one corner of the rectangle
* @y: The Y coordinate of one corner of the rectangle
* @width: Non-negative width of the rectangle
* @height: Non-negative height of the rectangle
*
* Draw a squiggly line in the specified cairo context that approximately
* covers the given rectangle in the style of an underline used to indicate a
* spelling error. (The width of the underline is rounded to an integer
* number of up/down segments and the resulting rectangle is centered in the
* original rectangle)
*
* Since: 1.14
**/
void
pango_cairo_show_error_underline (cairo_t *cr,
double x,
double y,
double width,
double height)
{
g_return_if_fail (cr != NULL);
g_return_if_fail ((width >= 0) && (height >= 0));
_pango_cairo_do_error_underline (cr, x, y, width, height, FALSE);
}
/**
* pango_cairo_glyph_string_path:
* @cr: a Cairo context
* @font: a #PangoFont from a #PangoCairoFontMap
* @glyphs: a #PangoGlyphString
*
* Adds the glyphs in @glyphs to the current path in the specified
* cairo context. The origin of the glyphs (the left edge of the baseline)
* will be at the current point of the cairo context.
*
* Since: 1.10
**/
void
pango_cairo_glyph_string_path (cairo_t *cr,
PangoFont *font,
PangoGlyphString *glyphs)
{
g_return_if_fail (cr != NULL);
g_return_if_fail (glyphs != NULL);
_pango_cairo_do_glyph_string (cr, font, glyphs, TRUE);
}
/**
* pango_cairo_layout_line_path:
* @cr: a Cairo context
* @line: a #PangoLayoutLine
*
* Adds the text in #PangoLayoutLine to the current path in the
* specified cairo context. The origin of the glyphs (the left edge
* of the line) will be at the current point of the cairo context.
*
* Since: 1.10
**/
void
pango_cairo_layout_line_path (cairo_t *cr,
PangoLayoutLine *line)
{
g_return_if_fail (cr != NULL);
g_return_if_fail (line != NULL);
_pango_cairo_do_layout_line (cr, line, TRUE);
}
/**
* pango_cairo_layout_path:
* @cr: a Cairo context
* @layout: a Pango layout
*
* Adds the text in a #PangoLayout to the current path in the
* specified cairo context. The top-left corner of the #PangoLayout
* will be at the current point of the cairo context.
*
* Since: 1.10
**/
void
pango_cairo_layout_path (cairo_t *cr,
PangoLayout *layout)
{
g_return_if_fail (cr != NULL);
g_return_if_fail (PANGO_IS_LAYOUT (layout));
_pango_cairo_do_layout (cr, layout, TRUE);
}
/**
* pango_cairo_error_underline_path:
* @cr: a Cairo context
* @x: The X coordinate of one corner of the rectangle
* @y: The Y coordinate of one corner of the rectangle
* @width: Non-negative width of the rectangle
* @height: Non-negative height of the rectangle
*
* Add a squiggly line to the current path in the specified cairo context that
* approximately covers the given rectangle in the style of an underline used
* to indicate a spelling error. (The width of the underline is rounded to an
* integer number of up/down segments and the resulting rectangle is centered
* in the original rectangle)
*
* Since: 1.14
**/
void
pango_cairo_error_underline_path (cairo_t *cr,
double x,
double y,
double width,
double height)
{
g_return_if_fail (cr != NULL);
g_return_if_fail ((width >= 0) && (height >= 0));
_pango_cairo_do_error_underline (cr, x, y, width, height, TRUE);
}