diff options
Diffstat (limited to 'pango2/pangocairo-render.c')
-rw-r--r-- | pango2/pangocairo-render.c | 1255 |
1 files changed, 1255 insertions, 0 deletions
diff --git a/pango2/pangocairo-render.c b/pango2/pangocairo-render.c new file mode 100644 index 00000000..b5189bee --- /dev/null +++ b/pango2/pangocairo-render.c @@ -0,0 +1,1255 @@ +/* Pango2 + * 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. + */ + +#include "config.h" + +#include <math.h> + +#include "pango-item-private.h" +#include "pango-font-private.h" +#include "pangocairo-private.h" +#include "pango-glyph-item-private.h" +#include "pango-glyph-iter-private.h" +#include "pango-run-private.h" +#include "pango-impl-utils.h" +#include "pango-hbfont-private.h" + +typedef struct _Pango2CairoRendererClass Pango2CairoRendererClass; + +#define PANGO2_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO2_TYPE_CAIRO_RENDERER, Pango2CairoRendererClass)) +#define PANGO2_IS_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO2_TYPE_CAIRO_RENDERER)) +#define PANGO2_CAIRO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO2_TYPE_CAIRO_RENDERER, Pango2CairoRendererClass)) + +struct _Pango2CairoRenderer +{ + Pango2Renderer 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 _Pango2CairoRendererClass +{ + Pango2RendererClass parent_class; +}; + +G_DEFINE_TYPE (Pango2CairoRenderer, pango2_cairo_renderer, PANGO2_TYPE_RENDERER) + +static void +set_color (Pango2CairoRenderer *crenderer, + Pango2RenderPart part) +{ + Pango2Color *color = pango2_renderer_get_color ((Pango2Renderer *) (crenderer), part); + double red, green, blue, alpha; + + if (!color) + return; + + if (color) + { + red = color->red / 65535.; + green = color->green / 65535.; + blue = color->blue / 65535.; + alpha = color->alpha / 65535.; + } + 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.; + } + } + + cairo_set_source_rgba (crenderer->cr, red, green, blue, alpha); +} + +/* note: modifies crenderer->cr without doing cairo_save/restore() */ +static void +_pango2_cairo_renderer_draw_frame (Pango2CairoRenderer *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 +_pango2_cairo_renderer_draw_box_glyph (Pango2CairoRenderer *crenderer, + Pango2GlyphInfo *gi, + double cx, + double cy, + gboolean invalid) +{ + cairo_save (crenderer->cr); + + _pango2_cairo_renderer_draw_frame (crenderer, + cx + 1.5, + cy + 1.5 - PANGO2_UNKNOWN_GLYPH_HEIGHT, + (double)gi->geometry.width / PANGO2_SCALE - 3.0, + PANGO2_UNKNOWN_GLYPH_HEIGHT - 3.0, + 1.0, + invalid); + + cairo_restore (crenderer->cr); +} + +static void +_pango2_cairo_renderer_draw_unknown_glyph (Pango2CairoRenderer *crenderer, + Pango2Font *font, + Pango2GlyphInfo *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 }; + Pango2CairoFontHexBoxInfo *hbi; + gunichar ch; + gboolean invalid_input; + const char *p; + const char *name; + + cairo_save (crenderer->cr); + + ch = gi->glyph & ~PANGO2_GLYPH_UNKNOWN_FLAG; + invalid_input = G_UNLIKELY (gi->glyph == PANGO2_GLYPH_INVALID_INPUT || ch > 0x10FFFF); + + if (PANGO2_IS_HB_FONT (font)) + hbi = PANGO2_HB_FONT (font)->hex_box_info; + else + hbi = _pango2_cairo_font_get_hex_box_info (font); + if (!hbi || !_pango2_cairo_font_install ((Pango2Font *)(hbi->font), crenderer->cr)) + { + _pango2_cairo_renderer_draw_box_glyph (crenderer, gi, cx, cy, invalid_input); + goto done; + } + + if (G_UNLIKELY (invalid_input)) + { + rows = hbi->rows; + cols = 1; + } + else if (ch == 0x2423 || + g_unichar_type (ch) == G_UNICODE_SPACE_SEPARATOR) + { + /* We never want to show a hex box or other drawing for + * space. If we want space to be visible, we replace 0x20 + * by 0x2423 (visible space). + * + * Since we don't want to rely on glyph availability, + * we render a centered dot ourselves. + */ + double x = cx + 0.5 *((double)gi->geometry.width / PANGO2_SCALE); + double y = cy + hbi->box_descent - 0.5 * hbi->box_height; + + cairo_new_sub_path (crenderer->cr); + cairo_arc (crenderer->cr, x, y, 1.5 * hbi->line_width, 0, 2 * G_PI); + cairo_close_path (crenderer->cr); + cairo_fill (crenderer->cr); + goto done; + } + else if (ch == '\t') + { + /* Since we don't want to rely on glyph availability, + * we render an arrow like ↦ ourselves. + */ + double y = cy + hbi->box_descent - 0.5 * hbi->box_height; + double width = (double)gi->geometry.width / PANGO2_SCALE; + double offset = 0.2 * width; + double x = cx + offset; + double al = width - 2 * offset; /* arrow length */ + double tl = MIN (hbi->digit_width, 0.75 * al); /* tip length */ + double tw2 = 2.5 * hbi->line_width; /* tip width / 2 */ + double lw2 = 0.5 * hbi->line_width; /* line width / 2 */ + + cairo_move_to (crenderer->cr, x - lw2, y - tw2); + cairo_line_to (crenderer->cr, x + lw2, y - tw2); + cairo_line_to (crenderer->cr, x + lw2, y - lw2); + cairo_line_to (crenderer->cr, x + al - tl, y - lw2); + cairo_line_to (crenderer->cr, x + al - tl, y - tw2); + cairo_line_to (crenderer->cr, x + al, y); + cairo_line_to (crenderer->cr, x + al - tl, y + tw2); + cairo_line_to (crenderer->cr, x + al - tl, y + lw2); + cairo_line_to (crenderer->cr, x + lw2, y + lw2); + cairo_line_to (crenderer->cr, x + lw2, y + tw2); + cairo_line_to (crenderer->cr, x - lw2, y + tw2); + cairo_close_path (crenderer->cr); + cairo_fill (crenderer->cr); + goto done; + } + else if (ch == '\n' || ch == 0x2028 || ch == 0x2029) + { + /* Since we don't want to rely on glyph availability, + * we render an arrow like ↵ ourselves. + */ + double width = (double)gi->geometry.width / PANGO2_SCALE; + double offset = 0.2 * width; + double al = width - 2 * offset; /* arrow length */ + double tl = MIN (hbi->digit_width, 0.75 * al); /* tip length */ + double ah = al - 0.5 * tl; /* arrow height */ + double tw2 = 2.5 * hbi->line_width; /* tip width / 2 */ + double x = cx + offset; + double y = cy - (hbi->box_height - al) / 2; + double lw2 = 0.5 * hbi->line_width; /* line width / 2 */ + + cairo_move_to (crenderer->cr, x, y); + cairo_line_to (crenderer->cr, x + tl, y - tw2); + cairo_line_to (crenderer->cr, x + tl, y - lw2); + cairo_line_to (crenderer->cr, x + al - lw2, y - lw2); + cairo_line_to (crenderer->cr, x + al - lw2, y - ah); + cairo_line_to (crenderer->cr, x + al + lw2, y - ah); + cairo_line_to (crenderer->cr, x + al + lw2, y + lw2); + cairo_line_to (crenderer->cr, x + tl, y + lw2); + cairo_line_to (crenderer->cr, x + tl, y + tw2); + cairo_close_path (crenderer->cr); + cairo_fill (crenderer->cr); + goto done; + } + else if ((name = pango2_get_ignorable_size (ch, &rows, &cols))) + { + /* Nothing else to do, we render 'default ignorable' chars + * as hex box with their nick. + */ + } + else + { + /* Everything else gets a traditional hex box. */ + rows = hbi->rows; + cols = (ch > 0xffff ? 6 : 4) / rows; + g_snprintf (buf, sizeof(buf), (ch > 0xffff) ? "%06X" : "%04X", ch); + name = buf; + } + + width = (3 * hbi->pad_x + cols * (hbi->digit_width + hbi->pad_x)); + lsb = ((double)gi->geometry.width / PANGO2_SCALE - width) * .5; + lsb = floor (lsb / hbi->pad_x) * hbi->pad_x; + + _pango2_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 - ((hbi->rows - rows) * hbi->digit_height / 2); + + for (row = 0, p = name; row < rows; row++) + { + double y = y0 - (rows - 1 - row) * (hbi->digit_height + hbi->pad_y); + for (col = 0; col < cols; col++, p++) + { + double x = x0 + col * (hbi->digit_width + hbi->pad_x); + + if (!p) + goto done; + + cairo_move_to (crenderer->cr, x, y); + + hexbox_string[0] = p[0]; + + 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 +pango2_cairo_renderer_show_text_glyphs (Pango2Renderer *renderer, + const char *text, + int text_len, + Pango2GlyphString *glyphs, + cairo_text_cluster_t *clusters, + int num_clusters, + gboolean backward, + Pango2Font *font, + int x, + int y) +{ + Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (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 / PANGO2_SCALE; + double base_y = crenderer->y_offset + (double)y / PANGO2_SCALE; + + cairo_save (crenderer->cr); + if (!crenderer->do_path) + set_color (crenderer, PANGO2_RENDER_PART_FOREGROUND); + + if (!_pango2_cairo_font_install (font, crenderer->cr)) + { + for (i = 0; i < glyphs->num_glyphs; i++) + { + Pango2GlyphInfo *gi = &glyphs->glyphs[i]; + + if (gi->glyph != PANGO2_GLYPH_EMPTY) + { + double cx = base_x + (double)(x_position + gi->geometry.x_offset) / PANGO2_SCALE; + double cy = gi->geometry.y_offset == 0 ? + base_y : + base_y + (double)(gi->geometry.y_offset) / PANGO2_SCALE; + + _pango2_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++) + { + Pango2GlyphInfo *gi = &glyphs->glyphs[i]; + + if (gi->glyph != PANGO2_GLYPH_EMPTY) + { + double cx = base_x + (double)(x_position + gi->geometry.x_offset) / PANGO2_SCALE; + double cy = gi->geometry.y_offset == 0 ? + base_y : + base_y + (double)(gi->geometry.y_offset) / PANGO2_SCALE; + + if (gi->glyph & PANGO2_GLYPH_UNKNOWN_FLAG) + { + if (gi->glyph == (0x20 | PANGO2_GLYPH_UNKNOWN_FLAG)) + ; /* no hex boxes for space, please */ + else + _pango2_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 +pango2_cairo_renderer_draw_glyphs (Pango2Renderer *renderer, + Pango2Font *font, + Pango2GlyphString *glyphs, + int x, + int y) +{ + pango2_cairo_renderer_show_text_glyphs (renderer, NULL, 0, glyphs, NULL, 0, FALSE, font, x, y); +} + +static void +pango2_cairo_renderer_draw_run (Pango2Renderer *renderer, + const char *text, + Pango2Run *run, + int x, + int y) +{ + Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer); + Pango2Item *item = pango2_run_get_item (run); + Pango2GlyphString *glyphs = pango2_run_get_glyphs (run); + Pango2Font *font = item->analysis.font; + gboolean backward = (item->analysis.level & 1) != 0; + Pango2GlyphItem *glyph_item = pango2_run_get_glyph_item (run); + Pango2GlyphItemIter 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) + { + pango2_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 (pango2_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 ("pango2_cairo_renderer_draw_glyph_item: bad cluster has num_bytes %d", num_bytes); + if (num_glyphs < 1) + g_warning ("pango2_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++) + { + Pango2GlyphInfo *gi = &glyphs->glyphs[i]; + + if (gi->glyph == PANGO2_GLYPH_EMPTY || + gi->glyph & PANGO2_GLYPH_UNKNOWN_FLAG) + num_glyphs--; + } + + cairo_clusters[num_clusters].num_bytes = num_bytes; + cairo_clusters[num_clusters].num_glyphs = num_glyphs; + num_clusters++; + } while (pango2_glyph_item_iter_next_cluster (&iter)); + } + + pango2_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 +pango2_cairo_renderer_draw_rectangle (Pango2Renderer *renderer, + Pango2RenderPart part, + int x, + int y, + int width, + int height) +{ + Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer); + + if (!crenderer->do_path) + { + cairo_save (crenderer->cr); + + set_color (crenderer, part); + } + + cairo_rectangle (crenderer->cr, + crenderer->x_offset + (double)x / PANGO2_SCALE, + crenderer->y_offset + (double)y / PANGO2_SCALE, + (double)width / PANGO2_SCALE, (double)height / PANGO2_SCALE); + + if (!crenderer->do_path) + { + cairo_fill (crenderer->cr); + + cairo_restore (crenderer->cr); + } +} + +static void +pango2_cairo_renderer_draw_trapezoid (Pango2Renderer *renderer, + Pango2RenderPart part, + double y1_, + double x11, + double x21, + double y2, + double x12, + double x22) +{ + Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (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_wavy_line (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 +pango2_cairo_renderer_draw_styled_line (Pango2Renderer *renderer, + Pango2RenderPart part, + Pango2LineStyle style, + int x, + int y, + int width, + int height) +{ + Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer); + cairo_t *cr = crenderer->cr; + + if (!crenderer->do_path) + { + cairo_save (cr); + + set_color (crenderer, part); + + cairo_new_path (cr); + } + + switch (style) + { + case PANGO2_LINE_STYLE_NONE: + break; + + case PANGO2_LINE_STYLE_DOTTED: + case PANGO2_LINE_STYLE_DASHED: + cairo_save (cr); + cairo_set_line_width (cr, (double)height / PANGO2_SCALE); + if (style == PANGO2_LINE_STYLE_DOTTED) + { + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_dash (cr, (const double []){0., (double)(2 * height) / PANGO2_SCALE}, 2, 0.); + } + else + { + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + cairo_set_dash (cr, (const double []){(double)(3 * height) / PANGO2_SCALE, (double)(3 * height) / PANGO2_SCALE}, 2, 0.); + } + cairo_move_to (cr, + crenderer->x_offset + (double)x / PANGO2_SCALE + (double)height / (2 * PANGO2_SCALE), + crenderer->y_offset + (double)y / PANGO2_SCALE + (double)height / (2 * PANGO2_SCALE)); + cairo_line_to (cr, + crenderer->x_offset + (double)x / PANGO2_SCALE + (double)width / PANGO2_SCALE - (double)height / PANGO2_SCALE, + crenderer->y_offset + (double)y / PANGO2_SCALE + (double)height / (2 * PANGO2_SCALE)); + cairo_stroke (cr); + cairo_restore (cr); + break; + + case PANGO2_LINE_STYLE_SOLID: + cairo_rectangle (cr, + crenderer->x_offset + (double)x / PANGO2_SCALE, + crenderer->y_offset + (double)y / PANGO2_SCALE, + (double)width / PANGO2_SCALE, (double)height / PANGO2_SCALE); + break; + + case PANGO2_LINE_STYLE_DOUBLE: + cairo_rectangle (cr, + crenderer->x_offset + (double)x / PANGO2_SCALE, + crenderer->y_offset + (double)y / PANGO2_SCALE, + (double)width / PANGO2_SCALE, (double)height / (3 * PANGO2_SCALE)); + cairo_rectangle (cr, + crenderer->x_offset + (double)x / PANGO2_SCALE, + crenderer->y_offset + (double)y / PANGO2_SCALE + (double)(2 * height) / (3 * PANGO2_SCALE), + (double)width / PANGO2_SCALE, (double)height / (3 * PANGO2_SCALE)); + break; + + + case PANGO2_LINE_STYLE_WAVY: + draw_wavy_line (cr, + crenderer->x_offset + (double)x / PANGO2_SCALE, + crenderer->y_offset + (double)y / PANGO2_SCALE, + (double)width / PANGO2_SCALE, (double)height / PANGO2_SCALE); + break; + default: + g_assert_not_reached (); + } + + if (!crenderer->do_path) + { + cairo_fill (cr); + + cairo_restore (cr); + } +} + +static void +pango2_cairo_renderer_init (Pango2CairoRenderer *renderer G_GNUC_UNUSED) +{ +} + +static void +pango2_cairo_renderer_class_init (Pango2CairoRendererClass *klass) +{ + Pango2RendererClass *renderer_class = PANGO2_RENDERER_CLASS (klass); + + renderer_class->draw_glyphs = pango2_cairo_renderer_draw_glyphs; + renderer_class->draw_run = pango2_cairo_renderer_draw_run; + renderer_class->draw_rectangle = pango2_cairo_renderer_draw_rectangle; + renderer_class->draw_styled_line = pango2_cairo_renderer_draw_styled_line; + renderer_class->draw_trapezoid = pango2_cairo_renderer_draw_trapezoid; +} + +static Pango2CairoRenderer *cached_renderer = NULL; /* MT-safe */ +G_LOCK_DEFINE_STATIC (cached_renderer); + +static Pango2CairoRenderer * +acquire_renderer (void) +{ + Pango2CairoRenderer *renderer; + + if (G_LIKELY (G_TRYLOCK (cached_renderer))) + { + if (G_UNLIKELY (!cached_renderer)) + { + cached_renderer = g_object_new (PANGO2_TYPE_CAIRO_RENDERER, NULL); + cached_renderer->is_cached_renderer = TRUE; + } + + renderer = cached_renderer; + } + else + { + renderer = g_object_new (PANGO2_TYPE_CAIRO_RENDERER, NULL); + } + + return renderer; +} + +static void +release_renderer (Pango2CairoRenderer *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 (Pango2CairoRenderer *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 (Pango2CairoRenderer *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 +_pango2_cairo_do_glyph_string (cairo_t *cr, + Pango2Font *font, + Pango2GlyphString *glyphs, + gboolean do_path) +{ + Pango2CairoRenderer *crenderer = acquire_renderer (); + Pango2Renderer *renderer = (Pango2Renderer *) 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. + */ + + pango2_renderer_activate (renderer); + + pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_FOREGROUND, NULL); + pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_BACKGROUND, NULL); + pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_UNDERLINE, NULL); + pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_STRIKETHROUGH, NULL); + pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_OVERLINE, NULL); + } + + pango2_renderer_draw_glyphs (renderer, font, glyphs, 0, 0); + + if (!do_path) + { + pango2_renderer_deactivate (renderer); + } + + restore_current_point (crenderer); + + release_renderer (crenderer); +} + +static void +_pango2_cairo_do_run (cairo_t *cr, + const char *text, + Pango2Run *run, + gboolean do_path) +{ + Pango2CairoRenderer *crenderer = acquire_renderer (); + Pango2Renderer *renderer = (Pango2Renderer *) 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. + */ + + pango2_renderer_activate (renderer); + + pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_FOREGROUND, NULL); + pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_BACKGROUND, NULL); + pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_UNDERLINE, NULL); + pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_STRIKETHROUGH, NULL); + pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_OVERLINE, NULL); + } + + pango2_renderer_draw_run (renderer, text, run, 0, 0); + + if (!do_path) + pango2_renderer_deactivate (renderer); + + restore_current_point (crenderer); + + release_renderer (crenderer); +} + +static void +_pango2_cairo_do_line (cairo_t *cr, + Pango2Line *line, + gboolean do_path) +{ + Pango2CairoRenderer *crenderer = acquire_renderer (); + Pango2Renderer *renderer = (Pango2Renderer *) crenderer; + + crenderer->cr = cr; + crenderer->do_path = do_path; + save_current_point (crenderer); + + pango2_renderer_draw_line (renderer, line, 0, 0); + + restore_current_point (crenderer); + + release_renderer (crenderer); +} + +static void +_pango2_cairo_do_lines (cairo_t *cr, + Pango2Lines *lines, + gboolean do_path) +{ + Pango2CairoRenderer *crenderer = acquire_renderer (); + Pango2Renderer *renderer = (Pango2Renderer *) crenderer; + + crenderer->cr = cr; + crenderer->do_path = do_path; + save_current_point (crenderer); + + pango2_renderer_draw_lines (renderer, lines, 0, 0); + + restore_current_point (crenderer); + + release_renderer (crenderer); +} + +static void +_pango2_cairo_do_layout (cairo_t *cr, + Pango2Layout *layout, + gboolean do_path) +{ + Pango2CairoRenderer *crenderer = acquire_renderer (); + Pango2Renderer *renderer = (Pango2Renderer *) crenderer; + + crenderer->cr = cr; + crenderer->do_path = do_path; + save_current_point (crenderer); + + pango2_renderer_draw_lines (renderer, pango2_layout_get_lines (layout), 0, 0); + + restore_current_point (crenderer); + + release_renderer (crenderer); +} + +/* public wrapper of above to show or append path */ + + +/** + * pango2_cairo_show_glyph_string: + * @cr: a Cairo context + * @font: a `Pango2Font` from a `Pango2CairoFontMap` + * @glyphs: a `Pango2GlyphString` + * + * 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. + */ +void +pango2_cairo_show_glyph_string (cairo_t *cr, + Pango2Font *font, + Pango2GlyphString *glyphs) +{ + g_return_if_fail (cr != NULL); + g_return_if_fail (glyphs != NULL); + + _pango2_cairo_do_glyph_string (cr, font, glyphs, FALSE); +} + +/** + * pango2_cairo_show_run: + * @cr: a Cairo context + * @text: the UTF-8 text that @run refers to + * @run: a `Pango2Run` + * + * Draws the glyphs in @run 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 [func@Pango2.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 `run->item->offset`. + */ +void +pango2_cairo_show_run (cairo_t *cr, + const char *text, + Pango2Run *run) +{ + g_return_if_fail (cr != NULL); + g_return_if_fail (text != NULL); + g_return_if_fail (run != NULL); + + _pango2_cairo_do_run (cr, text, run, FALSE); +} + +/** + * pango2_cairo_show_line: + * @cr: a Cairo context + * @line: a `Pango2Line` + * + * Draws a `Pango2Line` 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. + */ +void +pango2_cairo_show_line (cairo_t *cr, + Pango2Line *line) +{ + g_return_if_fail (cr != NULL); + g_return_if_fail (line != NULL); + + _pango2_cairo_do_line (cr, line, FALSE); +} + +/** + * pango2_cairo_show_lines: + * @cr: a Cairo context + * @lines: a `Pango2Lines` object + * + * Draws a `Pango2Lines` object in the specified cairo context. + * + * The top-left corner of the `Pango2Lines` will be drawn + * at the current point of the cairo context. + */ +void +pango2_cairo_show_lines (cairo_t *cr, + Pango2Lines *lines) +{ + g_return_if_fail (cr != NULL); + g_return_if_fail (lines != NULL); + + _pango2_cairo_do_lines (cr, lines, FALSE); +} + +/** + * pango2_cairo_show_layout: + * @cr: a Cairo context + * @layout: a Pango2 layout + * + * Draws a `Pango2Layout` in the specified cairo context. + * + * The top-left corner of the `Pango2Layout` will be drawn + * at the current point of the cairo context. + */ +void +pango2_cairo_show_layout (cairo_t *cr, + Pango2Layout *layout) +{ + g_return_if_fail (cr != NULL); + g_return_if_fail (PANGO2_IS_LAYOUT (layout)); + + _pango2_cairo_do_layout (cr, layout, FALSE); +} + +/** + * pango2_cairo_glyph_string_path: + * @cr: a Cairo context + * @font: a `Pango2Font` from a `Pango2CairoFontMap` + * @glyphs: a `Pango2GlyphString` + * + * 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. + */ +void +pango2_cairo_glyph_string_path (cairo_t *cr, + Pango2Font *font, + Pango2GlyphString *glyphs) +{ + g_return_if_fail (cr != NULL); + g_return_if_fail (glyphs != NULL); + + _pango2_cairo_do_glyph_string (cr, font, glyphs, TRUE); +} + +/** + * pango2_cairo_run_path: + * @cr: a Cairo context + * @text: the UTF-8 text that @run refers to + * @run: a `Pango2Run` + * + * Adds the text in `Pango2Run` 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. + */ +void +pango2_cairo_run_path (cairo_t *cr, + const char *text, + Pango2Run *run) +{ + _pango2_cairo_do_run (cr, text, run, TRUE); +} + +/** + * pango2_cairo_line_path: + * @cr: a Cairo context + * @line: a `Pango2Line` + * + * Adds the text in `Pango2Line` 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. + */ +void +pango2_cairo_line_path (cairo_t *cr, + Pango2Line *line) +{ + g_return_if_fail (cr != NULL); + + _pango2_cairo_do_line (cr, line, TRUE); +} + +/** + * pango2_cairo_layout_path: + * @cr: a Cairo context + * @layout: a Pango2 layout + * + * Adds the text in a `Pango2Layout` to the current path in the + * specified cairo context. + * + * The top-left corner of the `Pango2Layout` will be at the + * current point of the cairo context. + */ +void +pango2_cairo_layout_path (cairo_t *cr, + Pango2Layout *layout) +{ + g_return_if_fail (cr != NULL); + g_return_if_fail (PANGO2_IS_LAYOUT (layout)); + + _pango2_cairo_do_layout (cr, layout, TRUE); +} + +/** + * pango2_cairo_lines_path: + * @cr: a Cairo context + * @lines: a `Pango2Lines` object + * + * Adds the text in a `Pango2Lines` to the current path in the + * specified cairo context. + * + * The top-left corner of the `Pango2Layout` will be at the + * current point of the cairo context. + */ +void +pango2_cairo_lines_path (cairo_t *cr, + Pango2Lines *lines) +{ + g_return_if_fail (cr != NULL); + g_return_if_fail (PANGO2_IS_LINES (lines)); + + _pango2_cairo_do_lines (cr, lines, TRUE); +} |