/* cairo - a vector graphics library with display and print output * * Copyright © 2002 University of Southern California * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. * * The Original Code is the cairo graphics library. * * The Initial Developer of the Original Code is University of Southern * California. * * Contributor(s): * Carl D. Worth */ #include #include #include "cairoint.h" static cairo_status_t _cairo_gstate_clip_and_composite_trapezoids (cairo_gstate_t *gstate, cairo_pattern_t *src, cairo_operator_t operator, cairo_surface_t *dst, cairo_traps_t *traps); static cairo_status_t _cairo_gstate_ensure_font (cairo_gstate_t *gstate); static void _cairo_gstate_unset_font (cairo_gstate_t *gstate); cairo_gstate_t * _cairo_gstate_create () { cairo_gstate_t *gstate; gstate = malloc (sizeof (cairo_gstate_t)); if (gstate) _cairo_gstate_init (gstate); return gstate; } void _cairo_gstate_init (cairo_gstate_t *gstate) { gstate->operator = CAIRO_GSTATE_OPERATOR_DEFAULT; gstate->tolerance = CAIRO_GSTATE_TOLERANCE_DEFAULT; gstate->line_width = CAIRO_GSTATE_LINE_WIDTH_DEFAULT; gstate->line_cap = CAIRO_GSTATE_LINE_CAP_DEFAULT; gstate->line_join = CAIRO_GSTATE_LINE_JOIN_DEFAULT; gstate->miter_limit = CAIRO_GSTATE_MITER_LIMIT_DEFAULT; gstate->fill_rule = CAIRO_GSTATE_FILL_RULE_DEFAULT; gstate->dash = NULL; gstate->num_dashes = 0; gstate->dash_offset = 0.0; gstate->font_family = NULL; gstate->font_slant = CAIRO_FONT_SLANT_DEFAULT; gstate->font_weight = CAIRO_FONT_WEIGHT_DEFAULT; gstate->font = NULL; gstate->surface = NULL; gstate->clip.region = NULL; gstate->clip.surface = NULL; gstate->pattern = _cairo_pattern_create_solid (0.0, 0.0, 0.0); gstate->alpha = 1.0; gstate->pixels_per_inch = CAIRO_GSTATE_PIXELS_PER_INCH_DEFAULT; _cairo_gstate_default_matrix (gstate); _cairo_path_init (&gstate->path); _cairo_pen_init_empty (&gstate->pen_regular); gstate->next = NULL; } cairo_status_t _cairo_gstate_init_copy (cairo_gstate_t *gstate, cairo_gstate_t *other) { cairo_status_t status; cairo_gstate_t *next; /* Copy all members, but don't smash the next pointer */ next = gstate->next; *gstate = *other; gstate->next = next; /* Now fix up pointer data that needs to be cloned/referenced */ if (other->dash) { gstate->dash = malloc (other->num_dashes * sizeof (double)); if (gstate->dash == NULL) return CAIRO_STATUS_NO_MEMORY; memcpy (gstate->dash, other->dash, other->num_dashes * sizeof (double)); } if (other->font_family) { gstate->font_family = strdup (other->font_family); if (!gstate->font_family) goto CLEANUP_DASH; } if (other->font) { gstate->font = other->font; cairo_font_reference (gstate->font); } if (other->clip.region) { gstate->clip.region = pixman_region_create (); pixman_region_copy (gstate->clip.region, other->clip.region); } cairo_surface_reference (gstate->surface); cairo_surface_reference (gstate->clip.surface); cairo_pattern_reference (gstate->pattern); status = _cairo_path_init_copy (&gstate->path, &other->path); if (status) goto CLEANUP_FONT; status = _cairo_pen_init_copy (&gstate->pen_regular, &other->pen_regular); if (status) goto CLEANUP_PATH; return status; CLEANUP_PATH: _cairo_path_fini (&gstate->path); CLEANUP_FONT: cairo_font_destroy (gstate->font); gstate->font = NULL; if (gstate->font_family) { free (gstate->font_family); gstate->font_family = NULL; } CLEANUP_DASH: free (gstate->dash); gstate->dash = NULL; return CAIRO_STATUS_NO_MEMORY; } void _cairo_gstate_fini (cairo_gstate_t *gstate) { if (gstate->font_family) free (gstate->font_family); if (gstate->font) cairo_font_destroy (gstate->font); if (gstate->surface) cairo_surface_destroy (gstate->surface); gstate->surface = NULL; if (gstate->clip.surface) cairo_surface_destroy (gstate->clip.surface); gstate->clip.surface = NULL; if (gstate->clip.region) pixman_region_destroy (gstate->clip.region); gstate->clip.region = NULL; cairo_pattern_destroy (gstate->pattern); _cairo_matrix_fini (&gstate->font_matrix); _cairo_matrix_fini (&gstate->ctm); _cairo_matrix_fini (&gstate->ctm_inverse); _cairo_path_fini (&gstate->path); _cairo_pen_fini (&gstate->pen_regular); if (gstate->dash) { free (gstate->dash); gstate->dash = NULL; } } void _cairo_gstate_destroy (cairo_gstate_t *gstate) { _cairo_gstate_fini (gstate); free (gstate); } cairo_gstate_t* _cairo_gstate_clone (cairo_gstate_t *gstate) { cairo_status_t status; cairo_gstate_t *clone; clone = malloc (sizeof (cairo_gstate_t)); if (clone) { status = _cairo_gstate_init_copy (clone, gstate); if (status) { free (clone); return NULL; } } clone->next = NULL; return clone; } cairo_status_t _cairo_gstate_copy (cairo_gstate_t *dest, cairo_gstate_t *src) { cairo_status_t status; cairo_gstate_t *next; /* Preserve next pointer over fini/init */ next = dest->next; _cairo_gstate_fini (dest); status = _cairo_gstate_init_copy (dest, src); dest->next = next; return status; } /* Push rendering off to an off-screen group. */ /* XXX: Rethinking this API cairo_status_t _cairo_gstate_begin_group (cairo_gstate_t *gstate) { Pixmap pix; cairo_color_t clear; unsigned int width, height; gstate->parent_surface = gstate->surface; width = _cairo_surface_get_width (gstate->surface); height = _cairo_surface_get_height (gstate->surface); pix = XCreatePixmap (gstate->dpy, _cairo_surface_get_drawable (gstate->surface), width, height, _cairo_surface_get_depth (gstate->surface)); if (pix == 0) return CAIRO_STATUS_NO_MEMORY; gstate->surface = cairo_surface_create (gstate->dpy); if (gstate->surface == NULL) return CAIRO_STATUS_NO_MEMORY; _cairo_surface_set_drawableWH (gstate->surface, pix, width, height); _cairo_color_init (&clear); _cairo_color_set_alpha (&clear, 0); status = _cairo_surface_fill_rectangle (gstate->surface, CAIRO_OPERATOR_SRC, &clear, 0, 0, _cairo_surface_get_width (gstate->surface), _cairo_surface_get_height (gstate->surface)); if (status) return status; return CAIRO_STATUS_SUCCESS; } */ /* Complete the current offscreen group, composing its contents onto the parent surface. */ /* XXX: Rethinking this API cairo_status_t _cairo_gstate_end_group (cairo_gstate_t *gstate) { Pixmap pix; cairo_color_t mask_color; cairo_surface_t mask; if (gstate->parent_surface == NULL) return CAIRO_STATUS_INVALID_POP_GROUP; _cairo_surface_init (&mask, gstate->dpy); _cairo_color_init (&mask_color); _cairo_color_set_alpha (&mask_color, gstate->alpha); _cairo_surface_set_solid_color (&mask, &mask_color); * XXX: This could be made much more efficient by using _cairo_surface_get_damaged_width/Height if cairo_surface_t actually kept track of such informaton. * _cairo_surface_composite (gstate->operator, gstate->surface, mask, gstate->parent_surface, 0, 0, 0, 0, 0, 0, _cairo_surface_get_width (gstate->surface), _cairo_surface_get_height (gstate->surface)); _cairo_surface_fini (&mask); pix = _cairo_surface_get_drawable (gstate->surface); XFreePixmap (gstate->dpy, pix); cairo_surface_destroy (gstate->surface); gstate->surface = gstate->parent_surface; gstate->parent_surface = NULL; return CAIRO_STATUS_SUCCESS; } */ cairo_status_t _cairo_gstate_set_target_surface (cairo_gstate_t *gstate, cairo_surface_t *surface) { double scale; _cairo_gstate_unset_font (gstate); if (gstate->surface) cairo_surface_destroy (gstate->surface); gstate->surface = surface; /* Sometimes the user wants to return to having no target surface, * (just like after cairo_create). This can be useful for forcing * the old surface to be destroyed. */ if (surface == NULL) return CAIRO_STATUS_SUCCESS; cairo_surface_reference (gstate->surface); scale = _cairo_surface_pixels_per_inch (surface) / gstate->pixels_per_inch; _cairo_gstate_scale (gstate, scale, scale); gstate->pixels_per_inch = _cairo_surface_pixels_per_inch (surface); return CAIRO_STATUS_SUCCESS; } /* XXX: Need to decide the memory mangement semantics of this function. Should it reference the surface again? */ cairo_surface_t * _cairo_gstate_current_target_surface (cairo_gstate_t *gstate) { if (gstate == NULL) return NULL; /* XXX: Do we want this? if (gstate->surface) _cairo_surface_reference (gstate->surface); */ return gstate->surface; } cairo_status_t _cairo_gstate_set_pattern (cairo_gstate_t *gstate, cairo_pattern_t *pattern) { if (pattern == NULL) return CAIRO_STATUS_NULL_POINTER; if (gstate->pattern) cairo_pattern_destroy (gstate->pattern); gstate->pattern = pattern; cairo_pattern_reference (pattern); return CAIRO_STATUS_SUCCESS; } cairo_pattern_t * _cairo_gstate_current_pattern (cairo_gstate_t *gstate) { if (gstate == NULL) return NULL; /* XXX: Do we want this? cairo_pattern_reference (gstate->pattern); */ return gstate->pattern; } cairo_status_t _cairo_gstate_set_operator (cairo_gstate_t *gstate, cairo_operator_t operator) { gstate->operator = operator; return CAIRO_STATUS_SUCCESS; } cairo_operator_t _cairo_gstate_current_operator (cairo_gstate_t *gstate) { return gstate->operator; } cairo_status_t _cairo_gstate_set_rgb_color (cairo_gstate_t *gstate, double red, double green, double blue) { cairo_pattern_destroy (gstate->pattern); gstate->pattern = _cairo_pattern_create_solid (red, green, blue); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_current_rgb_color (cairo_gstate_t *gstate, double *red, double *green, double *blue) { return _cairo_pattern_get_rgb (gstate->pattern, red, green, blue); } cairo_status_t _cairo_gstate_set_tolerance (cairo_gstate_t *gstate, double tolerance) { gstate->tolerance = tolerance; return CAIRO_STATUS_SUCCESS; } double _cairo_gstate_current_tolerance (cairo_gstate_t *gstate) { return gstate->tolerance; } cairo_status_t _cairo_gstate_set_alpha (cairo_gstate_t *gstate, double alpha) { gstate->alpha = alpha; return CAIRO_STATUS_SUCCESS; } double _cairo_gstate_current_alpha (cairo_gstate_t *gstate) { return gstate->alpha; } cairo_status_t _cairo_gstate_set_fill_rule (cairo_gstate_t *gstate, cairo_fill_rule_t fill_rule) { gstate->fill_rule = fill_rule; return CAIRO_STATUS_SUCCESS; } cairo_fill_rule_t _cairo_gstate_current_fill_rule (cairo_gstate_t *gstate) { return gstate->fill_rule; } cairo_status_t _cairo_gstate_set_line_width (cairo_gstate_t *gstate, double width) { gstate->line_width = width; return CAIRO_STATUS_SUCCESS; } double _cairo_gstate_current_line_width (cairo_gstate_t *gstate) { return gstate->line_width; } cairo_status_t _cairo_gstate_set_line_cap (cairo_gstate_t *gstate, cairo_line_cap_t line_cap) { gstate->line_cap = line_cap; return CAIRO_STATUS_SUCCESS; } cairo_line_cap_t _cairo_gstate_current_line_cap (cairo_gstate_t *gstate) { return gstate->line_cap; } cairo_status_t _cairo_gstate_set_line_join (cairo_gstate_t *gstate, cairo_line_join_t line_join) { gstate->line_join = line_join; return CAIRO_STATUS_SUCCESS; } cairo_line_join_t _cairo_gstate_current_line_join (cairo_gstate_t *gstate) { return gstate->line_join; } cairo_status_t _cairo_gstate_set_dash (cairo_gstate_t *gstate, double *dash, int num_dashes, double offset) { if (gstate->dash) { free (gstate->dash); gstate->dash = NULL; } gstate->num_dashes = num_dashes; if (gstate->num_dashes) { gstate->dash = malloc (gstate->num_dashes * sizeof (double)); if (gstate->dash == NULL) { gstate->num_dashes = 0; return CAIRO_STATUS_NO_MEMORY; } } memcpy (gstate->dash, dash, gstate->num_dashes * sizeof (double)); gstate->dash_offset = offset; return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_set_miter_limit (cairo_gstate_t *gstate, double limit) { gstate->miter_limit = limit; return CAIRO_STATUS_SUCCESS; } double _cairo_gstate_current_miter_limit (cairo_gstate_t *gstate) { return gstate->miter_limit; } void _cairo_gstate_current_matrix (cairo_gstate_t *gstate, cairo_matrix_t *matrix) { cairo_matrix_copy (matrix, &gstate->ctm); } cairo_status_t _cairo_gstate_translate (cairo_gstate_t *gstate, double tx, double ty) { cairo_matrix_t tmp; _cairo_gstate_unset_font (gstate); _cairo_matrix_set_translate (&tmp, tx, ty); cairo_matrix_multiply (&gstate->ctm, &tmp, &gstate->ctm); _cairo_matrix_set_translate (&tmp, -tx, -ty); cairo_matrix_multiply (&gstate->ctm_inverse, &gstate->ctm_inverse, &tmp); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_scale (cairo_gstate_t *gstate, double sx, double sy) { cairo_matrix_t tmp; if (sx == 0 || sy == 0) return CAIRO_STATUS_INVALID_MATRIX; _cairo_gstate_unset_font (gstate); _cairo_matrix_set_scale (&tmp, sx, sy); cairo_matrix_multiply (&gstate->ctm, &tmp, &gstate->ctm); _cairo_matrix_set_scale (&tmp, 1/sx, 1/sy); cairo_matrix_multiply (&gstate->ctm_inverse, &gstate->ctm_inverse, &tmp); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_rotate (cairo_gstate_t *gstate, double angle) { cairo_matrix_t tmp; _cairo_gstate_unset_font (gstate); _cairo_matrix_set_rotate (&tmp, angle); cairo_matrix_multiply (&gstate->ctm, &tmp, &gstate->ctm); _cairo_matrix_set_rotate (&tmp, -angle); cairo_matrix_multiply (&gstate->ctm_inverse, &gstate->ctm_inverse, &tmp); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_concat_matrix (cairo_gstate_t *gstate, cairo_matrix_t *matrix) { cairo_matrix_t tmp; _cairo_gstate_unset_font (gstate); cairo_matrix_copy (&tmp, matrix); cairo_matrix_multiply (&gstate->ctm, &tmp, &gstate->ctm); cairo_matrix_invert (&tmp); cairo_matrix_multiply (&gstate->ctm_inverse, &gstate->ctm_inverse, &tmp); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_set_matrix (cairo_gstate_t *gstate, cairo_matrix_t *matrix) { cairo_status_t status; _cairo_gstate_unset_font (gstate); cairo_matrix_copy (&gstate->ctm, matrix); cairo_matrix_copy (&gstate->ctm_inverse, matrix); status = cairo_matrix_invert (&gstate->ctm_inverse); if (status) return status; return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_default_matrix (cairo_gstate_t *gstate) { int scale = gstate->pixels_per_inch / CAIRO_GSTATE_PIXELS_PER_INCH_DEFAULT + 0.5; if (scale == 0) scale = 1; _cairo_gstate_unset_font (gstate); cairo_matrix_set_identity (&gstate->font_matrix); cairo_matrix_set_identity (&gstate->ctm); cairo_matrix_scale (&gstate->ctm, scale, scale); cairo_matrix_copy (&gstate->ctm_inverse, &gstate->ctm); cairo_matrix_invert (&gstate->ctm_inverse); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_identity_matrix (cairo_gstate_t *gstate) { _cairo_gstate_unset_font (gstate); cairo_matrix_set_identity (&gstate->ctm); cairo_matrix_set_identity (&gstate->ctm_inverse); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_transform_point (cairo_gstate_t *gstate, double *x, double *y) { cairo_matrix_transform_point (&gstate->ctm, x, y); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_transform_distance (cairo_gstate_t *gstate, double *dx, double *dy) { cairo_matrix_transform_distance (&gstate->ctm, dx, dy); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_inverse_transform_point (cairo_gstate_t *gstate, double *x, double *y) { cairo_matrix_transform_point (&gstate->ctm_inverse, x, y); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_inverse_transform_distance (cairo_gstate_t *gstate, double *dx, double *dy) { cairo_matrix_transform_distance (&gstate->ctm_inverse, dx, dy); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_new_path (cairo_gstate_t *gstate) { _cairo_path_fini (&gstate->path); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_move_to (cairo_gstate_t *gstate, double x, double y) { cairo_point_t point; cairo_matrix_transform_point (&gstate->ctm, &x, &y); point.x = _cairo_fixed_from_double (x); point.y = _cairo_fixed_from_double (y); return _cairo_path_move_to (&gstate->path, &point); } cairo_status_t _cairo_gstate_line_to (cairo_gstate_t *gstate, double x, double y) { cairo_point_t point; cairo_matrix_transform_point (&gstate->ctm, &x, &y); point.x = _cairo_fixed_from_double (x); point.y = _cairo_fixed_from_double (y); return _cairo_path_line_to (&gstate->path, &point); } cairo_status_t _cairo_gstate_curve_to (cairo_gstate_t *gstate, double x0, double y0, double x1, double y1, double x2, double y2) { cairo_point_t p0, p1, p2; cairo_matrix_transform_point (&gstate->ctm, &x0, &y0); cairo_matrix_transform_point (&gstate->ctm, &x1, &y1); cairo_matrix_transform_point (&gstate->ctm, &x2, &y2); p0.x = _cairo_fixed_from_double (x0); p0.y = _cairo_fixed_from_double (y0); p1.x = _cairo_fixed_from_double (x1); p1.y = _cairo_fixed_from_double (y1); p2.x = _cairo_fixed_from_double (x2); p2.y = _cairo_fixed_from_double (y2); return _cairo_path_curve_to (&gstate->path, &p0, &p1, &p2); } /* Spline deviation from the circle in radius would be given by: error = sqrt (x**2 + y**2) - 1 A simpler error function to work with is: e = x**2 + y**2 - 1 From "Good approximation of circles by curvature-continuous Bezier curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric Design 8 (1990) 22-41, we learn: abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4) and abs (error) =~ 1/2 * e Of course, this error value applies only for the particular spline approximation that is used in _cairo_gstate_arc_segment. */ static double _arc_error_normalized (double angle) { return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2); } static double _arc_max_angle_for_tolerance_normalized (double tolerance) { double angle, error; int i; /* Use table lookup to reduce search time in most cases. */ struct { double angle; double error; } table[] = { { M_PI / 1.0, 0.0185185185185185036127 }, { M_PI / 2.0, 0.000272567143730179811158 }, { M_PI / 3.0, 2.38647043651461047433e-05 }, { M_PI / 4.0, 4.2455377443222443279e-06 }, { M_PI / 5.0, 1.11281001494389081528e-06 }, { M_PI / 6.0, 3.72662000942734705475e-07 }, { M_PI / 7.0, 1.47783685574284411325e-07 }, { M_PI / 8.0, 6.63240432022601149057e-08 }, { M_PI / 9.0, 3.2715520137536980553e-08 }, { M_PI / 10.0, 1.73863223499021216974e-08 }, { M_PI / 11.0, 9.81410988043554039085e-09 }, }; int table_size = (sizeof (table) / sizeof (table[0])); for (i = 0; i < table_size; i++) if (table[i].error < tolerance) return table[i].angle; ++i; do { angle = M_PI / i++; error = _arc_error_normalized (angle); } while (error > tolerance); return angle; } static int _cairo_gstate_arc_segments_needed (cairo_gstate_t *gstate, double angle, double radius) { double l1, l2, lmax; double max_angle; _cairo_matrix_compute_eigen_values (&gstate->ctm, &l1, &l2); l1 = fabs (l1); l2 = fabs (l2); if (l1 > l2) lmax = l1; else lmax = l2; max_angle = _arc_max_angle_for_tolerance_normalized (gstate->tolerance / (radius * lmax)); return (int) ceil (angle / max_angle); } /* We want to draw a single spline approximating a circular arc radius R from angle A to angle B. Since we want a symmetric spline that matches the endpoints of the arc in position and slope, we know that the spline control points must be: (R * cos(A), R * sin(A)) (R * cos(A) - h * sin(A), R * sin(A) + h * cos (A)) (R * cos(B) + h * sin(B), R * sin(B) - h * cos (B)) (R * cos(B), R * sin(B)) for some value of h. "Approximation of circular arcs by cubic poynomials", Michael Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides various values of h along with error analysis for each. From that paper, a very practical value of h is: h = 4/3 * tan(angle/4) This value does not give the spline with minimal error, but it does provide a very good approximation, (6th-order convergence), and the error expression is quite simple, (see the comment for _arc_error_normalized). */ static cairo_status_t _cairo_gstate_arc_segment (cairo_gstate_t *gstate, double xc, double yc, double radius, double angle_A, double angle_B) { cairo_status_t status; double r_sin_A, r_cos_A; double r_sin_B, r_cos_B; double h; r_sin_A = radius * sin (angle_A); r_cos_A = radius * cos (angle_A); r_sin_B = radius * sin (angle_B); r_cos_B = radius * cos (angle_B); h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0); status = _cairo_gstate_curve_to (gstate, xc + r_cos_A - h * r_sin_A, yc + r_sin_A + h * r_cos_A, xc + r_cos_B + h * r_sin_B, yc + r_sin_B - h * r_cos_B, xc + r_cos_B, yc + r_sin_B); if (status) return status; return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_gstate_arc_dir (cairo_gstate_t *gstate, double xc, double yc, double radius, double angle_min, double angle_max, cairo_direction_t dir) { cairo_status_t status; while (angle_max - angle_min > 4 * M_PI) angle_max -= 2 * M_PI; /* Recurse if drawing arc larger than pi */ if (angle_max - angle_min > M_PI) { double angle_mid = angle_min + (angle_max - angle_min) / 2.0; /* XXX: Something tells me this block could be condensed. */ if (dir == CAIRO_DIRECTION_FORWARD) { status = _cairo_gstate_arc_dir (gstate, xc, yc, radius, angle_min, angle_mid, dir); if (status) return status; status = _cairo_gstate_arc_dir (gstate, xc, yc, radius, angle_mid, angle_max, dir); if (status) return status; } else { status = _cairo_gstate_arc_dir (gstate, xc, yc, radius, angle_mid, angle_max, dir); if (status) return status; status = _cairo_gstate_arc_dir (gstate, xc, yc, radius, angle_min, angle_mid, dir); if (status) return status; } } else { int i, segments; double angle, angle_step; segments = _cairo_gstate_arc_segments_needed (gstate, angle_max - angle_min, radius); angle_step = (angle_max - angle_min) / (double) segments; if (dir == CAIRO_DIRECTION_FORWARD) { angle = angle_min; } else { angle = angle_max; angle_step = - angle_step; } for (i = 0; i < segments; i++, angle += angle_step) { _cairo_gstate_arc_segment (gstate, xc, yc, radius, angle, angle + angle_step); } } return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_arc (cairo_gstate_t *gstate, double xc, double yc, double radius, double angle1, double angle2) { cairo_status_t status; if (radius <= 0.0) return CAIRO_STATUS_SUCCESS; while (angle2 < angle1) angle2 += 2 * M_PI; status = _cairo_gstate_line_to (gstate, xc + radius * cos (angle1), yc + radius * sin (angle1)); if (status) return status; status = _cairo_gstate_arc_dir (gstate, xc, yc, radius, angle1, angle2, CAIRO_DIRECTION_FORWARD); if (status) return status; return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_arc_negative (cairo_gstate_t *gstate, double xc, double yc, double radius, double angle1, double angle2) { cairo_status_t status; if (radius <= 0.0) return CAIRO_STATUS_SUCCESS; while (angle2 > angle1) angle2 -= 2 * M_PI; status = _cairo_gstate_line_to (gstate, xc + radius * cos (angle1), yc + radius * sin (angle1)); if (status) return status; status = _cairo_gstate_arc_dir (gstate, xc, yc, radius, angle2, angle1, CAIRO_DIRECTION_REVERSE); if (status) return status; return CAIRO_STATUS_SUCCESS; } /* XXX: NYI cairo_status_t _cairo_gstate_arc_to (cairo_gstate_t *gstate, double x1, double y1, double x2, double y2, double radius) { } */ cairo_status_t _cairo_gstate_rel_move_to (cairo_gstate_t *gstate, double dx, double dy) { cairo_distance_t distance; cairo_matrix_transform_distance (&gstate->ctm, &dx, &dy); distance.dx = _cairo_fixed_from_double (dx); distance.dy = _cairo_fixed_from_double (dy); return _cairo_path_rel_move_to (&gstate->path, &distance); } cairo_status_t _cairo_gstate_rel_line_to (cairo_gstate_t *gstate, double dx, double dy) { cairo_distance_t distance; cairo_matrix_transform_distance (&gstate->ctm, &dx, &dy); distance.dx = _cairo_fixed_from_double (dx); distance.dy = _cairo_fixed_from_double (dy); return _cairo_path_rel_line_to (&gstate->path, &distance); } cairo_status_t _cairo_gstate_rel_curve_to (cairo_gstate_t *gstate, double dx0, double dy0, double dx1, double dy1, double dx2, double dy2) { cairo_distance_t distance[3]; cairo_matrix_transform_distance (&gstate->ctm, &dx0, &dy0); cairo_matrix_transform_distance (&gstate->ctm, &dx1, &dy1); cairo_matrix_transform_distance (&gstate->ctm, &dx2, &dy2); distance[0].dx = _cairo_fixed_from_double (dx0); distance[0].dy = _cairo_fixed_from_double (dy0); distance[1].dx = _cairo_fixed_from_double (dx1); distance[1].dy = _cairo_fixed_from_double (dy1); distance[2].dx = _cairo_fixed_from_double (dx2); distance[2].dy = _cairo_fixed_from_double (dy2); return _cairo_path_rel_curve_to (&gstate->path, &distance[0], &distance[1], &distance[2]); } /* XXX: NYI cairo_status_t _cairo_gstate_stroke_path (cairo_gstate_t *gstate) { cairo_status_t status; _cairo_pen_init (&gstate); return CAIRO_STATUS_SUCCESS; } */ cairo_status_t _cairo_gstate_close_path (cairo_gstate_t *gstate) { return _cairo_path_close_path (&gstate->path); } cairo_status_t _cairo_gstate_current_point (cairo_gstate_t *gstate, double *x_ret, double *y_ret) { cairo_status_t status; cairo_point_t point; double x, y; status = _cairo_path_current_point (&gstate->path, &point); if (status == CAIRO_STATUS_NO_CURRENT_POINT) { x = 0.0; y = 0.0; } else { x = _cairo_fixed_to_double (point.x); y = _cairo_fixed_to_double (point.y); cairo_matrix_transform_point (&gstate->ctm_inverse, &x, &y); } if (x_ret) *x_ret = x; if (y_ret) *y_ret = y; return CAIRO_STATUS_SUCCESS; } typedef struct gstate_path_interpreter { cairo_matrix_t ctm_inverse; double tolerance; cairo_point_t current_point; cairo_move_to_func_t *move_to; cairo_line_to_func_t *line_to; cairo_curve_to_func_t *curve_to; cairo_close_path_func_t *close_path; void *closure; } gpi_t; static cairo_status_t _gpi_move_to (void *closure, cairo_point_t *point) { gpi_t *gpi = closure; double x, y; x = _cairo_fixed_to_double (point->x); y = _cairo_fixed_to_double (point->y); cairo_matrix_transform_point (&gpi->ctm_inverse, &x, &y); gpi->move_to (gpi->closure, x, y); gpi->current_point = *point; return CAIRO_STATUS_SUCCESS; } static cairo_status_t _gpi_line_to (void *closure, cairo_point_t *point) { gpi_t *gpi = closure; double x, y; x = _cairo_fixed_to_double (point->x); y = _cairo_fixed_to_double (point->y); cairo_matrix_transform_point (&gpi->ctm_inverse, &x, &y); gpi->line_to (gpi->closure, x, y); gpi->current_point = *point; return CAIRO_STATUS_SUCCESS; } static cairo_status_t _gpi_curve_to (void *closure, cairo_point_t *p1, cairo_point_t *p2, cairo_point_t *p3) { gpi_t *gpi = closure; cairo_status_t status; cairo_spline_t spline; double x1, y1, x2, y2, x3, y3; if (gpi->curve_to) { x1 = _cairo_fixed_to_double (p1->x); y1 = _cairo_fixed_to_double (p1->y); cairo_matrix_transform_point (&gpi->ctm_inverse, &x1, &y1); x2 = _cairo_fixed_to_double (p2->x); y2 = _cairo_fixed_to_double (p2->y); cairo_matrix_transform_point (&gpi->ctm_inverse, &x2, &y2); x3 = _cairo_fixed_to_double (p3->x); y3 = _cairo_fixed_to_double (p3->y); cairo_matrix_transform_point (&gpi->ctm_inverse, &x3, &y3); gpi->curve_to (gpi->closure, x1, y1, x2, y2, x3, y3); } else { cairo_point_t *p0 = &gpi->current_point; int i; double x, y; status = _cairo_spline_init (&spline, p0, p1, p2, p3); if (status == CAIRO_INT_STATUS_DEGENERATE) return CAIRO_STATUS_SUCCESS; status = _cairo_spline_decompose (&spline, gpi->tolerance); if (status) return status; for (i=1; i < spline.num_points; i++) { x = _cairo_fixed_to_double (spline.points[i].x); y = _cairo_fixed_to_double (spline.points[i].y); cairo_matrix_transform_point (&gpi->ctm_inverse, &x, &y); gpi->line_to (gpi->closure, x, y); } } gpi->current_point = *p3; return CAIRO_STATUS_SUCCESS; } static cairo_status_t _gpi_close_path (void *closure) { gpi_t *gpi = closure; gpi->close_path (gpi->closure); gpi->current_point.x = 0; gpi->current_point.y = 0; return CAIRO_STATUS_SUCCESS; } /* It's OK for curve_path to be NULL. In that case, all curves in the path will be decomposed into one or more calls to the line_to function, (according to the current tolerance). */ cairo_status_t _cairo_gstate_interpret_path (cairo_gstate_t *gstate, cairo_move_to_func_t *move_to, cairo_line_to_func_t *line_to, cairo_curve_to_func_t *curve_to, cairo_close_path_func_t *close_path, void *closure) { cairo_path_t path; gpi_t gpi; /* Anything we want from gstate must be copied. We must not retain pointers into gstate. */ _cairo_path_init_copy (&path, &gstate->path); cairo_matrix_copy (&gpi.ctm_inverse, &gstate->ctm_inverse); gpi.tolerance = gstate->tolerance; gpi.move_to = move_to; gpi.line_to = line_to; gpi.curve_to = curve_to; gpi.close_path = close_path; gpi.closure = closure; gpi.current_point.x = 0; gpi.current_point.y = 0; return _cairo_path_interpret (&path, CAIRO_DIRECTION_FORWARD, _gpi_move_to, _gpi_line_to, _gpi_curve_to, _gpi_close_path, &gpi); } /* This function modifies the pattern and the state of the pattern surface it may contain. The pattern surface will be restored to its orignal state when the pattern is destroyed. The appropriate way is to pass a copy of the original pattern to this function just before the pattern should be used and destroy the copy when done. */ static void _cairo_gstate_create_pattern (cairo_gstate_t *gstate, cairo_pattern_t *pattern) { if (pattern->type == CAIRO_PATTERN_LINEAR || pattern->type == CAIRO_PATTERN_RADIAL) { if (pattern->n_stops < 2) { pattern->type = CAIRO_PATTERN_SOLID; if (pattern->n_stops) { cairo_color_stop_t *stop = pattern->stops; _cairo_color_set_rgb (&pattern->color, (double) stop->color_char[0] / 0xff, (double) stop->color_char[1] / 0xff, (double) stop->color_char[2] / 0xff); _cairo_color_set_alpha (&pattern->color, (double) stop->color_char[3] / 0xff); } } } _cairo_pattern_set_alpha (pattern, gstate->alpha); _cairo_pattern_transform (pattern, &gstate->ctm_inverse); } cairo_status_t _cairo_gstate_stroke (cairo_gstate_t *gstate) { cairo_status_t status; cairo_traps_t traps; if (gstate->line_width <= 0.0) return CAIRO_STATUS_SUCCESS; _cairo_pen_init (&gstate->pen_regular, gstate->line_width / 2.0, gstate); _cairo_traps_init (&traps); status = _cairo_path_stroke_to_traps (&gstate->path, gstate, &traps); if (status) { _cairo_traps_fini (&traps); return status; } _cairo_gstate_clip_and_composite_trapezoids (gstate, gstate->pattern, gstate->operator, gstate->surface, &traps); _cairo_traps_fini (&traps); _cairo_gstate_new_path (gstate); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_in_stroke (cairo_gstate_t *gstate, double x, double y, cairo_bool_t *inside_ret) { cairo_status_t status = CAIRO_STATUS_SUCCESS; cairo_traps_t traps; cairo_matrix_transform_point (&gstate->ctm, &x, &y); _cairo_pen_init (&gstate->pen_regular, gstate->line_width / 2.0, gstate); _cairo_traps_init (&traps); status = _cairo_path_stroke_to_traps (&gstate->path, gstate, &traps); if (status) goto BAIL; *inside_ret = _cairo_traps_contain (&traps, x, y); BAIL: _cairo_traps_fini (&traps); return status; } /* XXX We currently have a confusing mix of boxes and rectangles as * exemplified by this function. A cairo_box_t is a rectangular area * represented by the coordinates of the upper left and lower right * corners, expressed in fixed point numbers. A cairo_rectangle_t is * also a rectangular area, but represented by the upper left corner * and the width and the height, as integer numbers. * * This function converts a cairo_box_t to a cairo_rectangle_t by * increasing the area to the nearest integer coordinates. We should * standardize on cairo_rectangle_t and cairo_rectangle_fixed_t, and * this function could be renamed to the more reasonable * _cairo_rectangle_fixed_round. */ static void _cairo_box_round_to_rectangle (cairo_box_t *box, cairo_rectangle_t *rectangle) { rectangle->x = _cairo_fixed_integer_floor (box->p1.x); rectangle->y = _cairo_fixed_integer_floor (box->p1.y); rectangle->width = _cairo_fixed_integer_ceil (box->p2.x) - rectangle->x; rectangle->height = _cairo_fixed_integer_ceil (box->p2.y) - rectangle->y; } static void _cairo_rectangle_intersect (cairo_rectangle_t *dest, cairo_rectangle_t *src) { cairo_rectangle_t out; out.x = MAX (dest->x, src->x); out.y = MAX (dest->y, src->y); out.width = MIN (dest->x + dest->width, src->x + src->width) - out.x; out.height = MIN (dest->y + dest->height, src->y + src->height) - out.y; if (out.width <= 0 || out.height <= 0) { dest->x = 0; dest->y = 0; dest->width = 0; dest->height = 0; } else { *dest = out; } } static int _cairo_rectangle_empty (cairo_rectangle_t *rect) { return rect->width == 0 || rect->height == 0; } static void translate_traps (cairo_traps_t *traps, int x, int y) { cairo_fixed_t xoff, yoff; cairo_trapezoid_t *t; int i; /* Ugh. The cairo_composite/(Render) interface doesn't allow an offset for the trapezoids. Need to manually shift all the coordinates to align with the offset origin of the intermediate surface. */ xoff = _cairo_fixed_from_int (x); yoff = _cairo_fixed_from_int (y); for (i = 0, t = traps->traps; i < traps->num_traps; i++, t++) { t->top += yoff; t->bottom += yoff; t->left.p1.x += xoff; t->left.p1.y += yoff; t->left.p2.x += xoff; t->left.p2.y += yoff; t->right.p1.x += xoff; t->right.p1.y += yoff; t->right.p2.x += xoff; t->right.p2.y += yoff; } } /* Warning: This call modifies the coordinates of traps */ static cairo_status_t _cairo_gstate_clip_and_composite_trapezoids (cairo_gstate_t *gstate, cairo_pattern_t *src, cairo_operator_t operator, cairo_surface_t *dst, cairo_traps_t *traps) { cairo_status_t status; cairo_pattern_t pattern; cairo_rectangle_t extents; cairo_box_t trap_extents; if (traps->num_traps == 0) return CAIRO_STATUS_SUCCESS; if (gstate->surface == NULL) return CAIRO_STATUS_NO_TARGET_SURFACE; _cairo_traps_extents (traps, &trap_extents); _cairo_box_round_to_rectangle (&trap_extents, &extents); if (gstate->clip.surface) { cairo_surface_t *intermediate; cairo_color_t empty_color; _cairo_rectangle_intersect (&extents, &gstate->clip.rect); if (_cairo_rectangle_empty (&extents)) { status = CAIRO_STATUS_SUCCESS; goto BAIL1; } translate_traps (traps, -extents.x, -extents.y); _cairo_color_init (&empty_color); _cairo_color_set_alpha (&empty_color, 0.); intermediate = _cairo_surface_create_similar_solid (gstate->clip.surface, CAIRO_FORMAT_A8, extents.width, extents.height, &empty_color); if (intermediate == NULL) { status = CAIRO_STATUS_NO_MEMORY; goto BAIL1; } _cairo_pattern_init_solid (&pattern, 1.0, 1.0, 1.0); status = _cairo_surface_composite_trapezoids (CAIRO_OPERATOR_ADD, &pattern, intermediate, extents.x, extents.y, 0, 0, extents.width, extents.height, traps->traps, traps->num_traps); _cairo_pattern_fini (&pattern); if (status) goto BAIL2; _cairo_pattern_init_for_surface (&pattern, gstate->clip.surface); status = _cairo_surface_composite (CAIRO_OPERATOR_IN, &pattern, NULL, intermediate, extents.x - gstate->clip.rect.x, extents.y - gstate->clip.rect.y, 0, 0, 0, 0, extents.width, extents.height); _cairo_pattern_fini (&pattern); if (status) goto BAIL2; _cairo_pattern_init_copy (&pattern, src); _cairo_gstate_create_pattern (gstate, &pattern); status = _cairo_surface_composite (operator, &pattern, intermediate, dst, extents.x, extents.y, 0, 0, extents.x, extents.y, extents.width, extents.height); _cairo_pattern_fini (&pattern); BAIL2: cairo_surface_destroy (intermediate); BAIL1: if (status) return status; } else { if (gstate->clip.region) { pixman_box16_t box; pixman_box16_t *intersection_extents; pixman_region16_t *rect, *intersection; box.x1 = _cairo_fixed_integer_floor (trap_extents.p1.x); box.y1 = _cairo_fixed_integer_floor (trap_extents.p1.y); box.x2 = _cairo_fixed_integer_ceil (trap_extents.p2.x); box.y2 = _cairo_fixed_integer_ceil (trap_extents.p2.y); rect = pixman_region_create_simple (&box); if (rect == NULL) goto bail1; intersection = pixman_region_create(); if (intersection == NULL) goto bail2; if (pixman_region_intersect (intersection, gstate->clip.region, rect) != PIXMAN_REGION_STATUS_SUCCESS) goto bail3; intersection_extents = pixman_region_extents (intersection); extents.x = intersection_extents->x1; extents.y = intersection_extents->y1; extents.width = intersection_extents->x2 - intersection_extents->x1; extents.height = intersection_extents->y2 - intersection_extents->y1; bail3: pixman_region_destroy (intersection); bail2: pixman_region_destroy (rect); bail1: ; } _cairo_pattern_init_copy (&pattern, src); _cairo_gstate_create_pattern (gstate, &pattern); status = _cairo_surface_composite_trapezoids (gstate->operator, &pattern, dst, extents.x, extents.y, extents.x, extents.y, extents.width, extents.height, traps->traps, traps->num_traps); _cairo_pattern_fini (&pattern); if (status) return status; } return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_fill (cairo_gstate_t *gstate) { cairo_status_t status; cairo_traps_t traps; _cairo_traps_init (&traps); status = _cairo_path_fill_to_traps (&gstate->path, gstate, &traps); if (status) { _cairo_traps_fini (&traps); return status; } _cairo_gstate_clip_and_composite_trapezoids (gstate, gstate->pattern, gstate->operator, gstate->surface, &traps); _cairo_traps_fini (&traps); _cairo_gstate_new_path (gstate); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_in_fill (cairo_gstate_t *gstate, double x, double y, cairo_bool_t *inside_ret) { cairo_status_t status = CAIRO_STATUS_SUCCESS; cairo_traps_t traps; cairo_matrix_transform_point (&gstate->ctm, &x, &y); _cairo_traps_init (&traps); status = _cairo_path_fill_to_traps (&gstate->path, gstate, &traps); if (status) goto BAIL; *inside_ret = _cairo_traps_contain (&traps, x, y); BAIL: _cairo_traps_fini (&traps); return status; } cairo_status_t _cairo_gstate_copy_page (cairo_gstate_t *gstate) { if (gstate->surface == NULL) return CAIRO_STATUS_NO_TARGET_SURFACE; return _cairo_surface_copy_page (gstate->surface); } cairo_status_t _cairo_gstate_show_page (cairo_gstate_t *gstate) { if (gstate->surface == NULL) return CAIRO_STATUS_NO_TARGET_SURFACE; return _cairo_surface_show_page (gstate->surface); } cairo_status_t _cairo_gstate_stroke_extents (cairo_gstate_t *gstate, double *x1, double *y1, double *x2, double *y2) { cairo_status_t status; cairo_traps_t traps; cairo_box_t extents; _cairo_traps_init (&traps); status = _cairo_path_stroke_to_traps (&gstate->path, gstate, &traps); if (status) goto BAIL; _cairo_traps_extents (&traps, &extents); *x1 = _cairo_fixed_to_double (extents.p1.x); *y1 = _cairo_fixed_to_double (extents.p1.y); *x2 = _cairo_fixed_to_double (extents.p2.x); *y2 = _cairo_fixed_to_double (extents.p2.y); cairo_matrix_transform_point (&gstate->ctm_inverse, x1, y1); cairo_matrix_transform_point (&gstate->ctm_inverse, x2, y2); BAIL: _cairo_traps_fini (&traps); return status; } cairo_status_t _cairo_gstate_fill_extents (cairo_gstate_t *gstate, double *x1, double *y1, double *x2, double *y2) { cairo_status_t status; cairo_traps_t traps; cairo_box_t extents; _cairo_traps_init (&traps); status = _cairo_path_fill_to_traps (&gstate->path, gstate, &traps); if (status) goto BAIL; _cairo_traps_extents (&traps, &extents); *x1 = _cairo_fixed_to_double (extents.p1.x); *y1 = _cairo_fixed_to_double (extents.p1.y); *x2 = _cairo_fixed_to_double (extents.p2.x); *y2 = _cairo_fixed_to_double (extents.p2.y); cairo_matrix_transform_point (&gstate->ctm_inverse, x1, y1); cairo_matrix_transform_point (&gstate->ctm_inverse, x2, y2); BAIL: _cairo_traps_fini (&traps); return status; } cairo_status_t _cairo_gstate_init_clip (cairo_gstate_t *gstate) { /* destroy any existing clip-region artifacts */ if (gstate->clip.surface) cairo_surface_destroy (gstate->clip.surface); gstate->clip.surface = NULL; if (gstate->clip.region) pixman_region_destroy (gstate->clip.region); gstate->clip.region = NULL; /* reset the surface's clip to the whole surface */ if (gstate->surface) _cairo_surface_set_clip_region (gstate->surface, gstate->clip.region); return CAIRO_STATUS_SUCCESS; } static int extract_transformed_rectangle(cairo_matrix_t *mat, cairo_traps_t *tr, pixman_box16_t *box) { double a, b, c, d, tx, ty; cairo_status_t st; st = cairo_matrix_get_affine (mat, &a, &b, &c, &d, &tx, &ty); if (!(st == CAIRO_STATUS_SUCCESS && b == 0. && c == 0.)) return 0; if (tr->num_traps == 1 && tr->traps[0].left.p1.x == tr->traps[0].left.p2.x && tr->traps[0].right.p1.x == tr->traps[0].right.p2.x && tr->traps[0].left.p1.y == tr->traps[0].right.p1.y && tr->traps[0].left.p2.y == tr->traps[0].right.p2.y && _cairo_fixed_is_integer(tr->traps[0].left.p1.x) && _cairo_fixed_is_integer(tr->traps[0].left.p1.y) && _cairo_fixed_is_integer(tr->traps[0].left.p2.x) && _cairo_fixed_is_integer(tr->traps[0].left.p2.y) && _cairo_fixed_is_integer(tr->traps[0].right.p1.x) && _cairo_fixed_is_integer(tr->traps[0].right.p1.y) && _cairo_fixed_is_integer(tr->traps[0].right.p2.x) && _cairo_fixed_is_integer(tr->traps[0].right.p2.y)) { box->x1 = (short) _cairo_fixed_integer_part(tr->traps[0].left.p1.x); box->x2 = (short) _cairo_fixed_integer_part(tr->traps[0].right.p1.x); box->y1 = (short) _cairo_fixed_integer_part(tr->traps[0].left.p1.y); box->y2 = (short) _cairo_fixed_integer_part(tr->traps[0].left.p2.y); return 1; } return 0; } /* Reset surface clip region to the one in the gstate */ cairo_status_t _cairo_gstate_restore_external_state (cairo_gstate_t *gstate) { cairo_status_t status; status = CAIRO_STATUS_SUCCESS; if (gstate->surface) status = _cairo_surface_set_clip_region (gstate->surface, gstate->clip.region); /* If not supported we're already using surface clipping */ if (status == CAIRO_INT_STATUS_UNSUPPORTED) status = CAIRO_STATUS_SUCCESS; return status; } cairo_status_t _cairo_gstate_clip (cairo_gstate_t *gstate) { cairo_status_t status; cairo_pattern_t pattern; cairo_traps_t traps; cairo_color_t white_color; cairo_box_t extents; pixman_box16_t box; /* Fill the clip region as traps. */ _cairo_traps_init (&traps); status = _cairo_path_fill_to_traps (&gstate->path, gstate, &traps); if (status) { _cairo_traps_fini (&traps); return status; } /* Check to see if we can represent these traps as a PixRegion. */ if (extract_transformed_rectangle (&gstate->ctm, &traps, &box)) { pixman_region16_t *rect = NULL; pixman_region16_t *intersection = NULL; status = CAIRO_STATUS_SUCCESS; rect = pixman_region_create_simple (&box); if (rect == NULL) { status = CAIRO_STATUS_NO_MEMORY; } else { if (gstate->clip.region == NULL) { gstate->clip.region = rect; } else { intersection = pixman_region_create(); if (pixman_region_intersect (intersection, gstate->clip.region, rect) == PIXMAN_REGION_STATUS_SUCCESS) { pixman_region_destroy (gstate->clip.region); gstate->clip.region = intersection; } else { status = CAIRO_STATUS_NO_MEMORY; } pixman_region_destroy (rect); } if (!status) status = _cairo_surface_set_clip_region (gstate->surface, gstate->clip.region); } if (status != CAIRO_INT_STATUS_UNSUPPORTED) { _cairo_traps_fini (&traps); return status; } /* Fall through as status == CAIRO_INT_STATUS_UNSUPPORTED means that backend doesn't support clipping regions and mask surface clipping should be used instead. */ } /* Otherwise represent the clip as a mask surface. */ _cairo_color_init (&white_color); if (gstate->clip.surface == NULL) { _cairo_traps_extents (&traps, &extents); _cairo_box_round_to_rectangle (&extents, &gstate->clip.rect); gstate->clip.surface = _cairo_surface_create_similar_solid (gstate->surface, CAIRO_FORMAT_A8, gstate->clip.rect.width, gstate->clip.rect.height, &white_color); if (gstate->clip.surface == NULL) return CAIRO_STATUS_NO_MEMORY; } translate_traps (&traps, -gstate->clip.rect.x, -gstate->clip.rect.y); _cairo_pattern_init_solid (&pattern, 1.0, 1.0, 1.0); status = _cairo_surface_composite_trapezoids (CAIRO_OPERATOR_IN, &pattern, gstate->clip.surface, 0, 0, 0, 0, gstate->clip.rect.width, gstate->clip.rect.height, traps.traps, traps.num_traps); _cairo_pattern_fini (&pattern); _cairo_traps_fini (&traps); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_show_surface (cairo_gstate_t *gstate, cairo_surface_t *surface, int width, int height) { /* We are dealing with 6 coordinate spaces in this function. this makes * it ugly. * * - "Image" space is the space of the surface we're reading pixels from. * it is the surface argument to this function. The surface has a * matrix attached to it which maps "user" space (see below) into * image space. * * - "Device" space is the space of the surface we're ultimately writing * pixels to. It is the current surface of the gstate argument to * this function. * * - "User" space is an arbitrary space defined by the user, defined * implicitly by the gstate's CTM. The CTM maps from user space to * device space. The CTM inverse (which is also kept at all times) * maps from device space to user space. * * - "Clip" space is the space of the surface being used to clip pixels * during compositing. Space-wise, it is a bounding box (offset+size) * within device space. This surface is usually smaller than the device * surface (and possibly the image surface too) and logically occupies * a bounding box around the "clip path", situated somewhere in device * space. The clip path is already painted on the clip surface. * * - "Intermediate" space is the subset of the Clip space that the * drawing will affect, and we allocate an intermediate surface * of this size so that we can paint in it. * * - "Pattern" space is another arbitrary space defined in the pattern * element of gstate. As pixels are read from image space, they are * combined with pixels being read from pattern space and pixels * already existing in device space. User coordinates are converted * to pattern space, similarly, using a matrix attached to the pattern. * (in fact, there is a 7th space in here, which is the space of the * surface acting as a source for the pattern) * * To composite these spaces, we temporarily change the image surface * so that it can be read and written in device coordinates; in a sense * this makes it "spatially compatible" with the clip and device spaces. * * * There is also some confusion about the interaction between a clip and * a pattern; it is assumed that in this "show surface" operation a pattern * is to be used as an auxiliary alpha mask. this might be wrong, but it's * what we're doing now. * * so, to follow the operations below, remember that in the compositing * model, each operation is always of the form ((src IN mask) OP dst). * that's the basic operation. * * so the compositing we are trying to do here, in general, involves 2 * steps, going via a temporary surface: * * - combining clip and pattern pixels together into a mask channel. * this will be ((pattern IN clip) SRC temporary). it ignores the * pixels already in the temporary, overwriting it with the * pattern, clipped to the clip mask. * * - combining temporary and "image" pixels with "device" pixels, * with a user-provided porter/duff operator. this will be * ((image IN temporary) OP device). * * if there is no clip, the degenerate case is just the second step * with pattern standing in for temporary. * */ cairo_status_t status; cairo_matrix_t image_to_user, image_to_device; double device_x, device_y; double device_width, device_height; cairo_pattern_t pattern; cairo_box_t pattern_extents; cairo_rectangle_t extents; cairo_surface_get_matrix (surface, &image_to_user); cairo_matrix_invert (&image_to_user); cairo_matrix_multiply (&image_to_device, &image_to_user, &gstate->ctm); _cairo_gstate_current_point (gstate, &device_x, &device_y); device_width = width; device_height = height; _cairo_matrix_transform_bounding_box (&image_to_device, &device_x, &device_y, &device_width, &device_height); _cairo_pattern_init_for_surface (&pattern, surface); _cairo_gstate_create_pattern (gstate, &pattern); pattern_extents.p1.x = _cairo_fixed_from_double (device_x); pattern_extents.p1.y = _cairo_fixed_from_double (device_y); pattern_extents.p2.x = _cairo_fixed_from_double (device_x + device_width); pattern_extents.p2.y = _cairo_fixed_from_double (device_y + device_height); _cairo_box_round_to_rectangle (&pattern_extents, &extents); if (gstate->clip.surface) { _cairo_rectangle_intersect (&extents, &gstate->clip.rect); /* Shortcut if empty */ if (_cairo_rectangle_empty (&extents)) { status = CAIRO_STATUS_SUCCESS; goto BAIL1; } status = _cairo_surface_composite (gstate->operator, &pattern, gstate->clip.surface, gstate->surface, extents.x, extents.y, 0, 0, extents.x, extents.y, extents.width, extents.height); BAIL1: ; } else { /* XXX: The rendered size is sometimes 1 or 2 pixels short * from what I expect. Need to fix this. * KRH: I'm guessing this was due to rounding error when * passing double coordinates for integer arguments. Using * the extents rectangle should fix this, since it's properly * rounded. Is this still the case? */ status = _cairo_surface_composite (gstate->operator, &pattern, NULL, gstate->surface, extents.x, extents.y, 0, 0, extents.x, extents.y, extents.width, extents.height); } _cairo_pattern_fini (&pattern); return status; } static void _cairo_gstate_unset_font (cairo_gstate_t *gstate) { if (gstate->font) { cairo_font_destroy (gstate->font); gstate->font = NULL; } } cairo_status_t _cairo_gstate_select_font (cairo_gstate_t *gstate, const char *family, cairo_font_slant_t slant, cairo_font_weight_t weight) { char *new_family; new_family = strdup (family); if (!new_family) return CAIRO_STATUS_NO_MEMORY; _cairo_gstate_unset_font (gstate); gstate->font_family = new_family; gstate->font_slant = slant; gstate->font_weight = weight; cairo_matrix_set_identity (&gstate->font_matrix); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_scale_font (cairo_gstate_t *gstate, double scale) { _cairo_gstate_unset_font (gstate); return cairo_matrix_scale (&gstate->font_matrix, scale, scale); } cairo_status_t _cairo_gstate_transform_font (cairo_gstate_t *gstate, cairo_matrix_t *matrix) { cairo_matrix_t tmp; double a, b, c, d, tx, ty; _cairo_gstate_unset_font (gstate); cairo_matrix_get_affine (matrix, &a, &b, &c, &d, &tx, &ty); cairo_matrix_set_affine (&tmp, a, b, c, d, 0, 0); return cairo_matrix_multiply (&gstate->font_matrix, &gstate->font_matrix, &tmp); } cairo_status_t _cairo_gstate_current_font (cairo_gstate_t *gstate, cairo_font_t **font) { cairo_status_t status; status = _cairo_gstate_ensure_font (gstate); if (status) return status; *font = gstate->font; return CAIRO_STATUS_SUCCESS; } void _cairo_gstate_set_font_transform (cairo_gstate_t *gstate, cairo_matrix_t *matrix) { _cairo_gstate_unset_font (gstate); cairo_matrix_copy (&gstate->font_matrix, matrix); } void _cairo_gstate_current_font_transform (cairo_gstate_t *gstate, cairo_matrix_t *matrix) { cairo_matrix_copy (matrix, &gstate->font_matrix); } /* * Like everything else in this file, fonts involve Too Many Coordinate Spaces; * it is easy to get confused about what's going on. * * The user's view * --------------- * * Users ask for things in user space. When cairo starts, a user space unit * is about 1/96 inch, which is similar to (but importantly different from) * the normal "point" units most users think in terms of. When a user * selects a font, its scale is set to "one user unit". The user can then * independently scale the user coordinate system *or* the font matrix, in * order to adjust the rendered size of the font. * * The only font type exposed to the user is cairo_font_t which is a * a font specialized to a particular scale matrix, CTM, and target * surface. The user is responsible for not using a cairo_font_t * after changing the parameters; doing so will produce garbled metrics. * * * The font's view * --------------- * * Fonts are designed and stored (in say .ttf files) in "font space", which * describes an "EM Square" (a design tile) and has some abstract number * such as 1000, 1024, or 2048 units per "EM". This is basically an * uninteresting space for us, but we need to remember that it exists. * * Font resources (from libraries or operating systems) render themselves * to a particular device. Since they do not want to make most programmers * worry about the font design space, the scaling API is simplified to * involve just telling the font the required pixel size of the EM square * (that is, in device space). * * * Cairo's gstate view * ------------------- * * In addition to the CTM and CTM inverse, we keep a matrix in the gstate * called the "font matrix" which describes the user's most recent * font-scaling or font-transforming request. This is kept in terms of an * abstract scale factor, composed with the CTM and used to set the font's * pixel size. So if the user asks to "scale the font by 12", the matrix * is: * * [ 12.0, 0.0, 0.0, 12.0, 0.0, 0.0 ] * * It is an affine matrix, like all cairo matrices, but its tx and ty * components are always set to zero; we don't permit "nudging" fonts * around. * * In order to perform any action on a font, we must build an object * called a cairo_font_scale_t; this contains the central 2x2 matrix * resulting from "font matrix * CTM". * * We pass this to the font when making requests of it, which causes it to * reply for a particular [user request, device] combination, under the CTM * (to accomodate the "zoom in" == "bigger fonts" issue above). * * The other terms in our communication with the font are therefore in * device space. When we ask it to perform text->glyph conversion, it will * produce a glyph string in device space. Glyph vectors we pass to it for * measuring or rendering should be in device space. The metrics which we * get back from the font will be in device space. The contents of the * global glyph image cache will be in device space. * * * Cairo's public view * ------------------- * * Since the values entering and leaving via public API calls are in user * space, the gstate functions typically need to multiply argumens by the * CTM (for user-input glyph vectors), and return values by the CTM inverse * (for font responses such as metrics or glyph vectors). * */ void _cairo_gstate_current_font_scale (cairo_gstate_t *gstate, cairo_font_scale_t *sc) { cairo_matrix_t tmp; double dummy; cairo_matrix_multiply (&tmp, &gstate->font_matrix, &gstate->ctm); cairo_matrix_get_affine (&tmp, &sc->matrix[0][0], &sc->matrix[0][1], &sc->matrix[1][0], &sc->matrix[1][1], &dummy, &dummy); } static cairo_status_t _cairo_gstate_ensure_font (cairo_gstate_t *gstate) { cairo_font_scale_t sc; cairo_status_t status; const char *family; if (gstate->font) return CAIRO_STATUS_SUCCESS; _cairo_gstate_current_font_scale (gstate, &sc); if (gstate->font_family) family = gstate->font_family; else family = CAIRO_FONT_FAMILY_DEFAULT; status = _cairo_font_create (family, gstate->font_slant, gstate->font_weight, &sc, &gstate->font); if (status) return status; return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_current_font_extents (cairo_gstate_t *gstate, cairo_font_extents_t *extents) { cairo_status_t status = _cairo_gstate_ensure_font (gstate); if (status) return status; return cairo_font_extents (gstate->font, &gstate->font_matrix, extents); } cairo_status_t _cairo_gstate_text_to_glyphs (cairo_gstate_t *gstate, const unsigned char *utf8, cairo_glyph_t **glyphs, int *nglyphs) { cairo_status_t status; cairo_point_t point; double origin_x, origin_y; int i; status = _cairo_gstate_ensure_font (gstate); if (status) return status; status = _cairo_path_current_point (&gstate->path, &point); if (status == CAIRO_STATUS_NO_CURRENT_POINT) { origin_x = 0.0; origin_y = 0.0; } else { origin_x = _cairo_fixed_to_double (point.x); origin_y = _cairo_fixed_to_double (point.y); cairo_matrix_transform_point (&gstate->ctm_inverse, &origin_x, &origin_y); } status = _cairo_font_text_to_glyphs (gstate->font, utf8, glyphs, nglyphs); if (status || !glyphs || !nglyphs || !(*glyphs) || !(nglyphs)) return status; /* The font responded in glyph space, starting from (0,0). Convert to user space by applying the font transform, then add any current point offset. */ for (i = 0; i < *nglyphs; ++i) { cairo_matrix_transform_point (&gstate->font_matrix, &((*glyphs)[i].x), &((*glyphs)[i].y)); (*glyphs)[i].x += origin_x; (*glyphs)[i].y += origin_y; } return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_set_font (cairo_gstate_t *gstate, cairo_font_t *font) { if (font != gstate->font) { if (gstate->font) cairo_font_destroy (gstate->font); gstate->font = font; if (gstate->font) cairo_font_reference (gstate->font); } return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_glyph_extents (cairo_gstate_t *gstate, cairo_glyph_t *glyphs, int num_glyphs, cairo_text_extents_t *extents) { cairo_status_t status; status = _cairo_gstate_ensure_font (gstate); if (status) return status; cairo_font_glyph_extents (gstate->font, &gstate->font_matrix, glyphs, num_glyphs, extents); return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_gstate_show_glyphs (cairo_gstate_t *gstate, cairo_glyph_t *glyphs, int num_glyphs) { cairo_status_t status; int i; cairo_glyph_t *transformed_glyphs = NULL; cairo_pattern_t pattern; cairo_box_t bbox; cairo_rectangle_t extents; status = _cairo_gstate_ensure_font (gstate); if (status) return status; transformed_glyphs = malloc (num_glyphs * sizeof(cairo_glyph_t)); if (transformed_glyphs == NULL) return CAIRO_STATUS_NO_MEMORY; for (i = 0; i < num_glyphs; ++i) { transformed_glyphs[i] = glyphs[i]; cairo_matrix_transform_point (&gstate->ctm, &transformed_glyphs[i].x, &transformed_glyphs[i].y); } status = _cairo_font_glyph_bbox (gstate->font, transformed_glyphs, num_glyphs, &bbox); _cairo_box_round_to_rectangle (&bbox, &extents); if (status) goto CLEANUP_GLYPHS; if (gstate->clip.surface) { cairo_surface_t *intermediate; cairo_color_t empty_color; _cairo_rectangle_intersect (&extents, &gstate->clip.rect); /* Shortcut if empty */ if (_cairo_rectangle_empty (&extents)) { status = CAIRO_STATUS_SUCCESS; goto BAIL1; } _cairo_color_init (&empty_color); _cairo_color_set_alpha (&empty_color, .0); intermediate = _cairo_surface_create_similar_solid (gstate->clip.surface, CAIRO_FORMAT_A8, extents.width, extents.height, &empty_color); if (intermediate == NULL) { status = CAIRO_STATUS_NO_MEMORY; goto BAIL1; } /* move the glyphs again, from dev space to intermediate space */ for (i = 0; i < num_glyphs; ++i) { transformed_glyphs[i].x -= extents.x; transformed_glyphs[i].y -= extents.y; } _cairo_pattern_init_solid (&pattern, 1.0, 1.0, 1.0); status = _cairo_font_show_glyphs (gstate->font, CAIRO_OPERATOR_ADD, &pattern, intermediate, extents.x, extents.y, 0, 0, extents.width, extents.height, transformed_glyphs, num_glyphs); _cairo_pattern_fini (&pattern); if (status) goto BAIL2; _cairo_pattern_init_for_surface (&pattern, gstate->clip.surface); status = _cairo_surface_composite (CAIRO_OPERATOR_IN, &pattern, NULL, intermediate, extents.x - gstate->clip.rect.x, extents.y - gstate->clip.rect.y, 0, 0, 0, 0, extents.width, extents.height); _cairo_pattern_fini (&pattern); if (status) goto BAIL2; _cairo_pattern_init_copy (&pattern, gstate->pattern); _cairo_gstate_create_pattern (gstate, &pattern); status = _cairo_surface_composite (gstate->operator, &pattern, intermediate, gstate->surface, extents.x, extents.y, 0, 0, extents.x, extents.y, extents.width, extents.height); _cairo_pattern_fini (&pattern); BAIL2: cairo_surface_destroy (intermediate); BAIL1: ; } else { _cairo_pattern_init_copy (&pattern, gstate->pattern); _cairo_gstate_create_pattern (gstate, &pattern); status = _cairo_font_show_glyphs (gstate->font, gstate->operator, &pattern, gstate->surface, extents.x, extents.y, extents.x, extents.y, extents.width, extents.height, transformed_glyphs, num_glyphs); _cairo_pattern_fini (&pattern); } CLEANUP_GLYPHS: free (transformed_glyphs); return status; } cairo_status_t _cairo_gstate_glyph_path (cairo_gstate_t *gstate, cairo_glyph_t *glyphs, int num_glyphs) { cairo_status_t status; int i; cairo_glyph_t *transformed_glyphs = NULL; transformed_glyphs = malloc (num_glyphs * sizeof(cairo_glyph_t)); if (transformed_glyphs == NULL) return CAIRO_STATUS_NO_MEMORY; for (i = 0; i < num_glyphs; ++i) { transformed_glyphs[i] = glyphs[i]; cairo_matrix_transform_point (&gstate->ctm, &(transformed_glyphs[i].x), &(transformed_glyphs[i].y)); } status = _cairo_font_glyph_path (gstate->font, transformed_glyphs, num_glyphs, &gstate->path); free (transformed_glyphs); return status; }