/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */ /* cairo - a vector graphics library with display and print output * * Copyright © 2006 Mozilla Corporation * * 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 Mozilla Corporation. * * Contributor(s): * Vladimir Vukicevic */ #include #ifdef CAIRO_NQUARTZ_SUPPORT_AGL #include #include #endif #include "cairoint.h" #include "cairo-private.h" #include "cairo-nquartz.h" #include "cairo-quartz-private.h" #undef NQUARTZ_DEBUG #ifdef NQUARTZ_DEBUG #define ND(_x) fprintf _x #else #define ND(_x) do {} while(0) #endif /* This method is private, but it exists. Its params are are exposed * as args to the NS* method, but not as CG. */ enum PrivateCGCompositeMode { kPrivateCGCompositeClear = 0, kPrivateCGCompositeCopy = 1, kPrivateCGCompositeSourceOver = 2, kPrivateCGCompositeSourceIn = 3, kPrivateCGCompositeSourceOut = 4, kPrivateCGCompositeSourceAtop = 5, kPrivateCGCompositeDestinationOver = 6, kPrivateCGCompositeDestinationIn = 7, kPrivateCGCompositeDestinationOut = 8, kPrivateCGCompositeDestinationAtop = 9, kPrivateCGCompositeXOR = 10, kPrivateCGCompositePlusDarker = 11, // (max (0, (1-d) + (1-s))) kPrivateCGCompositePlusLighter = 12, // (min (1, s + d)) }; typedef enum PrivateCGCompositeMode PrivateCGCompositeMode; CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode); CG_EXTERN void CGContextResetCTM (CGContextRef); CG_EXTERN void CGContextSetCTM (CGContextRef, CGAffineTransform); CG_EXTERN void CGContextResetClip (CGContextRef); CG_EXTERN CGSize CGContextGetPatternPhase (CGContextRef); /* We need to work with the 10.3 SDK as well (and 10.3 machines; luckily, 10.3.9 * has all the stuff we care about, just some of it isn't exported in the SDK. */ #ifndef kCGBitmapByteOrder32Host #define USE_10_3_WORKAROUNDS #define kCGBitmapAlphaInfoMask 0x1F #define kCGBitmapByteOrderMask 0x7000 #define kCGBitmapByteOrder32Host 0 typedef uint32_t CGBitmapInfo; /* public in 10.4, present in 10.3.9 */ CG_EXTERN void CGContextReplacePathWithStrokedPath (CGContextRef); CG_EXTERN CGImageRef CGBitmapContextCreateImage (CGContextRef); #endif typedef struct cairo_nquartz_surface { cairo_surface_t base; void *imageData; CGContextRef cgContext; CGAffineTransform cgContextBaseCTM; cairo_rectangle_int16_t extents; /* These are stored while drawing operations are in place, set up * by nquartz_setup_source() and nquartz_finish_source() */ CGAffineTransform imageTransform; CGImageRef sourceImage; CGShadingRef sourceShading; CGPatternRef sourcePattern; #ifdef CAIRO_NQUARTZ_SUPPORT_AGL AGLContext aglContext; #else void *_unused; #endif } cairo_nquartz_surface_t; /** ** Utility functions **/ void nquartz_surface_to_png (cairo_nquartz_surface_t *nq, char *dest); void nquartz_image_to_png (CGImageRef, char *dest); /* * Cairo path -> Quartz path conversion helpers */ /* cairo path -> mutable path */ static cairo_status_t _cairo_path_to_quartz_path_move_to (void *closure, cairo_point_t *point) { CGPathMoveToPoint ((CGMutablePathRef) closure, NULL, _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y)); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_path_to_quartz_path_line_to (void *closure, cairo_point_t *point) { CGPathAddLineToPoint ((CGMutablePathRef) closure, NULL, _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y)); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_path_to_quartz_path_curve_to (void *closure, cairo_point_t *p0, cairo_point_t *p1, cairo_point_t *p2) { CGPathAddCurveToPoint ((CGMutablePathRef) closure, NULL, _cairo_fixed_to_double(p0->x), _cairo_fixed_to_double(p0->y), _cairo_fixed_to_double(p1->x), _cairo_fixed_to_double(p1->y), _cairo_fixed_to_double(p2->x), _cairo_fixed_to_double(p2->y)); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_path_to_quartz_path_close_path (void *closure) { CGPathCloseSubpath ((CGMutablePathRef) closure); return CAIRO_STATUS_SUCCESS; } /* cairo path -> execute in context */ static cairo_status_t _cairo_path_to_quartz_context_move_to (void *closure, cairo_point_t *point) { //ND((stderr, "moveto: %f %f\n", _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y))); CGContextMoveToPoint ((CGContextRef) closure, _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y)); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_path_to_quartz_context_line_to (void *closure, cairo_point_t *point) { //ND((stderr, "lineto: %f %f\n", _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y))); if (CGContextIsPathEmpty ((CGContextRef) closure)) CGContextMoveToPoint ((CGContextRef) closure, _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y)); else CGContextAddLineToPoint ((CGContextRef) closure, _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y)); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_path_to_quartz_context_curve_to (void *closure, cairo_point_t *p0, cairo_point_t *p1, cairo_point_t *p2) { //ND( (stderr, "curveto: %f,%f %f,%f %f,%f\n", // _cairo_fixed_to_double(p0->x), _cairo_fixed_to_double(p0->y), // _cairo_fixed_to_double(p1->x), _cairo_fixed_to_double(p1->y), // _cairo_fixed_to_double(p2->x), _cairo_fixed_to_double(p2->y))); CGContextAddCurveToPoint ((CGContextRef) closure, _cairo_fixed_to_double(p0->x), _cairo_fixed_to_double(p0->y), _cairo_fixed_to_double(p1->x), _cairo_fixed_to_double(p1->y), _cairo_fixed_to_double(p2->x), _cairo_fixed_to_double(p2->y)); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_path_to_quartz_context_close_path (void *closure) { //ND((stderr, "closepath\n")); CGContextClosePath ((CGContextRef) closure); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_nquartz_cairo_path_to_quartz_path (cairo_path_fixed_t *path, CGMutablePathRef cgPath) { return _cairo_path_fixed_interpret (path, CAIRO_DIRECTION_FORWARD, _cairo_path_to_quartz_path_move_to, _cairo_path_to_quartz_path_line_to, _cairo_path_to_quartz_path_curve_to, _cairo_path_to_quartz_path_close_path, cgPath); } static cairo_status_t _cairo_nquartz_cairo_path_to_quartz_context (cairo_path_fixed_t *path, CGContextRef cgc) { return _cairo_path_fixed_interpret (path, CAIRO_DIRECTION_FORWARD, _cairo_path_to_quartz_context_move_to, _cairo_path_to_quartz_context_line_to, _cairo_path_to_quartz_context_curve_to, _cairo_path_to_quartz_context_close_path, cgc); } /* * Misc helpers/callbacks */ static PrivateCGCompositeMode _cairo_nquartz_cairo_operator_to_quartz (cairo_operator_t op) { switch (op) { case CAIRO_OPERATOR_CLEAR: return kPrivateCGCompositeClear; case CAIRO_OPERATOR_SOURCE: return kPrivateCGCompositeCopy; case CAIRO_OPERATOR_OVER: return kPrivateCGCompositeSourceOver; case CAIRO_OPERATOR_IN: /* XXX This doesn't match image output */ return kPrivateCGCompositeSourceIn; case CAIRO_OPERATOR_OUT: /* XXX This doesn't match image output */ return kPrivateCGCompositeSourceOut; case CAIRO_OPERATOR_ATOP: return kPrivateCGCompositeSourceAtop; case CAIRO_OPERATOR_DEST: /* XXX this is handled specially (noop)! */ return kPrivateCGCompositeCopy; case CAIRO_OPERATOR_DEST_OVER: return kPrivateCGCompositeDestinationOver; case CAIRO_OPERATOR_DEST_IN: /* XXX This doesn't match image output */ return kPrivateCGCompositeDestinationIn; case CAIRO_OPERATOR_DEST_OUT: return kPrivateCGCompositeDestinationOut; case CAIRO_OPERATOR_DEST_ATOP: /* XXX This doesn't match image output */ return kPrivateCGCompositeDestinationAtop; case CAIRO_OPERATOR_XOR: return kPrivateCGCompositeXOR; /* This will generate strange results */ case CAIRO_OPERATOR_ADD: return kPrivateCGCompositePlusLighter; case CAIRO_OPERATOR_SATURATE: /* XXX This doesn't match image output for SATURATE; there's no equivalent */ return kPrivateCGCompositePlusDarker; /* ??? */ } return kPrivateCGCompositeCopy; } static CGLineCap _cairo_nquartz_cairo_line_cap_to_quartz (cairo_line_cap_t ccap) { switch (ccap) { case CAIRO_LINE_CAP_BUTT: return kCGLineCapButt; break; case CAIRO_LINE_CAP_ROUND: return kCGLineCapRound; break; case CAIRO_LINE_CAP_SQUARE: return kCGLineCapSquare; break; } return kCGLineCapButt; } static CGLineJoin _cairo_nquartz_cairo_line_join_to_quartz (cairo_line_join_t cjoin) { switch (cjoin) { case CAIRO_LINE_JOIN_MITER: return kCGLineJoinMiter; break; case CAIRO_LINE_JOIN_ROUND: return kCGLineJoinRound; break; case CAIRO_LINE_JOIN_BEVEL: return kCGLineJoinBevel; break; } return kCGLineJoinMiter; } static void _cairo_nquartz_cairo_matrix_to_quartz (const cairo_matrix_t *src, CGAffineTransform *dst) { dst->a = src->xx; dst->b = src->xy; dst->c = src->yx; dst->d = src->yy; dst->tx = src->x0; dst->ty = src->y0; } /** ** Source -> Quartz setup and finish functions **/ static void ComputeGradientValue (void *info, const float *in, float *out) { float fdist = *in; /* 0.0 .. 1.0 */ cairo_fixed_16_16_t fdist_fix = _cairo_fixed_from_double(*in); cairo_gradient_pattern_t *grad = (cairo_gradient_pattern_t*) info; int i; for (i = 0; i < grad->n_stops; i++) { if (grad->stops[i].x > fdist_fix) break; } if (i == 0 || i == grad->n_stops) { if (i == grad->n_stops) --i; out[0] = grad->stops[i].color.red / 65535.; out[1] = grad->stops[i].color.green / 65535.; out[2] = grad->stops[i].color.blue / 65535.; out[3] = grad->stops[i].color.alpha / 65535.; } else { float ax = _cairo_fixed_to_double(grad->stops[i-1].x); float bx = _cairo_fixed_to_double(grad->stops[i].x) - ax; float bp = (fdist - ax)/bx; float ap = 1.0 - bp; out[0] = (grad->stops[i-1].color.red / 65535.) * ap + (grad->stops[i].color.red / 65535.) * bp; out[1] = (grad->stops[i-1].color.green / 65535.) * ap + (grad->stops[i].color.green / 65535.) * bp; out[2] = (grad->stops[i-1].color.blue / 65535.) * ap + (grad->stops[i].color.blue / 65535.) * bp; out[3] = (grad->stops[i-1].color.alpha / 65535.) * ap + (grad->stops[i].color.alpha / 65535.) * bp; } } static CGFunctionRef CreateGradientFunction (cairo_gradient_pattern_t *gpat) { static const float input_value_range[2] = { 0.f, 1.f }; static const float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f }; static const CGFunctionCallbacks callbacks = { 0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy }; return CGFunctionCreate (gpat, 1, input_value_range, 4, output_value_ranges, &callbacks); } static CGShadingRef _cairo_nquartz_cairo_gradient_pattern_to_quartz (cairo_pattern_t *abspat) { cairo_matrix_t mat; double x0, y0; if (abspat->type != CAIRO_PATTERN_TYPE_LINEAR && abspat->type != CAIRO_PATTERN_TYPE_RADIAL) return NULL; /* We can only do this if we have an identity pattern matrix; * otherwise fall back through to the generic pattern case. * XXXperf we could optimize this by creating a pattern with the shading; * but we'd need to know the extents to do that. * ... but we don't care; we can use the surface extents for it * XXXtodo - implement gradients with non-identity pattern matrices */ cairo_pattern_get_matrix (abspat, &mat); if (mat.xx != 1.0 || mat.yy != 1.0 || mat.xy != 0.0 || mat.yx != 0.0) return NULL; x0 = mat.x0; y0 = mat.y0; if (abspat->type == CAIRO_PATTERN_TYPE_LINEAR) { cairo_linear_pattern_t *lpat = (cairo_linear_pattern_t*) abspat; CGShadingRef shading; CGPoint start, end; CGFunctionRef gradFunc; CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); start = CGPointMake (_cairo_fixed_to_double (lpat->gradient.p1.x) - x0, _cairo_fixed_to_double (lpat->gradient.p1.y) - y0); end = CGPointMake (_cairo_fixed_to_double (lpat->gradient.p2.x) - x0, _cairo_fixed_to_double (lpat->gradient.p2.y) - y0); cairo_pattern_reference (abspat); gradFunc = CreateGradientFunction ((cairo_gradient_pattern_t*) lpat); shading = CGShadingCreateAxial (rgb, start, end, gradFunc, true, true); CGColorSpaceRelease(rgb); CGFunctionRelease(gradFunc); return shading; } if (abspat->type == CAIRO_PATTERN_TYPE_RADIAL) { cairo_radial_pattern_t *rpat = (cairo_radial_pattern_t*) abspat; CGShadingRef shading; CGPoint start, end; CGFunctionRef gradFunc; CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); start = CGPointMake (_cairo_fixed_to_double (rpat->gradient.inner.x) - x0, _cairo_fixed_to_double (rpat->gradient.inner.y) - y0); end = CGPointMake (_cairo_fixed_to_double (rpat->gradient.outer.x) - x0, _cairo_fixed_to_double (rpat->gradient.outer.y) - y0); cairo_pattern_reference (abspat); gradFunc = CreateGradientFunction ((cairo_gradient_pattern_t*) rpat); shading = CGShadingCreateRadial (rgb, start, _cairo_fixed_to_double (rpat->gradient.inner.radius), end, _cairo_fixed_to_double (rpat->gradient.outer.radius), gradFunc, true, true); CGColorSpaceRelease(rgb); CGFunctionRelease(gradFunc); return shading; } /* Shouldn't be reached */ ASSERT_NOT_REACHED; return NULL; } /* Generic cairo_pattern -> CGPattern function */ static void SurfacePatternDrawFunc (void *info, CGContextRef context) { cairo_surface_pattern_t *spat = (cairo_surface_pattern_t *) info; cairo_surface_t *pat_surf = spat->surface; cairo_rectangle_int16_t extents; cairo_status_t status; cairo_nquartz_surface_t *quartz_surf = NULL; cairo_bool_t flip = FALSE; CGImageRef img; if (!cairo_surface_is_nquartz (pat_surf)) { ND((stderr, "-- source is not nquartz surface\n")); /* This sucks; we should really store a dummy nquartz surface * for passing in here * XXXtodo store a dummy nquartz surface somewhere for handing off to clone_similar * XXXtodo/perf don't use clone if the source surface is an image surface! Instead, * just create the CGImage directly! */ cairo_surface_t *dummy = cairo_nquartz_surface_create (CAIRO_FORMAT_ARGB32, 1, 1); cairo_rectangle_int16_t rect; _cairo_surface_get_extents (pat_surf, &rect); cairo_surface_t *new_surf = NULL; _cairo_surface_clone_similar (dummy, pat_surf, rect.x, rect.y, rect.width, rect.height, &new_surf); cairo_surface_destroy(dummy); quartz_surf = (cairo_nquartz_surface_t *) new_surf; } else { ND((stderr, "-- source is nquartz surface\n")); /* If it's a nquartz surface, we can try to see if it's a CGBitmapContext; * we do this when we call CGBitmapContextCreateImage below. */ cairo_surface_reference (pat_surf); quartz_surf = (cairo_nquartz_surface_t*) pat_surf; /* XXXtodo WHY does this need to be flipped? Writing this stuff * to disk shows that in both this path and the path above the source image * has an identical orientation, and the destination context at all times has a Y * flip. So why do we need to flip in this case? */ flip = TRUE; } /* this is a 10.4 API, present in 10.3.9 */ CGContextFlush (quartz_surf->cgContext); img = CGBitmapContextCreateImage (quartz_surf->cgContext); if (!img) { // ... give up. ND((stderr, "CGBitmapContextCreateImage failed\n")); cairo_surface_destroy ((cairo_surface_t*)quartz_surf); return; } if (flip) { CGContextTranslateCTM (context, 0, CGImageGetHeight(img)); CGContextScaleCTM (context, 1, -1); } CGRect imageBounds; imageBounds.size = CGSizeMake (CGImageGetWidth(img), CGImageGetHeight(img)); imageBounds.origin.x = 0; imageBounds.origin.y = 0; CGContextDrawImage (context, imageBounds, img); CGImageRelease (img); cairo_surface_destroy ((cairo_surface_t*) quartz_surf); } static CGPatternRef _cairo_nquartz_cairo_repeating_surface_pattern_to_quartz (cairo_nquartz_surface_t *dest, cairo_pattern_t *abspat) { cairo_surface_pattern_t *spat; cairo_surface_t *pat_surf; cairo_rectangle_int16_t extents; CGRect pbounds; CGAffineTransform ptransform, stransform; CGPatternCallbacks cb = { 0, SurfacePatternDrawFunc, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy }; CGPatternRef cgpat; float rw, rh; /* SURFACE is the only type we'll handle here */ if (abspat->type != CAIRO_PATTERN_TYPE_SURFACE) return NULL; spat = (cairo_surface_pattern_t *) abspat; pat_surf = spat->surface; _cairo_surface_get_extents (pat_surf, &extents); pbounds.origin.x = 0; pbounds.origin.y = 0; pbounds.size.width = extents.width; pbounds.size.height = extents.height; cairo_matrix_t m = spat->base.matrix; cairo_matrix_invert(&m); _cairo_nquartz_cairo_matrix_to_quartz (&m, &stransform); /* The pattern matrix is relative to the bottom left, again; the * incoming cairo pattern matrix is relative to the upper left. * So we take the pattern matrix and the original context matrix, * which gives us the correct base translation/y flip. */ ptransform = CGAffineTransformConcat(stransform, dest->cgContextBaseCTM); #ifdef NQUARTZ_DEBUG ND((stderr, " pbounds: %f %f %f %f\n", pbounds.origin.x, pbounds.origin.y, pbounds.size.width, pbounds.size.height)); ND((stderr, " pattern xform: t: %f %f xx: %f xy: %f yx: %f yy: %f\n", ptransform.tx, ptransform.ty, ptransform.a, ptransform.b, ptransform.c, ptransform.d)); CGAffineTransform xform = CGContextGetCTM(dest->cgContext); ND((stderr, " context xform: t: %f %f xx: %f xy: %f yx: %f yy: %f\n", xform.tx, xform.ty, xform.a, xform.b, xform.c, xform.d)); #endif // kjs seems to indicate this should work (setting to 0,0 to avoid // tiling); however, the pattern CTM scaling ends up being NaN in // the pattern draw function if either rw or rh are 0. // XXXtodo get pattern drawing working with extend options // XXXtodo/perf optimize CAIRO_EXTEND_NONE to a single DrawImage instead of a pattern #if 0 if (spat->base.extend == CAIRO_EXTEND_NONE) { /* XXX wasteful; this will keep drawing the pattern in the * original location. We need to set up the clip region * instead to do this right. */ rw = 0; rh = 0; } else if (spat->base.extend == CAIRO_EXTEND_REPEAT) { rw = extents.width; rh = extents.height; } else if (spat->base.extend == CAIRO_EXTEND_REFLECT) { /* XXX broken; need to emulate by reflecting the image into 4 quadrants * and then tiling that */ rw = extents.width; rh = extents.height; } else { /* CAIRO_EXTEND_PAD */ /* XXX broken. */ rw = 0; rh = 0; } #else rw = extents.width; rh = extents.height; #endif cairo_pattern_reference (abspat); cgpat = CGPatternCreate (abspat, pbounds, ptransform, rw, rh, kCGPatternTilingConstantSpacing, /* kCGPatternTilingNoDistortion, */ TRUE, &cb); return cgpat; } typedef enum { DO_SOLID, DO_SHADING, DO_PATTERN, DO_UNSUPPORTED } cairo_nquartz_action_t; static cairo_nquartz_action_t _cairo_nquartz_setup_source (cairo_nquartz_surface_t *surface, cairo_pattern_t *source) { assert (!(surface->sourceImage || surface->sourceShading || surface->sourcePattern)); if (source->type == CAIRO_PATTERN_TYPE_SOLID) { cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source; CGContextSetRGBStrokeColor (surface->cgContext, solid->color.red, solid->color.green, solid->color.blue, solid->color.alpha); CGContextSetRGBFillColor (surface->cgContext, solid->color.red, solid->color.green, solid->color.blue, solid->color.alpha); return DO_SOLID; } else if (source->type == CAIRO_PATTERN_TYPE_LINEAR || source->type == CAIRO_PATTERN_TYPE_RADIAL) { CGShadingRef shading = _cairo_nquartz_cairo_gradient_pattern_to_quartz (source); if (!shading) return CAIRO_INT_STATUS_UNSUPPORTED; surface->sourceShading = shading; return DO_SHADING; } else if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { CGPatternRef pattern = _cairo_nquartz_cairo_repeating_surface_pattern_to_quartz (surface, source); if (!pattern) return CAIRO_INT_STATUS_UNSUPPORTED; float patternAlpha = 1.0f; // Save before we change the pattern, colorspace, etc. so that // we can restore and make sure that quartz releases our // pattern (which may be stack allocated) CGContextSaveGState(surface->cgContext); CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL); CGContextSetFillColorSpace (surface->cgContext, patternSpace); CGContextSetFillPattern (surface->cgContext, pattern, &patternAlpha); CGColorSpaceRelease (patternSpace); /* Quartz likes to munge the pattern phase (as yet unexplained * why); force it to 0,0 as we've already baked in the correct * pattern translation into the pattern matrix */ CGContextSetPatternPhase (surface->cgContext, CGSizeMake(0,0)); surface->sourcePattern = pattern; return DO_PATTERN; } else { return DO_UNSUPPORTED; } ASSERT_NOT_REACHED; } static void _cairo_nquartz_teardown_source (cairo_nquartz_surface_t *surface, cairo_pattern_t *source) { if (surface->sourceImage) { // nothing to do; we don't use sourceImage yet } if (surface->sourceShading) { CGShadingRelease(surface->sourceShading); surface->sourceShading = NULL; } if (surface->sourcePattern) { CGPatternRelease(surface->sourcePattern); // To tear down the pattern and colorspace CGContextRestoreGState(surface->cgContext); surface->sourcePattern = NULL; } } /** ** get source/dest image implementation **/ static void ImageDataReleaseFunc(void *info, const void *data, size_t size) { if (data != NULL) { free((void *) data); } } /* Read the image from the surface's front buffer */ static cairo_int_status_t _cairo_nquartz_get_image (cairo_nquartz_surface_t *surface, cairo_image_surface_t **image_out, unsigned char **data_out) { unsigned char *imageData; cairo_image_surface_t *isurf; /* If we weren't constructed with an AGL Context * or a CCGBitmapContext, then we have no way * of doing this */ #ifdef CAIRO_NQUARTZ_SUPPORT_AGL if (surface->aglContext) { AGLContext oldContext; cairo_format_masks_t masks = { 32, 0xff << 24, 0xff << 16, 0xff << 8, 0xff << 0 }; unsigned int i; oldContext = aglGetCurrentContext(); if (oldContext != surface->aglContext) aglSetCurrentContext(surface->aglContext); imageData = (unsigned char *) malloc (surface->extents.width * surface->extents.height * 4); if (!imageData) return CAIRO_STATUS_NO_MEMORY; glReadBuffer (GL_FRONT); glReadPixels (0, 0, surface->extents.width, surface->extents.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, imageData); /* swap lines */ /* XXX find some fast code to do this */ unsigned int lineSize = surface->extents.width * 4; unsigned char *tmpLine = malloc(lineSize); for (i = 0; i < surface->extents.height / 2; i++) { unsigned char *l0 = imageData + lineSize * i; unsigned char *l1 = imageData + (lineSize * (surface->extents.height - i - 1)); memcpy (tmpLine, l0, lineSize); memcpy (l0, l1, lineSize); memcpy (l1, tmpLine, lineSize); } free (tmpLine); if (oldContext && oldContext != surface->aglContext) aglSetCurrentContext(oldContext); isurf = (cairo_image_surface_t *)_cairo_image_surface_create_with_masks (imageData, &masks, surface->extents.width, surface->extents.height, surface->extents.width * 4); if (data_out) *data_out = imageData; else _cairo_image_surface_assume_ownership_of_data (isurf); #else /* no AGL */ if (0) { #endif } else if (CGBitmapContextGetBitsPerPixel(surface->cgContext) != 0) { unsigned int stride; unsigned int bitinfo; unsigned int bpc, bpp; CGColorSpaceRef colorspace; unsigned int color_comps; imageData = (unsigned char *) CGBitmapContextGetData(surface->cgContext); #ifdef USE_10_3_WORKAROUNDS bitinfo = CGBitmapContextGetAlphaInfo (surface->cgContext); #else bitinfo = CGBitmapContextGetBitmapInfo (surface->cgContext); #endif stride = CGBitmapContextGetBytesPerRow (surface->cgContext); bpp = CGBitmapContextGetBitsPerPixel (surface->cgContext); bpc = CGBitmapContextGetBitsPerComponent (surface->cgContext); // let's hope they don't add YUV under us colorspace = CGBitmapContextGetColorSpace (surface->cgContext); color_comps = CGColorSpaceGetNumberOfComponents(colorspace); // XXX TODO: We can handle all of these by converting to // pixman masks, including non-native-endian masks if (bpc != 8) return CAIRO_INT_STATUS_UNSUPPORTED; if (bpp != 32 && bpp != 8) return CAIRO_INT_STATUS_UNSUPPORTED; if (color_comps != 3 && color_comps != 1) return CAIRO_INT_STATUS_UNSUPPORTED; if (bpp == 32 && color_comps == 3 && (bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaPremultipliedFirst && (bitinfo & kCGBitmapByteOrderMask) == kCGBitmapByteOrder32Host) { isurf = (cairo_image_surface_t *) cairo_image_surface_create_for_data (imageData, CAIRO_FORMAT_ARGB32, surface->extents.width, surface->extents.height, stride); } else if (bpp == 32 && color_comps == 3 && (bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaNoneSkipFirst && (bitinfo & kCGBitmapByteOrderMask) == kCGBitmapByteOrder32Host) { isurf = (cairo_image_surface_t *) cairo_image_surface_create_for_data (imageData, CAIRO_FORMAT_RGB24, surface->extents.width, surface->extents.height, stride); } else if (bpp == 8 && color_comps == 1) { isurf = (cairo_image_surface_t *) cairo_image_surface_create_for_data (imageData, CAIRO_FORMAT_A8, surface->extents.width, surface->extents.height, stride); } else { return CAIRO_INT_STATUS_UNSUPPORTED; } } else { return CAIRO_INT_STATUS_UNSUPPORTED; } *image_out = isurf; return CAIRO_STATUS_SUCCESS; } /** ** Cairo surface backend implementations **/ static cairo_status_t _cairo_nquartz_surface_finish (void *abstract_surface) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; ND((stderr, "_cairo_nquartz_surface_finish[%p] cgc: %p\n", surface, surface->cgContext)); #ifdef CAIRO_NQUARTZ_SUPPORT_AGL if (surface->aglContext) aglSetCurrentContext(surface->aglContext); #endif CGContextFlush (surface->cgContext); /* Restore our saved gstate that we use to reset clipping */ CGContextRestoreGState (surface->cgContext); CGContextRelease (surface->cgContext); surface->cgContext = NULL; #ifdef CAIRO_NQUARTZ_SUPPORT_AGL if (surface->aglContext) glFlush(); surface->aglContext = NULL; #endif if (surface->imageData) { free (surface->imageData); surface->imageData = NULL; } return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_nquartz_surface_acquire_source_image (void *abstract_surface, cairo_image_surface_t **image_out, void **image_extra) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; //ND((stderr, "%p _cairo_nquartz_surface_acquire_source_image\n", surface)); *image_extra = NULL; return _cairo_nquartz_get_image (surface, image_out, NULL); } static cairo_status_t _cairo_nquartz_surface_acquire_dest_image (void *abstract_surface, cairo_rectangle_int16_t *interest_rect, cairo_image_surface_t **image_out, cairo_rectangle_int16_t *image_rect, void **image_extra) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; cairo_int_status_t status; unsigned char *data; ND((stderr, "%p _cairo_nquartz_surface_acquire_dest_image\n", surface)); *image_rect = surface->extents; status = _cairo_nquartz_get_image (surface, image_out, &data); if (status) return status; *image_extra = data; return CAIRO_STATUS_SUCCESS; } static void _cairo_nquartz_surface_release_dest_image (void *abstract_surface, cairo_rectangle_int16_t *interest_rect, cairo_image_surface_t *image, cairo_rectangle_int16_t *image_rect, void *image_extra) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; unsigned char *imageData = (unsigned char *) image_extra; //ND((stderr, "%p _cairo_nquartz_surface_release_dest_image\n", surface)); if (!CGBitmapContextGetData (surface->cgContext)) { CGDataProviderRef dataProvider; CGImageRef img; dataProvider = CGDataProviderCreateWithData (NULL, imageData, surface->extents.width * surface->extents.height * 4, ImageDataReleaseFunc); img = CGImageCreate (surface->extents.width, surface->extents.height, 8, 32, surface->extents.width * 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedFirst, dataProvider, NULL, false, kCGRenderingIntentDefault); CGContextSetCompositeOperation (surface->cgContext, kPrivateCGCompositeCopy); CGContextDrawImage (surface->cgContext, CGRectMake (0, 0, surface->extents.width, surface->extents.height), img); CGImageRelease (img); CGDataProviderRelease (dataProvider); ND((stderr, "Image for surface %p was recovered from a bitmap\n", surface)); } cairo_surface_destroy ((cairo_surface_t *) image); } static cairo_surface_t * _cairo_nquartz_surface_create_similar (void *abstract_surface, cairo_content_t content, int width, int height) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; cairo_format_t format; if (content == CAIRO_CONTENT_COLOR_ALPHA) format = CAIRO_FORMAT_ARGB32; else if (content == CAIRO_CONTENT_COLOR) format = CAIRO_FORMAT_RGB24; else if (content == CAIRO_CONTENT_ALPHA) format = CAIRO_FORMAT_A8; else return NULL; return cairo_nquartz_surface_create (format, width, height); } static cairo_status_t _cairo_nquartz_surface_clone_similar (void *abstract_surface, cairo_surface_t *src, int src_x, int src_y, int width, int height, cairo_surface_t **clone_out) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; cairo_nquartz_surface_t *new_surface = NULL; cairo_format_t new_format; CGImageRef quartz_image = NULL; cairo_surface_t *surface_to_release = NULL; if (cairo_surface_is_nquartz (src)) { cairo_nquartz_surface_t *qsurf = (cairo_nquartz_surface_t *) src; quartz_image = CGBitmapContextCreateImage (qsurf->cgContext); new_format = CAIRO_FORMAT_ARGB32; /* XXX bogus; recover a real format from the image */ } else if (_cairo_surface_is_image (src)) { cairo_image_surface_t *isurf = (cairo_image_surface_t *) src; CGDataProviderRef dataProvider; CGColorSpaceRef cgColorspace; CGBitmapInfo bitinfo; int bitsPerComponent, bitsPerPixel; if (isurf->format == CAIRO_FORMAT_ARGB32) { cgColorspace = CGColorSpaceCreateDeviceRGB(); bitinfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; bitsPerComponent = 8; bitsPerPixel = 32; } else if (isurf->format == CAIRO_FORMAT_RGB24) { cgColorspace = CGColorSpaceCreateDeviceRGB(); bitinfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; bitsPerComponent = 8; bitsPerPixel = 32; } else if (isurf->format == CAIRO_FORMAT_A8) { cgColorspace = CGColorSpaceCreateDeviceGray(); bitinfo = kCGImageAlphaNone; bitsPerComponent = 8; bitsPerPixel = 8; } else { /* SUPPORT A1, maybe */ return CAIRO_INT_STATUS_UNSUPPORTED; } new_format = isurf->format; dataProvider = CGDataProviderCreateWithData (NULL, isurf->data, isurf->height * isurf->stride, NULL); quartz_image = CGImageCreate (isurf->width, isurf->height, bitsPerComponent, bitsPerPixel, isurf->stride, cgColorspace, bitinfo, dataProvider, NULL, false, kCGRenderingIntentDefault); CGDataProviderRelease (dataProvider); CGColorSpaceRelease (cgColorspace); } else { return CAIRO_INT_STATUS_UNSUPPORTED; } if (!quartz_image) return CAIRO_INT_STATUS_UNSUPPORTED; new_surface = (cairo_nquartz_surface_t *) cairo_nquartz_surface_create (new_format, CGImageGetWidth (quartz_image), CGImageGetHeight (quartz_image)); if (!new_surface || new_surface->base.status) return CAIRO_INT_STATUS_UNSUPPORTED; CGContextSetCompositeOperation (new_surface->cgContext, kPrivateCGCompositeCopy); nquartz_image_to_png (quartz_image, NULL); CGContextDrawImage (new_surface->cgContext, CGRectMake (src_x, src_y, width, height), quartz_image); CGImageRelease (quartz_image); *clone_out = (cairo_surface_t*) new_surface; return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_nquartz_surface_get_extents (void *abstract_surface, cairo_rectangle_int16_t *extents) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; *extents = surface->extents; return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_nquartz_surface_paint (void *abstract_surface, cairo_operator_t op, cairo_pattern_t *source) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; cairo_nquartz_action_t action; ND((stderr, "%p _cairo_nquartz_surface_paint op %d source->type %d\n", surface, op, source->type)); if (op == CAIRO_OPERATOR_DEST) return CAIRO_STATUS_SUCCESS; CGContextSetCompositeOperation (surface->cgContext, _cairo_nquartz_cairo_operator_to_quartz (op)); CGRect bounds = CGContextGetClipBoundingBox (surface->cgContext); action = _cairo_nquartz_setup_source (surface, source); if (action == DO_SOLID || action == DO_PATTERN) { CGContextFillRect (surface->cgContext, CGRectMake(surface->extents.x, surface->extents.y, surface->extents.width, surface->extents.height)); } else if (action == DO_SHADING) { CGContextDrawShading (surface->cgContext, surface->sourceShading); } else { rv = CAIRO_INT_STATUS_UNSUPPORTED; } _cairo_nquartz_teardown_source (surface, source); ND((stderr, "-- paint\n")); return rv; } static cairo_int_status_t _cairo_nquartz_surface_fill (void *abstract_surface, cairo_operator_t op, cairo_pattern_t *source, cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, double tolerance, cairo_antialias_t antialias) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; cairo_nquartz_action_t action; ND((stderr, "%p _cairo_nquartz_surface_fill op %d source->type %d\n", surface, op, source->type)); if (op == CAIRO_OPERATOR_DEST) return CAIRO_STATUS_SUCCESS; CGContextSaveGState (surface->cgContext); CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); CGContextSetCompositeOperation (surface->cgContext, _cairo_nquartz_cairo_operator_to_quartz (op)); action = _cairo_nquartz_setup_source (surface, source); if (action == DO_UNSUPPORTED) { CGContextRestoreGState (surface->cgContext); return CAIRO_INT_STATUS_UNSUPPORTED; } CGContextBeginPath (surface->cgContext); _cairo_nquartz_cairo_path_to_quartz_context (path, surface->cgContext); if (action == DO_SOLID || action == DO_PATTERN) { if (fill_rule == CAIRO_FILL_RULE_WINDING) CGContextFillPath (surface->cgContext); else CGContextEOFillPath (surface->cgContext); } else if (action == DO_SHADING) { // we have to clip and then paint the shading; we can't fill // with the shading if (fill_rule == CAIRO_FILL_RULE_WINDING) CGContextClip (surface->cgContext); else CGContextEOClip (surface->cgContext); CGContextDrawShading (surface->cgContext, surface->sourceShading); } else { rv = CAIRO_INT_STATUS_UNSUPPORTED; } _cairo_nquartz_teardown_source (surface, source); CGContextRestoreGState (surface->cgContext); ND((stderr, "-- fill\n")); return rv; } static cairo_int_status_t _cairo_nquartz_surface_stroke (void *abstract_surface, cairo_operator_t op, cairo_pattern_t *source, cairo_path_fixed_t *path, cairo_stroke_style_t *style, cairo_matrix_t *ctm, cairo_matrix_t *ctm_inverse, double tolerance, cairo_antialias_t antialias) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; cairo_nquartz_action_t action; ND((stderr, "%p _cairo_nquartz_surface_stroke op %d source->type %d\n", surface, op, source->type)); if (op == CAIRO_OPERATOR_DEST) return CAIRO_STATUS_SUCCESS; CGContextSaveGState (surface->cgContext); CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); CGContextSetLineWidth (surface->cgContext, style->line_width); CGContextSetLineCap (surface->cgContext, _cairo_nquartz_cairo_line_cap_to_quartz (style->line_cap)); CGContextSetLineJoin (surface->cgContext, _cairo_nquartz_cairo_line_join_to_quartz (style->line_join)); CGContextSetMiterLimit (surface->cgContext, style->miter_limit); if (style->dash && style->num_dashes) { #define STATIC_DASH 32 float sdash[STATIC_DASH]; float *fdash = sdash; int k; if (style->num_dashes > STATIC_DASH) fdash = malloc (sizeof(float)*style->num_dashes); for (k = 0; k < style->num_dashes; k++) fdash[k] = (float) style->dash[k]; CGContextSetLineDash (surface->cgContext, style->dash_offset, fdash, style->num_dashes); if (fdash != sdash) free (fdash); } CGContextSetCompositeOperation (surface->cgContext, _cairo_nquartz_cairo_operator_to_quartz (op)); action = _cairo_nquartz_setup_source (surface, source); if (action == DO_UNSUPPORTED) { CGContextRestoreGState (surface->cgContext); return CAIRO_INT_STATUS_UNSUPPORTED; } CGContextBeginPath (surface->cgContext); _cairo_nquartz_cairo_path_to_quartz_context (path, surface->cgContext); if (action == DO_SOLID || action == DO_PATTERN) { CGContextStrokePath (surface->cgContext); } else if (action == DO_SHADING) { // we have to clip and then paint the shading; first we have to convert // the stroke to a path that we can fill CGContextReplacePathWithStrokedPath (surface->cgContext); CGContextClip (surface->cgContext); CGContextDrawShading (surface->cgContext, surface->sourceShading); } else { rv = CAIRO_INT_STATUS_UNSUPPORTED; } CGContextRestoreGState (surface->cgContext); ND((stderr, "-- stroke\n")); return rv; } static cairo_int_status_t _cairo_nquartz_surface_show_glyphs (void *abstract_surface, cairo_operator_t op, cairo_pattern_t *source, cairo_glyph_t *glyphs, int num_glyphs, cairo_scaled_font_t *scaled_font) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; cairo_nquartz_action_t action; int i; if (num_glyphs <= 0) return CAIRO_STATUS_SUCCESS; if (op == CAIRO_OPERATOR_DEST) return CAIRO_STATUS_SUCCESS; if (!_cairo_scaled_font_is_atsui (scaled_font)) return CAIRO_INT_STATUS_UNSUPPORTED; CGContextSaveGState (surface->cgContext); action = _cairo_nquartz_setup_source (surface, source); if (action == DO_SOLID || action == DO_PATTERN) { CGContextSetTextDrawingMode (surface->cgContext, kCGTextFill); } else if (action == DO_SHADING) { CGContextSetTextDrawingMode (surface->cgContext, kCGTextClip); } else { /* Unsupported */ CGContextRestoreGState (surface->cgContext); return CAIRO_INT_STATUS_UNSUPPORTED; } double y_height = surface->extents.height; CGContextSetCompositeOperation (surface->cgContext, _cairo_nquartz_cairo_operator_to_quartz (op)); ATSUFontID fid = _cairo_atsui_scaled_font_get_atsu_font_id (scaled_font); ATSFontRef atsfref = FMGetATSFontRefFromFont (fid); CGFontRef cgfref = CGFontCreateWithPlatformFont (&atsfref); CGContextSetFont (surface->cgContext, cgfref); CGFontRelease (cgfref); /* So this should include the size; I don't know if I need to extract the * size from this and call CGContextSetFontSize.. will I get crappy hinting * with this 1.0 size business? Or will CG just multiply that size into the * text matrix? */ //ND((stderr, "show_glyphs: glyph 0 at: %f, %f\n", glyphs[0].x, glyphs[0].y)); CGAffineTransform cairoTextTransform, textTransform; _cairo_nquartz_cairo_matrix_to_quartz (&scaled_font->font_matrix, &cairoTextTransform); textTransform = CGAffineTransformMakeTranslation (glyphs[0].x, glyphs[0].y); textTransform = CGAffineTransformScale (textTransform, 1.0, -1.0); textTransform = CGAffineTransformConcat (cairoTextTransform, textTransform); CGContextSetTextMatrix (surface->cgContext, textTransform); CGContextSetFontSize (surface->cgContext, 1.0); // XXXtodo/perf: stack storage for glyphs/sizes CGGlyph *cg_glyphs = (CGGlyph*) malloc(sizeof(CGGlyph) * num_glyphs); CGSize *cg_advances = (CGSize*) malloc(sizeof(CGSize) * num_glyphs); double xprev = glyphs[0].x; double yprev = glyphs[0].y; cg_glyphs[0] = glyphs[0].index; cg_advances[0].width = 0; cg_advances[0].height = 0; for (i = 1; i < num_glyphs; i++) { cg_glyphs[i] = glyphs[i].index; cg_advances[i-1].width = glyphs[i].x - xprev; cg_advances[i-1].height = glyphs[i].y - yprev; xprev = glyphs[i].x; yprev = glyphs[i].y; } #if 0 for (i = 0; i < num_glyphs; i++) { ND((stderr, "[%d: %d %f,%f]\n", i, cg_glyphs[i], cg_advances[i].width, cg_advances[i].height)); } #endif CGContextShowGlyphsWithAdvances (surface->cgContext, cg_glyphs, cg_advances, num_glyphs); free (cg_glyphs); free (cg_advances); if (action == DO_SHADING) CGContextDrawShading (surface->cgContext, surface->sourceShading); _cairo_nquartz_teardown_source (surface, source); CGContextRestoreGState (surface->cgContext); return rv; } static cairo_int_status_t _cairo_nquartz_surface_mask (void *abstract_surface, cairo_operator_t op, cairo_pattern_t *source, cairo_pattern_t *mask) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; ND((stderr, "%p _cairo_nquartz_surface_mask op %d source->type %d mask->type %d\n", surface, op, source->type, mask->type)); if (mask->type == CAIRO_PATTERN_TYPE_SOLID) { /* This is easy; we just need to paint with the alpha. */ cairo_solid_pattern_t *solid_mask = (cairo_solid_pattern_t *) mask; CGContextSetAlpha (surface->cgContext, solid_mask->color.alpha); } else { /* So, CGContextClipToMask is not present in 10.3.9, so we're doomed; we could * do fallback, if we implemented _composite, but for now let's just not support * this. (But pretend we did.) */ return CAIRO_STATUS_SUCCESS; } rv = _cairo_nquartz_surface_paint (surface, op, source); if (mask->type == CAIRO_PATTERN_TYPE_SOLID) { CGContextSetAlpha (surface->cgContext, 1.0); } ND((stderr, "-- mask\n")); return rv; } static cairo_int_status_t _cairo_nquartz_surface_intersect_clip_path (void *abstract_surface, cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, double tolerance, cairo_antialias_t antialias) { cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface; ND((stderr, "%p _cairo_nquartz_surface_intersect_clip_path path: %p\n", surface, path)); if (path == NULL) { /* If we're being asked to reset the clip, we can only do it * by restoring the gstate to our previous saved one, and * saving it again. * * Note that this assumes that ALL nquartz surface creation * functions will do a SaveGState first; we do this in create_internal. */ CGContextRestoreGState (surface->cgContext); CGContextSaveGState (surface->cgContext); } else { CGContextBeginPath (surface->cgContext); _cairo_nquartz_cairo_path_to_quartz_context (path, surface->cgContext); if (fill_rule == CAIRO_FILL_RULE_WINDING) CGContextClip (surface->cgContext); else CGContextEOClip (surface->cgContext); } ND((stderr, "-- intersect_clip_path\n")); return CAIRO_STATUS_SUCCESS; } // XXXtodo implement show_page; need to figure out how to handle begin/end static const struct _cairo_surface_backend cairo_nquartz_surface_backend = { CAIRO_SURFACE_TYPE_NQUARTZ, _cairo_nquartz_surface_create_similar, _cairo_nquartz_surface_finish, _cairo_nquartz_surface_acquire_source_image, NULL, /* release_source_image */ _cairo_nquartz_surface_acquire_dest_image, _cairo_nquartz_surface_release_dest_image, _cairo_nquartz_surface_clone_similar, NULL, /* composite */ NULL, /* fill_rectangles */ NULL, /* composite_trapezoids */ NULL, /* copy_page */ NULL, /* show_page */ NULL, /* set_clip_region */ _cairo_nquartz_surface_intersect_clip_path, _cairo_nquartz_surface_get_extents, NULL, /* old_show_glyphs */ NULL, /* get_font_options */ NULL, /* flush */ NULL, /* mark_dirty_rectangle */ NULL, /* scaled_font_fini */ NULL, /* scaled_glyph_fini */ _cairo_nquartz_surface_paint, _cairo_nquartz_surface_mask, _cairo_nquartz_surface_stroke, _cairo_nquartz_surface_fill, _cairo_nquartz_surface_show_glyphs, NULL, /* snapshot */ }; cairo_bool_t cairo_surface_is_nquartz (cairo_surface_t *surf) { return (surf->backend == &cairo_nquartz_surface_backend); } static cairo_nquartz_surface_t * _cairo_nquartz_surface_create_internal (CGContextRef cgContext, #ifdef CAIRO_NQUARTZ_SUPPORT_AGL AGLContext aglContext, #else void * unused, #endif cairo_content_t content, unsigned int width, unsigned int height, cairo_bool_t y_grows_down) { cairo_nquartz_surface_t *surface; /* Init the base surface */ surface = malloc(sizeof(cairo_nquartz_surface_t)); if (surface == NULL) { _cairo_error (CAIRO_STATUS_NO_MEMORY); return NULL; } memset(surface, 0, sizeof(cairo_nquartz_surface_t)); _cairo_surface_init(&surface->base, &cairo_nquartz_surface_backend, content); /* Save our extents */ surface->extents.x = surface->extents.y = 0; surface->extents.width = width; surface->extents.height = height; if (!y_grows_down) { /* Then make the CGContext sane */ CGContextTranslateCTM (cgContext, 0.0, surface->extents.height); CGContextScaleCTM (cgContext, 1.0, -1.0); } /* Save so we can always get back to a known-good CGContext -- this is * required for proper behaviour of intersect_clip_path(NULL) */ CGContextSaveGState (cgContext); #ifdef CAIRO_NQUARTZ_SUPPORT_AGL surface->aglContext = aglContext; #endif surface->cgContext = cgContext; surface->cgContextBaseCTM = CGContextGetCTM (cgContext); surface->imageData = NULL; return surface; } #ifdef CAIRO_NQUARTZ_SUPPORT_AGL cairo_surface_t * cairo_nquartz_surface_create_for_agl_context (AGLContext aglContext, unsigned int width, unsigned int height, cairo_bool_t y_grows_down) { cairo_nquartz_surface_t *surf; CGSize sz; /* Make our CGContext from the AGL context */ sz.width = width; sz.height = height; CGContextRef cgc = CGGLContextCreate (aglContext, sz, NULL /* device RGB colorspace */); surf = _cairo_nquartz_surface_create_internal (cgc, aglContext, CAIRO_CONTENT_COLOR_ALPHA, width, height, y_grows_down); if (!surf) { CGContextRelease (cgc); // create_internal will have set an error return (cairo_surface_t*) &_cairo_surface_nil; } return (cairo_surface_t *) surf; } #endif cairo_surface_t * cairo_nquartz_surface_create_for_cg_context (CGContextRef cgContext, unsigned int width, unsigned int height, cairo_bool_t y_grows_down) { cairo_nquartz_surface_t *surf; CGContextRetain (cgContext); surf = _cairo_nquartz_surface_create_internal (cgContext, NULL, CAIRO_CONTENT_COLOR_ALPHA, width, height, y_grows_down); if (!surf) { CGContextRelease (cgContext); // create_internal will have set an error return (cairo_surface_t*) &_cairo_surface_nil; } return (cairo_surface_t *) surf; } cairo_surface_t * cairo_nquartz_surface_create (cairo_format_t format, unsigned int width, unsigned int height) { cairo_nquartz_surface_t *surf; CGContextRef cgc; CGColorSpaceRef cgColorspace; CGBitmapInfo bitinfo; void *imageData; int stride; int bitsPerComponent; if (format == CAIRO_FORMAT_ARGB32) { cgColorspace = CGColorSpaceCreateDeviceRGB(); stride = width * 4; bitinfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; bitsPerComponent = 8; } else if (format == CAIRO_FORMAT_RGB24) { cgColorspace = CGColorSpaceCreateDeviceRGB(); stride = width * 4; bitinfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; bitsPerComponent = 8; } else if (format == CAIRO_FORMAT_A8) { cgColorspace = CGColorSpaceCreateDeviceGray(); if (width % 4 == 0) stride = width; else stride = (width & 3) + 1; bitinfo = kCGImageAlphaNone; bitsPerComponent = 8; } else if (format == CAIRO_FORMAT_A1) { /* I don't think we can usefully support this, as defined by * cairo_format_t -- these are 1-bit pixels stored in 32-bit * quantities. */ _cairo_error (CAIRO_STATUS_INVALID_FORMAT); return (cairo_surface_t*) &_cairo_surface_nil; } else { _cairo_error (CAIRO_STATUS_INVALID_FORMAT); return (cairo_surface_t*) &_cairo_surface_nil; } imageData = malloc (height * stride); if (!imageData) { CGColorSpaceRelease (cgColorspace); _cairo_error (CAIRO_STATUS_NO_MEMORY); return (cairo_surface_t*) &_cairo_surface_nil; } cgc = CGBitmapContextCreate (imageData, width, height, bitsPerComponent, stride, cgColorspace, bitinfo); CGColorSpaceRelease (cgColorspace); if (!cgc) { _cairo_error (CAIRO_STATUS_NO_MEMORY); return (cairo_surface_t*) &_cairo_surface_nil; } surf = _cairo_nquartz_surface_create_internal (cgc, NULL, _cairo_content_from_format (format), width, height, FALSE); if (!surf) { CGContextRelease (cgc); // create_internal will have set an error return (cairo_surface_t*) &_cairo_surface_nil; } surf->imageData = imageData; return (cairo_surface_t *) surf; } CGContextRef cairo_nquartz_surface_get_cg_context (cairo_surface_t *surf) { cairo_nquartz_surface_t *nquartz = (cairo_nquartz_surface_t*)surf; if (!cairo_surface_is_nquartz(surf)) return NULL; return nquartz->cgContext; } /* Debug stuff */ #ifdef NQUARTZ_DEBUG #include void ExportCGImageToPNGFile(CGImageRef inImageRef, char* dest) { Handle dataRef = NULL; OSType dataRefType; CFStringRef inPath = CFStringCreateWithCString(NULL, dest, kCFStringEncodingASCII); GraphicsExportComponent grex = 0; unsigned long sizeWritten; ComponentResult result; // create the data reference result = QTNewDataReferenceFromFullPathCFString(inPath, kQTNativeDefaultPathStyle, 0, &dataRef, &dataRefType); if (NULL != dataRef && noErr == result) { // get the PNG exporter result = OpenADefaultComponent(GraphicsExporterComponentType, kQTFileTypePNG, &grex); if (grex) { // tell the exporter where to find its source image result = GraphicsExportSetInputCGImage(grex, inImageRef); if (noErr == result) { // tell the exporter where to save the exporter image result = GraphicsExportSetOutputDataReference(grex, dataRef, dataRefType); if (noErr == result) { // write the PNG file result = GraphicsExportDoExport(grex, &sizeWritten); } } // remember to close the component CloseComponent(grex); } // remember to dispose of the data reference handle DisposeHandle(dataRef); } } #endif void nquartz_image_to_png (CGImageRef imgref, char *dest) { #if 0 static int sctr = 0; char sptr[] = "/Users/vladimir/Desktop/barXXXXX.png"; if (dest == NULL) { fprintf (stderr, "** Writing %p to bar%d\n", imgref, sctr); sprintf (sptr, "/Users/vladimir/Desktop/bar%d.png", sctr); sctr++; dest = sptr; } ExportCGImageToPNGFile(imgref, dest); #endif } void nquartz_surface_to_png (cairo_nquartz_surface_t *nq, char *dest) { #if 0 static int sctr = 0; char sptr[] = "/Users/vladimir/Desktop/fooXXXXX.png"; if (nq->base.type != CAIRO_SURFACE_TYPE_NQUARTZ) { fprintf (stderr, "** nquartz_surface_to_png: surface %p isn't nquartz!\n", nq); return; } if (dest == NULL) { fprintf (stderr, "** Writing %p to foo%d\n", nq, sctr); sprintf (sptr, "/Users/vladimir/Desktop/foo%d.png", sctr); sctr++; dest = sptr; } CGImageRef imgref = CGBitmapContextCreateImage (nq->cgContext); if (imgref == NULL) { fprintf (stderr, "nquartz surface at %p is not a bitmap context!\n", nq); return; } ExportCGImageToPNGFile(imgref, dest); CGImageRelease(imgref); #endif }