--- a/src/cairo-quartz-font.c 2012-11-13 18:20:00.000000000 -0800 +++ b/src/cairo-quartz-font.c 2012-11-13 18:06:56.000000000 -0800 @@ -90,8 +90,9 @@ static int (*CGFontGetAscentPtr) (CGFont static int (*CGFontGetDescentPtr) (CGFontRef fontRef) = NULL; static int (*CGFontGetLeadingPtr) (CGFontRef fontRef) = NULL; -/* Not public anymore in 64-bits nor in 10.7 */ -static ATSFontRef (*FMGetATSFontRefFromFontPtr) (FMFont iFont) = NULL; +/* CTFontCreateWithGraphicsFont is not public until 10.5. */ +typedef const struct __CTFontDescriptor *CTFontDescriptorRef; +static CTFontRef (*CTFontCreateWithGraphicsFontPtr) (CGFontRef, CGFloat, const CGAffineTransform *, CTFontDescriptorRef) = NULL; static cairo_bool_t _cairo_quartz_font_symbol_lookup_done = FALSE; static cairo_bool_t _cairo_quartz_font_symbols_present = FALSE; @@ -130,7 +131,7 @@ quartz_font_ensure_symbols(void) CGContextGetAllowsFontSmoothingPtr = dlsym(RTLD_DEFAULT, "CGContextGetAllowsFontSmoothing"); CGContextSetAllowsFontSmoothingPtr = dlsym(RTLD_DEFAULT, "CGContextSetAllowsFontSmoothing"); - FMGetATSFontRefFromFontPtr = dlsym(RTLD_DEFAULT, "FMGetATSFontRefFromFont"); + CTFontCreateWithGraphicsFontPtr = dlsym(RTLD_DEFAULT, "CTFontCreateWithGraphicsFont"); if ((CGFontCreateWithFontNamePtr || CGFontCreateWithNamePtr) && CGFontGetGlyphBBoxesPtr && @@ -155,6 +156,7 @@ struct _cairo_quartz_font_face { cairo_font_face_t base; CGFontRef cgFont; + CTFontRef ctFont; }; /* @@ -239,6 +241,10 @@ _cairo_quartz_font_face_destroy (void *a { cairo_quartz_font_face_t *font_face = (cairo_quartz_font_face_t*) abstract_face; + if (font_face->ctFont) { + CFRelease (font_face->ctFont); + } + CGFontRelease (font_face->cgFont); } @@ -363,6 +369,12 @@ cairo_quartz_font_face_create_for_cgfont font_face->cgFont = CGFontRetain (font); + if (CTFontCreateWithGraphicsFontPtr) { + font_face->ctFont = CTFontCreateWithGraphicsFontPtr (font, 1.0, NULL, NULL); + } else { + font_face->ctFont = NULL; + } + _cairo_font_face_init (&font_face->base, &_cairo_quartz_font_face_backend); return &font_face->base; @@ -782,49 +794,10 @@ _cairo_quartz_scaled_font_get_cg_font_re return ffont->cgFont; } -/* - * compat with old ATSUI backend - */ - -/** - * cairo_quartz_font_face_create_for_atsu_font_id - * @font_id: an ATSUFontID for the font. - * - * Creates a new font for the Quartz font backend based on an - * #ATSUFontID. This font can then be used with - * cairo_set_font_face() or cairo_scaled_font_create(). - * - * Return value: a newly created #cairo_font_face_t. Free with - * cairo_font_face_destroy() when you are done using it. - * - * Since: 1.6 - **/ -cairo_font_face_t * -cairo_quartz_font_face_create_for_atsu_font_id (ATSUFontID font_id) +CTFontRef +_cairo_quartz_scaled_font_get_ct_font_ref (cairo_scaled_font_t *abstract_font) { - quartz_font_ensure_symbols(); - - if (FMGetATSFontRefFromFontPtr != NULL) { - ATSFontRef atsFont = FMGetATSFontRefFromFontPtr (font_id); - CGFontRef cgFont = CGFontCreateWithPlatformFont (&atsFont); - cairo_font_face_t *ff; - - ff = cairo_quartz_font_face_create_for_cgfont (cgFont); - - CGFontRelease (cgFont); - - return ff; - } else { - _cairo_error_throw (CAIRO_STATUS_NO_MEMORY); - return (cairo_font_face_t *)&_cairo_font_face_nil; - } -} - -/* This is the old name for the above function, exported for compat purposes */ -cairo_font_face_t *cairo_atsui_font_face_create_for_atsu_font_id (ATSUFontID font_id); + cairo_quartz_font_face_t *ffont = _cairo_quartz_scaled_to_face(abstract_font); -cairo_font_face_t * -cairo_atsui_font_face_create_for_atsu_font_id (ATSUFontID font_id) -{ - return cairo_quartz_font_face_create_for_atsu_font_id (font_id); + return ffont->ctFont; } --- a/src/cairo-quartz-image-surface.c 2010-06-18 04:47:13.000000000 -0700 +++ b/src/cairo-quartz-image-surface.c 2012-11-13 18:06:56.000000000 -0800 @@ -148,6 +148,8 @@ _cairo_quartz_image_surface_flush (void surface->image = newImage; CGImageRelease (oldImage); + surface->base.is_clear = surface->imageSurface->base.is_clear; + return CAIRO_STATUS_SUCCESS; } @@ -270,6 +272,8 @@ cairo_quartz_image_surface_create (cairo qisurf->image = image; qisurf->imageSurface = image_surface; + qisurf->base.is_clear = image_surface->base.is_clear; + return &qisurf->base; } --- a/src/cairo-quartz-private.h 2010-12-25 06:21:34.000000000 -0800 +++ b/src/cairo-quartz-private.h 2012-11-13 18:06:56.000000000 -0800 @@ -50,6 +50,9 @@ typedef CGFloat cairo_quartz_float_t; typedef float cairo_quartz_float_t; #endif +/* define CTFontRef for pre-10.5 SDKs */ +typedef const struct __CTFont *CTFontRef; + typedef struct cairo_quartz_surface { cairo_surface_t base; @@ -60,21 +63,22 @@ typedef struct cairo_quartz_surface { cairo_surface_t *imageSurfaceEquiv; cairo_surface_clipper_t clipper; - cairo_rectangle_int_t extents; - /* These are stored while drawing operations are in place, set up - * by quartz_setup_source() and quartz_finish_source() + /** + * If non-null, this is a CGImage representing the contents of the surface. + * We clear this out before any painting into the surface, so that we + * don't force a copy to be created. */ - CGAffineTransform sourceTransform; + CGImageRef bitmapContextImage; - CGImageRef sourceImage; - cairo_surface_t *sourceImageSurface; - CGRect sourceImageRect; + /** + * If non-null, this is the CGLayer for the surface. + */ + CGLayerRef cgLayer; - CGShadingRef sourceShading; - CGPatternRef sourcePattern; + cairo_rectangle_int_t extents; - CGInterpolationQuality oldInterpolationQuality; + cairo_bool_t ownsData; } cairo_quartz_surface_t; typedef struct cairo_quartz_image_surface { @@ -103,6 +107,9 @@ _cairo_quartz_create_cgimage (cairo_form CGFontRef _cairo_quartz_scaled_font_get_cg_font_ref (cairo_scaled_font_t *sfont); +CTFontRef +_cairo_quartz_scaled_font_get_ct_font_ref (cairo_scaled_font_t *sfont); + #else # error Cairo was not compiled with support for the quartz backend --- a/src/cairo-quartz-surface.c 2012-11-13 18:20:00.000000000 -0800 +++ b/src/cairo-quartz-surface.c 2012-11-13 18:06:56.000000000 -0800 @@ -41,6 +41,8 @@ #include "cairo-error-private.h" #include "cairo-surface-clipper-private.h" +#include "cairo-gstate-private.h" +#include "cairo-private.h" #include @@ -77,6 +79,11 @@ * This macro can be used to conditionally compile backend-specific code. */ +/* Here are some of the differences between cairo and CoreGraphics + - cairo has only a single source active at once vs. CoreGraphics having + separate sources for stroke and fill +*/ + /* This method is private, but it exists. Its params are are exposed * as args to the NS* method, but not as CG. */ @@ -126,6 +133,12 @@ static void (*CGContextSetShouldAntialia static void (*CGContextSetAllowsFontSmoothingPtr) (CGContextRef, bool) = NULL; static bool (*CGContextGetAllowsFontSmoothingPtr) (CGContextRef) = NULL; static CGPathRef (*CGContextCopyPathPtr) (CGContextRef) = NULL; +static CGFloat (*CGContextGetAlphaPtr) (CGContextRef) = NULL; + +/* CTFontDrawGlyphs is not available until 10.7 */ +static void (*CTFontDrawGlyphsPtr) (CTFontRef, const CGGlyph[], const CGPoint[], size_t, CGContextRef) = NULL; + +static SInt32 _cairo_quartz_osx_version = 0x0; static cairo_bool_t _cairo_quartz_symbol_lookup_done = FALSE; @@ -160,6 +173,14 @@ static void quartz_ensure_symbols(void) CGContextCopyPathPtr = dlsym(RTLD_DEFAULT, "CGContextCopyPath"); CGContextGetAllowsFontSmoothingPtr = dlsym(RTLD_DEFAULT, "CGContextGetAllowsFontSmoothing"); CGContextSetAllowsFontSmoothingPtr = dlsym(RTLD_DEFAULT, "CGContextSetAllowsFontSmoothing"); + CGContextGetAlphaPtr = dlsym(RTLD_DEFAULT, "CGContextGetAlpha"); + + CTFontDrawGlyphsPtr = dlsym(RTLD_DEFAULT, "CTFontDrawGlyphs"); + + if (Gestalt(gestaltSystemVersion, &_cairo_quartz_osx_version) != noErr) { + // assume 10.5 + _cairo_quartz_osx_version = 0x1050; + } _cairo_quartz_symbol_lookup_done = TRUE; } @@ -430,6 +446,7 @@ _cairo_quartz_cairo_operator_to_quartz_c case CAIRO_OPERATOR_HSL_LUMINOSITY: default: assert (0); + return kPrivateCGCompositeClear; } } @@ -598,10 +615,13 @@ _cairo_quartz_cairo_matrix_to_quartz (co typedef struct { bool isClipping; CGGlyph *cg_glyphs; - CGSize *cg_advances; + union { + CGSize *cg_advances; + CGPoint *cg_positions; + } u; size_t nglyphs; CGAffineTransform textTransform; - CGFontRef font; + cairo_scaled_font_t *scaled_font; CGPoint origin; } unbounded_show_glyphs_t; @@ -679,12 +699,6 @@ _cairo_quartz_fixup_unbounded_operation else CGContextEOFillPath (cgc); } else if (op->op == UNBOUNDED_SHOW_GLYPHS) { - CGContextSetFont (cgc, op->u.show_glyphs.font); - CGContextSetFontSize (cgc, 1.0); - CGContextSetTextMatrix (cgc, CGAffineTransformIdentity); - CGContextTranslateCTM (cgc, op->u.show_glyphs.origin.x, op->u.show_glyphs.origin.y); - CGContextConcatCTM (cgc, op->u.show_glyphs.textTransform); - if (op->u.show_glyphs.isClipping) { /* Note that the comment in show_glyphs about kCGTextClip * and the text transform still applies here; however, the @@ -693,12 +707,25 @@ _cairo_quartz_fixup_unbounded_operation CGContextSetTextDrawingMode (cgc, kCGTextClip); CGContextSaveGState (cgc); } + CGContextTranslateCTM (cgc, op->u.show_glyphs.origin.x, op->u.show_glyphs.origin.y); + CGContextConcatCTM (cgc, op->u.show_glyphs.textTransform); + if (CTFontDrawGlyphsPtr) { + CTFontDrawGlyphsPtr (_cairo_quartz_scaled_font_get_ct_font_ref (op->u.show_glyphs.scaled_font), + op->u.show_glyphs.cg_glyphs, + op->u.show_glyphs.u.cg_positions, + op->u.show_glyphs.nglyphs, + cgc); + } else { + CGContextSetFont (cgc, _cairo_quartz_scaled_font_get_cg_font_ref (op->u.show_glyphs.scaled_font)); + CGContextSetFontSize (cgc, 1.0); + CGContextSetTextMatrix (cgc, CGAffineTransformIdentity); + + CGContextShowGlyphsWithAdvances (cgc, + op->u.show_glyphs.cg_glyphs, + op->u.show_glyphs.u.cg_advances, + op->u.show_glyphs.nglyphs); - CGContextShowGlyphsWithAdvances (cgc, - op->u.show_glyphs.cg_glyphs, - op->u.show_glyphs.cg_advances, - op->u.show_glyphs.nglyphs); - + } if (op->u.show_glyphs.isClipping) { CGContextClearRect (cgc, clipBoxRound); CGContextRestoreGState (cgc); @@ -1102,12 +1129,12 @@ DataProviderReleaseCallback (void *info, { quartz_source_image_t *source_img = info; _cairo_surface_release_source_image (source_img->surface, source_img->image_out, source_img->image_extra); + cairo_surface_destroy (source_img->surface); free (source_img); } static cairo_status_t -_cairo_surface_to_cgimage (cairo_surface_t *target, - cairo_surface_t *source, +_cairo_surface_to_cgimage (cairo_surface_t *source, CGImageRef *image_out) { cairo_status_t status; @@ -1127,9 +1154,14 @@ _cairo_surface_to_cgimage (cairo_surface } if (_cairo_quartz_is_cgcontext_bitmap_context (surface->cgContext)) { - *image_out = CGBitmapContextCreateImage (surface->cgContext); - if (*image_out) - return CAIRO_STATUS_SUCCESS; + if (!surface->bitmapContextImage) { + surface->bitmapContextImage = + CGBitmapContextCreateImage (surface->cgContext); + } + if (surface->bitmapContextImage) { + *image_out = CGImageRetain (surface->bitmapContextImage); + return CAIRO_STATUS_SUCCESS; + } } } @@ -1137,10 +1169,11 @@ _cairo_surface_to_cgimage (cairo_surface if (source_img == NULL) return _cairo_error (CAIRO_STATUS_NO_MEMORY); - source_img->surface = source; + source_img->surface = cairo_surface_reference(source); status = _cairo_surface_acquire_source_image (source_img->surface, &source_img->image_out, &source_img->image_extra); if (status) { + cairo_surface_destroy (source_img->surface); free (source_img); return status; } @@ -1251,7 +1284,7 @@ _cairo_quartz_cairo_repeating_surface_pa is_bounded = _cairo_surface_get_extents (pat_surf, &extents); assert (is_bounded); - status = _cairo_surface_to_cgimage ((cairo_surface_t*) dest, pat_surf, &image); + status = _cairo_surface_to_cgimage (pat_surf, &image); if (status) return status; if (image == NULL) @@ -1322,16 +1355,43 @@ typedef enum { DO_SHADING, DO_PATTERN, DO_IMAGE, + DO_TILED_IMAGE, + DO_LAYER, DO_UNSUPPORTED, - DO_NOTHING, - DO_TILED_IMAGE + DO_NOTHING } cairo_quartz_action_t; -static cairo_quartz_action_t +/* State used during a drawing operation. */ +typedef struct { + CGContextRef context; + cairo_quartz_action_t action; + + // Used with DO_SHADING, DO_IMAGE, DO_TILED_IMAGE and DO_LAYER + CGAffineTransform transform; + + // Used with DO_IMAGE and DO_TILED_IMAGE + CGImageRef image; + cairo_surface_t *imageSurface; + + // Used with DO_IMAGE, DO_TILED_IMAGE and DO_LAYER + CGRect imageRect; + + // Used with DO_LAYER + CGLayerRef layer; + + // Used with DO_SHADING + CGShadingRef shading; + + // Used with DO_PATTERN + CGPatternRef pattern; +} cairo_quartz_drawing_state_t; + +static void _cairo_quartz_setup_fallback_source (cairo_quartz_surface_t *surface, - const cairo_pattern_t *source) + const cairo_pattern_t *source, + cairo_quartz_drawing_state_t *state) { - CGRect clipBox = CGContextGetClipBoundingBox (surface->cgContext); + CGRect clipBox = CGContextGetClipBoundingBox (state->context); double x0, y0, w, h; cairo_surface_t *fallback; @@ -1340,8 +1400,10 @@ _cairo_quartz_setup_fallback_source (cai cairo_status_t status; if (clipBox.size.width == 0.0f || - clipBox.size.height == 0.0f) - return DO_NOTHING; + clipBox.size.height == 0.0f) { + state->action = DO_NOTHING; + return; + } x0 = floor(clipBox.origin.x); y0 = floor(clipBox.origin.y); @@ -1384,18 +1446,21 @@ _cairo_quartz_setup_fallback_source (cai } #endif - status = _cairo_surface_to_cgimage (&surface->base, fallback, &img); - if (status) - return DO_UNSUPPORTED; - if (img == NULL) - return DO_NOTHING; - - surface->sourceImageRect = CGRectMake (0.0, 0.0, w, h); - surface->sourceImage = img; - surface->sourceImageSurface = fallback; - surface->sourceTransform = CGAffineTransformMakeTranslation (x0, y0); + status = _cairo_surface_to_cgimage (fallback, &img); + if (status) { + state->action = DO_UNSUPPORTED; + return; + } + if (img == NULL) { + state->action = DO_NOTHING; + return; + } - return DO_IMAGE; + state->imageRect = CGRectMake (0.0, 0.0, w, h); + state->image = img; + state->imageSurface = fallback; + state->transform = CGAffineTransformMakeTranslation (x0, y0); + state->action = DO_IMAGE; } /* @@ -1411,10 +1476,11 @@ based on the extents of the object (the we don't want the rasterization of the entire gradient to depend on the clip region). */ -static cairo_quartz_action_t +static void _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface, const cairo_linear_pattern_t *lpat, - cairo_rectangle_int_t *extents) + cairo_rectangle_int_t *extents, + cairo_quartz_drawing_state_t *state) { const cairo_pattern_t *abspat = &lpat->base.base; cairo_matrix_t mat; @@ -1424,9 +1490,10 @@ _cairo_quartz_setup_linear_source (cairo bool extend = abspat->extend == CAIRO_EXTEND_PAD; if (lpat->base.n_stops == 0) { - CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.); - CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.); - return DO_SOLID; + CGContextSetRGBStrokeColor (state->context, 0., 0., 0., 0.); + CGContextSetRGBFillColor (state->context, 0., 0., 0., 0.); + state->action = DO_SOLID; + return; } if (lpat->p1.x == lpat->p2.x && @@ -1436,12 +1503,13 @@ _cairo_quartz_setup_linear_source (cairo * Whatever the correct behaviour is, let's at least have only pixman's * implementation to worry about. */ - return _cairo_quartz_setup_fallback_source (surface, abspat); + _cairo_quartz_setup_fallback_source (surface, abspat, state); + return; } mat = abspat->matrix; cairo_matrix_invert (&mat); - _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform); + _cairo_quartz_cairo_matrix_to_quartz (&mat, &state->transform); rgb = CGColorSpaceCreateDeviceRGB(); @@ -1461,21 +1529,22 @@ _cairo_quartz_setup_linear_source (cairo extents); } - surface->sourceShading = CGShadingCreateAxial (rgb, - start, end, - gradFunc, - extend, extend); + state->shading = CGShadingCreateAxial (rgb, + start, end, + gradFunc, + extend, extend); CGColorSpaceRelease(rgb); CGFunctionRelease(gradFunc); - return DO_SHADING; + state->action = DO_SHADING; } -static cairo_quartz_action_t +static void _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface, const cairo_radial_pattern_t *rpat, - cairo_rectangle_int_t *extents) + cairo_rectangle_int_t *extents, + cairo_quartz_drawing_state_t *state) { const cairo_pattern_t *abspat = &rpat->base.base; cairo_matrix_t mat; @@ -1494,9 +1563,10 @@ _cairo_quartz_setup_radial_source (cairo double centerDistance = sqrt (dx*dx + dy*dy); if (rpat->base.n_stops == 0) { - CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.); - CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.); - return DO_SOLID; + CGContextSetRGBStrokeColor (state->context, 0., 0., 0., 0.); + CGContextSetRGBFillColor (state->context, 0., 0., 0., 0.); + state->action = DO_SOLID; + return; } if (r2 <= centerDistance + r1 + 1e-6 && /* circle 2 doesn't contain circle 1 */ @@ -1507,12 +1577,13 @@ _cairo_quartz_setup_radial_source (cairo * implementation to worry about. * Note that this also catches the cases where r1 == r2. */ - return _cairo_quartz_setup_fallback_source (surface, abspat); + _cairo_quartz_setup_fallback_source (surface, abspat, state); + return; } mat = abspat->matrix; cairo_matrix_invert (&mat); - _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform); + _cairo_quartz_cairo_matrix_to_quartz (&mat, &state->transform); rgb = CGColorSpaceCreateDeviceRGB(); @@ -1531,90 +1602,79 @@ _cairo_quartz_setup_radial_source (cairo extents); } - surface->sourceShading = CGShadingCreateRadial (rgb, - start, - r1, - end, - r2, - gradFunc, - extend, extend); + state->shading = CGShadingCreateRadial (rgb, + start, + r1, + end, + r2, + gradFunc, + extend, extend); CGColorSpaceRelease(rgb); CGFunctionRelease(gradFunc); - return DO_SHADING; + state->action = DO_SHADING; } -static cairo_quartz_action_t -_cairo_quartz_setup_source (cairo_quartz_surface_t *surface, - const cairo_pattern_t *source, - cairo_rectangle_int_t *extents) +static void +_cairo_quartz_setup_surface_source (cairo_quartz_surface_t *surface, + const cairo_surface_pattern_t *spat, + cairo_rectangle_int_t *extents, + cairo_quartz_drawing_state_t *state) { - assert (!(surface->sourceImage || surface->sourceShading || surface->sourcePattern)); - - surface->oldInterpolationQuality = CGContextGetInterpolationQuality (surface->cgContext); - CGContextSetInterpolationQuality (surface->cgContext, _cairo_quartz_filter_to_quartz (source->filter)); + const cairo_pattern_t *source = &spat->base; + CGContextRef context = state->context; - 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; - } - - if (source->type == CAIRO_PATTERN_TYPE_LINEAR) { - const cairo_linear_pattern_t *lpat = (const cairo_linear_pattern_t *)source; - return _cairo_quartz_setup_linear_source (surface, lpat, extents); - } - - if (source->type == CAIRO_PATTERN_TYPE_RADIAL) { - const cairo_radial_pattern_t *rpat = (const cairo_radial_pattern_t *)source; - return _cairo_quartz_setup_radial_source (surface, rpat, extents); - } - - if (source->type == CAIRO_PATTERN_TYPE_SURFACE && - (source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT))) + if (source->extend == CAIRO_EXTEND_NONE || source->extend == CAIRO_EXTEND_PAD || + (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT)) { - const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source; cairo_surface_t *pat_surf = spat->surface; CGImageRef img; cairo_matrix_t m = spat->base.matrix; cairo_rectangle_int_t extents; - cairo_status_t status; CGAffineTransform xform; CGRect srcRect; cairo_fixed_t fw, fh; cairo_bool_t is_bounded; + cairo_bool_t repeat = source->extend == CAIRO_EXTEND_REPEAT; + cairo_status_t status; - status = _cairo_surface_to_cgimage ((cairo_surface_t *) surface, pat_surf, &img); - if (status) - return DO_UNSUPPORTED; - if (img == NULL) - return DO_NOTHING; + cairo_matrix_invert(&m); + _cairo_quartz_cairo_matrix_to_quartz (&m, &state->transform); - CGContextSetRGBFillColor (surface->cgContext, 0, 0, 0, 1); + /* Draw nonrepeating CGLayer surface using DO_LAYER */ + if (!repeat && cairo_surface_get_type (pat_surf) == CAIRO_SURFACE_TYPE_QUARTZ) { + cairo_quartz_surface_t *quartz_surf = (cairo_quartz_surface_t *) pat_surf; + if (quartz_surf->cgLayer) { + state->imageRect = CGRectMake (0, 0, quartz_surf->extents.width, quartz_surf->extents.height); + state->layer = quartz_surf->cgLayer; + state->action = DO_LAYER; + return; + } + } + + status = _cairo_surface_to_cgimage (pat_surf, &img); + if (status) { + state->action = DO_UNSUPPORTED; + return; + } + if (img == NULL) { + state->action = DO_NOTHING; + return; + } - surface->sourceImage = img; + /* XXXroc what is this for? */ + CGContextSetRGBFillColor (surface->cgContext, 0, 0, 0, 1); - cairo_matrix_invert(&m); - _cairo_quartz_cairo_matrix_to_quartz (&m, &surface->sourceTransform); + state->image = img; is_bounded = _cairo_surface_get_extents (pat_surf, &extents); assert (is_bounded); - if (source->extend == CAIRO_EXTEND_NONE) { - surface->sourceImageRect = CGRectMake (0, 0, extents.width, extents.height); - return DO_IMAGE; + if (!repeat) { + state->imageRect = CGRectMake (0, 0, extents.width, extents.height); + state->action = DO_IMAGE; + return; } /* Quartz seems to tile images at pixel-aligned regions only -- this @@ -1624,8 +1684,8 @@ _cairo_quartz_setup_source (cairo_quartz * epsilon), and if not, fall back to the CGPattern type. */ - xform = CGAffineTransformConcat (CGContextGetCTM (surface->cgContext), - surface->sourceTransform); + xform = CGAffineTransformConcat (CGContextGetCTM (context), + state->transform); srcRect = CGRectMake (0, 0, extents.width, extents.height); srcRect = CGRectApplyAffineTransform (srcRect, xform); @@ -1646,101 +1706,218 @@ _cairo_quartz_setup_source (cairo_quartz srcRect = CGRectApplyAffineTransform (srcRect, xform); - surface->sourceImageRect = srcRect; - - return DO_TILED_IMAGE; + state->imageRect = srcRect; + state->action = DO_TILED_IMAGE; + return; } /* Fall through to generic SURFACE case */ } - if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { - cairo_quartz_float_t patternAlpha = 1.0f; - CGColorSpaceRef patternSpace; - CGPatternRef pattern; - cairo_int_status_t status; - - status = _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (surface, source, &pattern); - if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) - return DO_NOTHING; - if (status) - return DO_UNSUPPORTED; - - // 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); - - patternSpace = CGColorSpaceCreatePattern(NULL); - CGContextSetFillColorSpace (surface->cgContext, patternSpace); - CGContextSetFillPattern (surface->cgContext, pattern, &patternAlpha); - CGContextSetStrokeColorSpace (surface->cgContext, patternSpace); - CGContextSetStrokePattern (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; + CGFloat patternAlpha = 1.0f; + CGColorSpaceRef patternSpace; + CGPatternRef pattern; + cairo_int_status_t status; - return DO_PATTERN; + status = _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (surface, source, &pattern); + if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) { + state->action = DO_NOTHING; + return; + } + if (status) { + state->action = DO_UNSUPPORTED; + return; } - return DO_UNSUPPORTED; + patternSpace = CGColorSpaceCreatePattern (NULL); + CGContextSetFillColorSpace (context, patternSpace); + CGContextSetFillPattern (context, pattern, &patternAlpha); + CGContextSetStrokeColorSpace (context, patternSpace); + CGContextSetStrokePattern (context, 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 (context, CGSizeMake(0,0)); + + state->pattern = pattern; + state->action = DO_PATTERN; + return; } +/** + * Call this before any operation that can modify the contents of a + * cairo_quartz_surface_t. + */ static void -_cairo_quartz_teardown_source (cairo_quartz_surface_t *surface, - const cairo_pattern_t *source) +_cairo_quartz_surface_will_change (cairo_quartz_surface_t *surface) { - CGContextSetInterpolationQuality (surface->cgContext, surface->oldInterpolationQuality); + if (surface->bitmapContextImage) { + CGImageRelease (surface->bitmapContextImage); + surface->bitmapContextImage = NULL; + } +} - if (surface->sourceImage) { - CGImageRelease(surface->sourceImage); - surface->sourceImage = NULL; +/** + * Sets up internal state to be used to draw the source mask, stored in + * cairo_quartz_state_t. Guarantees to call CGContextSaveGState on + * surface->cgContext. + */ +static cairo_quartz_drawing_state_t +_cairo_quartz_setup_state (cairo_quartz_surface_t *surface, + const cairo_pattern_t *source, + cairo_operator_t op, + cairo_rectangle_int_t *extents) +{ + CGContextRef context = surface->cgContext; + cairo_quartz_drawing_state_t state; + cairo_status_t status; - cairo_surface_destroy(surface->sourceImageSurface); - surface->sourceImageSurface = NULL; + state.context = context; + state.image = NULL; + state.imageSurface = NULL; + state.layer = NULL; + state.shading = NULL; + state.pattern = NULL; + + _cairo_quartz_surface_will_change (surface); + + // 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(context); + + CGContextSetInterpolationQuality (context, _cairo_quartz_filter_to_quartz (source->filter)); + + status = _cairo_quartz_surface_set_cairo_operator (surface, op); + if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) { + state.action = DO_NOTHING; + return state; + } + if (status) { + state.action = DO_UNSUPPORTED; + return state; } - if (surface->sourceShading) { - CGShadingRelease(surface->sourceShading); - surface->sourceShading = NULL; + if (source->type == CAIRO_PATTERN_TYPE_SOLID) { + cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source; + + CGContextSetRGBStrokeColor (context, + solid->color.red, + solid->color.green, + solid->color.blue, + solid->color.alpha); + CGContextSetRGBFillColor (context, + solid->color.red, + solid->color.green, + solid->color.blue, + solid->color.alpha); + + state.action = DO_SOLID; + return state; + } + + if (source->type == CAIRO_PATTERN_TYPE_LINEAR) { + const cairo_linear_pattern_t *lpat = (const cairo_linear_pattern_t *)source; + _cairo_quartz_setup_linear_source (surface, lpat, extents, &state); + return state; + } + + if (source->type == CAIRO_PATTERN_TYPE_RADIAL) { + const cairo_radial_pattern_t *rpat = (const cairo_radial_pattern_t *)source; + _cairo_quartz_setup_radial_source (surface, rpat, extents, &state); + return state; } - if (surface->sourcePattern) { - CGPatternRelease(surface->sourcePattern); - // To tear down the pattern and colorspace - CGContextRestoreGState(surface->cgContext); + if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { + if (op == CAIRO_OPERATOR_OVER && _cairo_pattern_is_opaque (source, NULL) && + CGContextGetAlphaPtr && + CGContextGetAlphaPtr (surface->cgContext) == 1.0) { + // Quartz won't touch pixels outside the bounds of the + // source surface, so we can just go ahead and use Copy here + // to accelerate things. + // Quartz won't necessarily be able to do this optimization internally; + // for CGLayer surfaces, we can know all the pixels are opaque + // (because it's CONTENT_COLOR), but Quartz won't know. + CGContextSetCompositeOperation (context, kPrivateCGCompositeCopy); + } - surface->sourcePattern = NULL; + const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source; + _cairo_quartz_setup_surface_source (surface, spat, extents, &state); + return state; } -} + state.action = DO_UNSUPPORTED; + return state; +} +/** + * 1) Tears down internal state used to draw the source + * 2) Does CGContextRestoreGState(state->context) + */ static void -_cairo_quartz_draw_image (cairo_quartz_surface_t *surface, cairo_operator_t op, cairo_quartz_action_t action) +_cairo_quartz_teardown_state (cairo_quartz_drawing_state_t *state) { - assert (surface && surface->sourceImage && (action == DO_IMAGE || action == DO_TILED_IMAGE)); + if (state->image) { + CGImageRelease(state->image); + } - CGContextConcatCTM (surface->cgContext, surface->sourceTransform); - CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height); - CGContextScaleCTM (surface->cgContext, 1, -1); - - if (action == DO_IMAGE) { - CGContextDrawImage (surface->cgContext, surface->sourceImageRect, surface->sourceImage); - if (!_cairo_operator_bounded_by_source(op)) { - CGContextBeginPath (surface->cgContext); - CGContextAddRect (surface->cgContext, surface->sourceImageRect); - CGContextAddRect (surface->cgContext, CGContextGetClipBoundingBox (surface->cgContext)); - CGContextSetRGBFillColor (surface->cgContext, 0, 0, 0, 0); - CGContextEOFillPath (surface->cgContext); + if (state->imageSurface) { + cairo_surface_destroy(state->imageSurface); + } + + if (state->shading) { + CGShadingRelease(state->shading); + } + + if (state->pattern) { + CGPatternRelease(state->pattern); + } + + CGContextRestoreGState(state->context); +} + + +static void +_cairo_quartz_draw_image (cairo_quartz_drawing_state_t *state, cairo_operator_t op) +{ + assert (state && + ((state->image && (state->action == DO_IMAGE || state->action == DO_TILED_IMAGE)) || + (state->layer && state->action == DO_LAYER))); + + CGContextConcatCTM (state->context, state->transform); + CGContextTranslateCTM (state->context, 0, state->imageRect.size.height); + CGContextScaleCTM (state->context, 1, -1); + + if (state->action == DO_TILED_IMAGE) { + CGContextDrawTiledImagePtr (state->context, state->imageRect, state->image); + /* no need to worry about unbounded operators, since tiled images + fill the entire clip region */ + } else { + if (state->action == DO_LAYER) { + /* Note that according to Apple docs it's completely legal + * to draw a CGLayer to any CGContext, even one it wasn't + * created for. + */ + CGContextSetInterpolationQuality (state->context, kCGInterpolationNone); + CGContextDrawLayerAtPoint (state->context, state->imageRect.origin, + state->layer); + } else { + CGContextDrawImage (state->context, state->imageRect, state->image); + } + + /* disable this EXTEND_NONE correctness code because we use this path + * for both EXTEND_NONE and EXTEND_PAD */ + if (0 && !_cairo_operator_bounded_by_source (op)) { + CGContextBeginPath (state->context); + CGContextAddRect (state->context, state->imageRect); + CGContextAddRect (state->context, CGContextGetClipBoundingBox (state->context)); + CGContextSetRGBFillColor (state->context, 0, 0, 0, 0); + CGContextEOFillPath (state->context); } - } else - CGContextDrawTiledImagePtr (surface->cgContext, surface->sourceImageRect, surface->sourceImage); + } } @@ -1762,6 +1939,7 @@ _cairo_quartz_get_image (cairo_quartz_su } if (surface->imageSurfaceEquiv) { + CGContextFlush(surface->cgContext); *image_out = (cairo_image_surface_t*) cairo_surface_reference(surface->imageSurfaceEquiv); return CAIRO_STATUS_SUCCESS; } @@ -1773,6 +1951,7 @@ _cairo_quartz_get_image (cairo_quartz_su CGColorSpaceRef colorspace; unsigned int color_comps; + CGContextFlush(surface->cgContext); imageData = (unsigned char *) CGBitmapContextGetData(surface->cgContext); #ifdef USE_10_3_WORKAROUNDS @@ -1860,53 +2039,79 @@ _cairo_quartz_surface_finish (void *abst surface->cgContext = NULL; + if (surface->bitmapContextImage) { + CGImageRelease (surface->bitmapContextImage); + surface->bitmapContextImage = NULL; + } + if (surface->imageSurfaceEquiv) { + if (surface->ownsData) + _cairo_image_surface_assume_ownership_of_data (surface->imageSurfaceEquiv); cairo_surface_destroy (surface->imageSurfaceEquiv); surface->imageSurfaceEquiv = NULL; + } else if (surface->imageData && surface->ownsData) { + free (surface->imageData); } - if (surface->imageData) { - free (surface->imageData); - surface->imageData = NULL; + surface->imageData = NULL; + + if (surface->cgLayer) { + CGLayerRelease (surface->cgLayer); } return CAIRO_STATUS_SUCCESS; } static cairo_status_t -_cairo_quartz_surface_acquire_source_image (void *abstract_surface, - cairo_image_surface_t **image_out, - void **image_extra) +_cairo_quartz_surface_acquire_image (void *abstract_surface, + cairo_image_surface_t **image_out, + void **image_extra) { cairo_int_status_t status; cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; - //ND((stderr, "%p _cairo_quartz_surface_acquire_source_image\n", surface)); - - status = _cairo_quartz_get_image (surface, image_out); - if (status) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); - *image_extra = NULL; - return CAIRO_STATUS_SUCCESS; -} + /* ND((stderr, "%p _cairo_quartz_surface_acquire_image\n", surface)); */ -static cairo_surface_t * -_cairo_quartz_surface_snapshot (void *abstract_surface) -{ - cairo_int_status_t status; - cairo_quartz_surface_t *surface = abstract_surface; - cairo_image_surface_t *image; + status = _cairo_quartz_get_image (surface, image_out); - if (surface->imageSurfaceEquiv) - return NULL; + if (status == CAIRO_INT_STATUS_UNSUPPORTED && surface->cgLayer) { + /* copy the layer into a Quartz bitmap context so we can get the data */ + cairo_surface_t *tmp = + cairo_quartz_surface_create (CAIRO_FORMAT_ARGB32, + surface->extents.width, + surface->extents.height); + cairo_quartz_surface_t *tmp_surface = (cairo_quartz_surface_t *) tmp; + + /* if surface creation failed, we won't have a Quartz surface here */ + if (cairo_surface_get_type (tmp) == CAIRO_SURFACE_TYPE_QUARTZ && + tmp_surface->imageSurfaceEquiv) { + CGContextSaveGState (tmp_surface->cgContext); + CGContextTranslateCTM (tmp_surface->cgContext, 0, surface->extents.height); + CGContextScaleCTM (tmp_surface->cgContext, 1, -1); + /* Note that according to Apple docs it's completely legal + * to draw a CGLayer to any CGContext, even one it wasn't + * created for. + */ + CGContextDrawLayerAtPoint (tmp_surface->cgContext, + CGPointMake (0.0, 0.0), + surface->cgLayer); + CGContextRestoreGState (tmp_surface->cgContext); + + *image_out = (cairo_image_surface_t*) + cairo_surface_reference(tmp_surface->imageSurfaceEquiv); + *image_extra = tmp; + status = CAIRO_STATUS_SUCCESS; + } else { + cairo_surface_destroy (tmp); + } + } - status = _cairo_quartz_get_image (surface, &image); - if (unlikely (status)) - return _cairo_surface_create_in_error (CAIRO_STATUS_NO_MEMORY); + if (status) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); - return &image->base; + return CAIRO_STATUS_SUCCESS; } static void @@ -1915,6 +2120,10 @@ _cairo_quartz_surface_release_source_ima void *image_extra) { cairo_surface_destroy ((cairo_surface_t *) image); + + if (image_extra) { + cairo_surface_destroy ((cairo_surface_t *) image_extra); + } } @@ -1926,18 +2135,16 @@ _cairo_quartz_surface_acquire_dest_image void **image_extra) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; - cairo_int_status_t status; ND((stderr, "%p _cairo_quartz_surface_acquire_dest_image\n", surface)); - status = _cairo_quartz_get_image (surface, image_out); - if (status) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); - *image_rect = surface->extents; *image_extra = NULL; - return CAIRO_STATUS_SUCCESS; + _cairo_quartz_surface_will_change (surface); + + return _cairo_quartz_surface_acquire_image (abstract_surface, + image_out, image_extra); } static void @@ -1947,11 +2154,31 @@ _cairo_quartz_surface_release_dest_image cairo_rectangle_int_t *image_rect, void *image_extra) { - //cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; - - //ND((stderr, "%p _cairo_quartz_surface_release_dest_image\n", surface)); + /* ND((stderr, "%p _cairo_quartz_surface_release_dest_image\n", surface)); */ cairo_surface_destroy ((cairo_surface_t *) image); + + if (image_extra) { + /* we need to write the data from the temp surface back to the layer */ + cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; + cairo_quartz_surface_t *tmp_surface = (cairo_quartz_surface_t *) image_extra; + CGImageRef img; + cairo_status_t status = _cairo_surface_to_cgimage (&tmp_surface->base, &img); + if (status) { + cairo_surface_destroy (&tmp_surface->base); + return; + } + + CGContextSaveGState (surface->cgContext); + CGContextTranslateCTM (surface->cgContext, 0, surface->extents.height); + CGContextScaleCTM (surface->cgContext, 1, -1); + CGContextDrawImage (surface->cgContext, + CGRectMake (0.0, 0.0, surface->extents.width, surface->extents.height), + img); + CGContextRestoreGState (surface->cgContext); + + cairo_surface_destroy (&tmp_surface->base); + } } static cairo_surface_t * @@ -1960,10 +2187,13 @@ _cairo_quartz_surface_create_similar (vo int width, int height) { - /*cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;*/ - + cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_format_t format; + if (surface->cgLayer) + return cairo_quartz_surface_create_cg_layer (abstract_surface, content, + width, height); + if (content == CAIRO_CONTENT_COLOR_ALPHA) format = CAIRO_FORMAT_ARGB32; else if (content == CAIRO_CONTENT_COLOR) @@ -2027,7 +2257,7 @@ _cairo_quartz_surface_clone_similar (voi } } - status = _cairo_surface_to_cgimage ((cairo_surface_t*) abstract_surface, src, &quartz_image); + status = _cairo_surface_to_cgimage (src, &quartz_image); if (status) return CAIRO_INT_STATUS_UNSUPPORTED; @@ -2087,7 +2317,7 @@ _cairo_quartz_surface_paint_cg (void *ab { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; - cairo_quartz_action_t action; + cairo_quartz_drawing_state_t state; ND((stderr, "%p _cairo_quartz_surface_paint op %d source->type %d\n", surface, op, source->type)); @@ -2098,31 +2328,24 @@ _cairo_quartz_surface_paint_cg (void *ab if (unlikely (rv)) return rv; - rv = _cairo_quartz_surface_set_cairo_operator (surface, op); - if (unlikely (rv)) - return rv == CAIRO_INT_STATUS_NOTHING_TO_DO ? CAIRO_STATUS_SUCCESS : rv; + state = _cairo_quartz_setup_state (surface, source, op, NULL); - action = _cairo_quartz_setup_source (surface, source, NULL); - - 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) { - CGContextSaveGState (surface->cgContext); - CGContextConcatCTM (surface->cgContext, surface->sourceTransform); - CGContextDrawShading (surface->cgContext, surface->sourceShading); - CGContextRestoreGState (surface->cgContext); - } else if (action == DO_IMAGE || action == DO_TILED_IMAGE) { - CGContextSaveGState (surface->cgContext); - _cairo_quartz_draw_image (surface, op, action); - CGContextRestoreGState (surface->cgContext); - } else if (action != DO_NOTHING) { + if (state.action == DO_SOLID || state.action == DO_PATTERN) { + CGContextFillRect (state.context, CGRectMake(surface->extents.x, + surface->extents.y, + surface->extents.width, + surface->extents.height)); + } else if (state.action == DO_SHADING) { + CGContextConcatCTM (state.context, state.transform); + CGContextDrawShading (state.context, state.shading); + } else if (state.action == DO_IMAGE || state.action == DO_TILED_IMAGE || + state.action == DO_LAYER) { + _cairo_quartz_draw_image (&state, op); + } else if (state.action != DO_NOTHING) { rv = CAIRO_INT_STATUS_UNSUPPORTED; } - _cairo_quartz_teardown_source (surface, source); + _cairo_quartz_teardown_state (&state); ND((stderr, "-- paint\n")); return rv; @@ -2186,7 +2409,7 @@ _cairo_quartz_surface_fill_cg (void *abs { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; - cairo_quartz_action_t action; + cairo_quartz_drawing_state_t state; CGPathRef path_for_unbounded = NULL; ND((stderr, "%p _cairo_quartz_surface_fill op %d source->type %d\n", surface, op, source->type)); @@ -2198,14 +2421,6 @@ _cairo_quartz_surface_fill_cg (void *abs if (unlikely (rv)) return rv; - rv = _cairo_quartz_surface_set_cairo_operator (surface, op); - if (unlikely (rv)) - return rv == CAIRO_INT_STATUS_NOTHING_TO_DO ? CAIRO_STATUS_SUCCESS : rv; - - CGContextSaveGState (surface->cgContext); - - CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); - if (_cairo_quartz_source_needs_extents (source)) { /* We don't need precise extents since these are only used to @@ -2213,46 +2428,47 @@ _cairo_quartz_surface_fill_cg (void *abs object. */ cairo_rectangle_int_t path_extents; _cairo_path_fixed_approximate_fill_extents (path, &path_extents); - action = _cairo_quartz_setup_source (surface, source, &path_extents); + state = _cairo_quartz_setup_state (surface, source, op, &path_extents); } else { - action = _cairo_quartz_setup_source (surface, source, NULL); + state = _cairo_quartz_setup_state (surface, source, op, NULL); } - _cairo_quartz_cairo_path_to_quartz_context (path, surface->cgContext); + CGContextSetShouldAntialias (state.context, (antialias != CAIRO_ANTIALIAS_NONE)); + + _cairo_quartz_cairo_path_to_quartz_context (path, state.context); if (!_cairo_operator_bounded_by_mask(op) && CGContextCopyPathPtr) - path_for_unbounded = CGContextCopyPathPtr (surface->cgContext); + path_for_unbounded = CGContextCopyPathPtr (state.context); - if (action == DO_SOLID || action == DO_PATTERN) { + if (state.action == DO_SOLID || state.action == DO_PATTERN) { if (fill_rule == CAIRO_FILL_RULE_WINDING) - CGContextFillPath (surface->cgContext); + CGContextFillPath (state.context); else - CGContextEOFillPath (surface->cgContext); - } else if (action == DO_SHADING) { + CGContextEOFillPath (state.context); + } else if (state.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); + CGContextClip (state.context); else - CGContextEOClip (surface->cgContext); + CGContextEOClip (state.context); - CGContextConcatCTM (surface->cgContext, surface->sourceTransform); - CGContextDrawShading (surface->cgContext, surface->sourceShading); - } else if (action == DO_IMAGE || action == DO_TILED_IMAGE) { + CGContextConcatCTM (state.context, state.transform); + CGContextDrawShading (state.context, state.shading); + } else if (state.action == DO_IMAGE || state.action == DO_TILED_IMAGE || + state.action == DO_LAYER) { if (fill_rule == CAIRO_FILL_RULE_WINDING) - CGContextClip (surface->cgContext); + CGContextClip (state.context); else - CGContextEOClip (surface->cgContext); + CGContextEOClip (state.context); - _cairo_quartz_draw_image (surface, op, action); - } else if (action != DO_NOTHING) { + _cairo_quartz_draw_image (&state, op); + } else if (state.action != DO_NOTHING) { rv = CAIRO_INT_STATUS_UNSUPPORTED; } - _cairo_quartz_teardown_source (surface, source); - - CGContextRestoreGState (surface->cgContext); + _cairo_quartz_teardown_state (&state); if (path_for_unbounded) { unbounded_op_data_t ub; @@ -2319,7 +2535,7 @@ _cairo_quartz_surface_stroke_cg (void *a { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; - cairo_quartz_action_t action; + cairo_quartz_drawing_state_t state; CGAffineTransform origCTM, strokeTransform; CGPathRef path_for_unbounded = NULL; @@ -2336,16 +2552,25 @@ _cairo_quartz_surface_stroke_cg (void *a if (unlikely (rv)) return rv == CAIRO_INT_STATUS_NOTHING_TO_DO ? CAIRO_STATUS_SUCCESS : rv; + if (_cairo_quartz_source_needs_extents (source)) + { + cairo_rectangle_int_t path_extents; + _cairo_path_fixed_approximate_stroke_extents (path, style, ctm, &path_extents); + state = _cairo_quartz_setup_state (surface, source, op, &path_extents); + } else { + state = _cairo_quartz_setup_state (surface, source, op, NULL); + } + // Turning antialiasing off used to cause misrendering with // single-pixel lines (e.g. 20,10.5 -> 21,10.5 end up being rendered as 2 pixels). // That's been since fixed in at least 10.5, and in the latest 10.4 dot releases. - CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); - CGContextSetLineWidth (surface->cgContext, style->line_width); - CGContextSetLineCap (surface->cgContext, _cairo_quartz_cairo_line_cap_to_quartz (style->line_cap)); - CGContextSetLineJoin (surface->cgContext, _cairo_quartz_cairo_line_join_to_quartz (style->line_join)); - CGContextSetMiterLimit (surface->cgContext, style->miter_limit); + CGContextSetShouldAntialias (state.context, (antialias != CAIRO_ANTIALIAS_NONE)); + CGContextSetLineWidth (state.context, style->line_width); + CGContextSetLineCap (state.context, _cairo_quartz_cairo_line_cap_to_quartz (style->line_cap)); + CGContextSetLineJoin (state.context, _cairo_quartz_cairo_line_join_to_quartz (style->line_join)); + CGContextSetMiterLimit (state.context, style->miter_limit); - origCTM = CGContextGetCTM (surface->cgContext); + origCTM = CGContextGetCTM (state.context); if (style->dash && style->num_dashes) { #define STATIC_DASH 32 @@ -2368,72 +2593,62 @@ _cairo_quartz_surface_stroke_cg (void *a if (fdash != sdash) free (fdash); } else - CGContextSetLineDash (surface->cgContext, 0, NULL, 0); + CGContextSetLineDash (state.context, 0, NULL, 0); - CGContextSaveGState (surface->cgContext); + _cairo_quartz_cairo_path_to_quartz_context (path, state.context); - if (_cairo_quartz_source_needs_extents (source)) - { - cairo_rectangle_int_t path_extents; - _cairo_path_fixed_approximate_stroke_extents (path, style, ctm, &path_extents); - action = _cairo_quartz_setup_source (surface, source, &path_extents); - } else { - action = _cairo_quartz_setup_source (surface, source, NULL); - } - - _cairo_quartz_cairo_path_to_quartz_context (path, surface->cgContext); + _cairo_quartz_cairo_matrix_to_quartz (ctm, &strokeTransform); + CGContextConcatCTM (state.context, strokeTransform); if (!_cairo_operator_bounded_by_mask (op) && CGContextCopyPathPtr) - path_for_unbounded = CGContextCopyPathPtr (surface->cgContext); - - _cairo_quartz_cairo_matrix_to_quartz (ctm, &strokeTransform); - CGContextConcatCTM (surface->cgContext, strokeTransform); + path_for_unbounded = CGContextCopyPathPtr (state.context); - if (action == DO_SOLID || action == DO_PATTERN) { - CGContextStrokePath (surface->cgContext); - } else if (action == DO_IMAGE || action == DO_TILED_IMAGE) { - CGContextReplacePathWithStrokedPath (surface->cgContext); - CGContextClip (surface->cgContext); - - CGContextSetCTM (surface->cgContext, origCTM); - _cairo_quartz_draw_image (surface, op, action); - } else if (action == DO_SHADING) { - CGContextReplacePathWithStrokedPath (surface->cgContext); - CGContextClip (surface->cgContext); - - CGContextSetCTM (surface->cgContext, origCTM); - - CGContextConcatCTM (surface->cgContext, surface->sourceTransform); - CGContextDrawShading (surface->cgContext, surface->sourceShading); - } else if (action != DO_NOTHING) { + if (state.action == DO_SOLID || state.action == DO_PATTERN) { + CGContextStrokePath (state.context); + } else if (state.action == DO_IMAGE || state.action == DO_TILED_IMAGE || + state.action == DO_LAYER) { + CGContextReplacePathWithStrokedPath (state.context); + CGContextClip (state.context); + + CGContextSetCTM (state.context, origCTM); + _cairo_quartz_draw_image (&state, op); + } else if (state.action == DO_SHADING) { + CGContextReplacePathWithStrokedPath (state.context); + CGContextClip (state.context); + + CGContextSetCTM (state.context, origCTM); + + CGContextConcatCTM (state.context, state.transform); + CGContextDrawShading (state.context, state.shading); + } else if (state.action != DO_NOTHING) { rv = CAIRO_INT_STATUS_UNSUPPORTED; + goto BAIL; } - _cairo_quartz_teardown_source (surface, source); - - CGContextRestoreGState (surface->cgContext); - if (path_for_unbounded) { unbounded_op_data_t ub; ub.op = UNBOUNDED_STROKE_FILL; ub.u.stroke_fill.fill_rule = CAIRO_FILL_RULE_WINDING; - CGContextBeginPath (surface->cgContext); - CGContextAddPath (surface->cgContext, path_for_unbounded); + CGContextBeginPath (state.context); + CGContextAddPath (state.context, path_for_unbounded); CGPathRelease (path_for_unbounded); - CGContextSaveGState (surface->cgContext); - CGContextConcatCTM (surface->cgContext, strokeTransform); - CGContextReplacePathWithStrokedPath (surface->cgContext); - CGContextRestoreGState (surface->cgContext); + CGContextSaveGState (state.context); + CGContextConcatCTM (state.context, strokeTransform); + CGContextReplacePathWithStrokedPath (state.context); + CGContextRestoreGState (state.context); - ub.u.stroke_fill.cgPath = CGContextCopyPathPtr (surface->cgContext); + ub.u.stroke_fill.cgPath = CGContextCopyPathPtr (state.context); _cairo_quartz_fixup_unbounded_operation (surface, &ub, antialias); CGPathRelease (ub.u.stroke_fill.cgPath); } + BAIL: + _cairo_quartz_teardown_state (&state); + ND((stderr, "-- stroke\n")); return rv; } @@ -2490,18 +2705,22 @@ _cairo_quartz_surface_show_glyphs_cg (vo CGGlyph glyphs_static[STATIC_BUF_SIZE]; CGSize cg_advances_static[STATIC_BUF_SIZE]; CGGlyph *cg_glyphs = &glyphs_static[0]; + /* We'll use the cg_advances array for either advances or positions, + depending which API we're using to actually draw. The types involved + have the same size, so this is safe. */ CGSize *cg_advances = &cg_advances_static[0]; cairo_rectangle_int_t glyph_extents; cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; - cairo_quartz_action_t action; + cairo_quartz_drawing_state_t state; cairo_quartz_float_t xprev, yprev; int i; CGFontRef cgfref = NULL; cairo_bool_t isClipping = FALSE; cairo_bool_t didForceFontSmoothing = FALSE; + cairo_antialias_t effective_antialiasing; if (IS_EMPTY(surface)) return CAIRO_STATUS_SUCCESS; @@ -2516,54 +2735,51 @@ _cairo_quartz_surface_show_glyphs_cg (vo if (unlikely (rv)) return rv; - rv = _cairo_quartz_surface_set_cairo_operator (surface, op); - if (unlikely (rv)) - return rv == CAIRO_INT_STATUS_NOTHING_TO_DO ? CAIRO_STATUS_SUCCESS : rv; - - CGContextSaveGState (surface->cgContext); - if (_cairo_quartz_source_needs_extents (source) && !_cairo_scaled_font_glyph_device_extents (scaled_font, glyphs, num_glyphs, &glyph_extents, NULL)) { - action = _cairo_quartz_setup_source (surface, source, &glyph_extents); + state = _cairo_quartz_setup_state (surface, source, op, &glyph_extents); } else { - action = _cairo_quartz_setup_source (surface, source, NULL); + state = _cairo_quartz_setup_state (surface, source, op, NULL); } - if (action == DO_SOLID || action == DO_PATTERN) { - CGContextSetTextDrawingMode (surface->cgContext, kCGTextFill); - } else if (action == DO_IMAGE || action == DO_TILED_IMAGE || action == DO_SHADING) { - CGContextSetTextDrawingMode (surface->cgContext, kCGTextClip); + if (state.action == DO_SOLID || state.action == DO_PATTERN) { + CGContextSetTextDrawingMode (state.context, kCGTextFill); + } else if (state.action == DO_IMAGE || state.action == DO_TILED_IMAGE || + state.action == DO_SHADING || state.action == DO_LAYER) { + CGContextSetTextDrawingMode (state.context, kCGTextClip); isClipping = TRUE; } else { - if (action != DO_NOTHING) + if (state.action != DO_NOTHING) rv = CAIRO_INT_STATUS_UNSUPPORTED; goto BAIL; } /* this doesn't addref */ cgfref = _cairo_quartz_scaled_font_get_cg_font_ref (scaled_font); - CGContextSetFont (surface->cgContext, cgfref); - CGContextSetFontSize (surface->cgContext, 1.0); + CGContextSetFont (state.context, cgfref); + CGContextSetFontSize (state.context, 1.0); + + effective_antialiasing = scaled_font->options.antialias; switch (scaled_font->options.antialias) { case CAIRO_ANTIALIAS_SUBPIXEL: - CGContextSetShouldAntialias (surface->cgContext, TRUE); - CGContextSetShouldSmoothFonts (surface->cgContext, TRUE); + CGContextSetShouldAntialias (state.context, TRUE); + CGContextSetShouldSmoothFonts (state.context, TRUE); if (CGContextSetAllowsFontSmoothingPtr && - !CGContextGetAllowsFontSmoothingPtr (surface->cgContext)) + !CGContextGetAllowsFontSmoothingPtr (state.context)) { didForceFontSmoothing = TRUE; - CGContextSetAllowsFontSmoothingPtr (surface->cgContext, TRUE); + CGContextSetAllowsFontSmoothingPtr (state.context, TRUE); } break; case CAIRO_ANTIALIAS_NONE: - CGContextSetShouldAntialias (surface->cgContext, FALSE); + CGContextSetShouldAntialias (state.context, FALSE); break; case CAIRO_ANTIALIAS_GRAY: - CGContextSetShouldAntialias (surface->cgContext, TRUE); - CGContextSetShouldSmoothFonts (surface->cgContext, FALSE); + CGContextSetShouldAntialias (state.context, TRUE); + CGContextSetShouldSmoothFonts (state.context, FALSE); break; case CAIRO_ANTIALIAS_DEFAULT: /* Don't do anything */ @@ -2584,57 +2800,84 @@ _cairo_quartz_surface_show_glyphs_cg (vo } } + /* scale(1,-1) * scaled_font->scale */ textTransform = CGAffineTransformMake (scaled_font->scale.xx, scaled_font->scale.yx, -scaled_font->scale.xy, -scaled_font->scale.yy, 0, 0); - _cairo_quartz_cairo_matrix_to_quartz (&scaled_font->scale_inverse, &invTextTransform); - CGContextSetTextMatrix (surface->cgContext, CGAffineTransformIdentity); + /* scaled_font->scale_inverse * scale(1,-1) */ + invTextTransform = CGAffineTransformMake (scaled_font->scale_inverse.xx, + -scaled_font->scale_inverse.yx, + scaled_font->scale_inverse.xy, + -scaled_font->scale_inverse.yy, + 0.0, 0.0); - /* Convert our glyph positions to glyph advances. We need n-1 advances, - * since the advance at index 0 is applied after glyph 0. */ - xprev = glyphs[0].x; - yprev = glyphs[0].y; - - cg_glyphs[0] = glyphs[0].index; - - for (i = 1; i < num_glyphs; i++) { - cairo_quartz_float_t xf = glyphs[i].x; - cairo_quartz_float_t yf = glyphs[i].y; - cg_glyphs[i] = glyphs[i].index; - cg_advances[i - 1] = CGSizeApplyAffineTransform(CGSizeMake (xf - xprev, yf - yprev), invTextTransform); - xprev = xf; - yprev = yf; - } + CGContextSetTextMatrix (state.context, CGAffineTransformIdentity); /* Translate to the first glyph's position before drawing */ - ctm = CGContextGetCTM (surface->cgContext); - CGContextTranslateCTM (surface->cgContext, glyphs[0].x, glyphs[0].y); - CGContextConcatCTM (surface->cgContext, textTransform); - - CGContextShowGlyphsWithAdvances (surface->cgContext, - cg_glyphs, - cg_advances, - num_glyphs); - - CGContextSetCTM (surface->cgContext, ctm); + ctm = CGContextGetCTM (state.context); + CGContextTranslateCTM (state.context, glyphs[0].x, glyphs[0].y); + CGContextConcatCTM (state.context, textTransform); + + if (CTFontDrawGlyphsPtr) { + /* If CTFontDrawGlyphs is available (i.e. OS X 10.7 or later), we want to use + * that in preference to CGContextShowGlyphsWithAdvances so that colored-bitmap + * fonts like Apple Color Emoji will render properly. + * For this, we need to convert our glyph positions to Core Graphics's CGPoint. + * We borrow the cg_advances array, as CGPoint and CGSize are the same size. */ + + CGPoint *cg_positions = (CGPoint*) cg_advances; + cairo_quartz_float_t origin_x = glyphs[0].x; + cairo_quartz_float_t origin_y = glyphs[0].y; + + for (i = 0; i < num_glyphs; i++) { + CGPoint pt = CGPointMake (glyphs[i].x - origin_x, glyphs[i].y - origin_y); + cg_positions[i] = CGPointApplyAffineTransform (pt, invTextTransform); + cg_glyphs[i] = glyphs[i].index; + } - if (action == DO_IMAGE || action == DO_TILED_IMAGE) { - _cairo_quartz_draw_image (surface, op, action); - } else if (action == DO_SHADING) { - CGContextConcatCTM (surface->cgContext, surface->sourceTransform); - CGContextDrawShading (surface->cgContext, surface->sourceShading); + CTFontDrawGlyphsPtr (_cairo_quartz_scaled_font_get_ct_font_ref (scaled_font), + cg_glyphs, cg_positions, num_glyphs, state.context); + } else { + /* Convert our glyph positions to glyph advances. We need n-1 advances, + * since the advance at index 0 is applied after glyph 0. */ + xprev = glyphs[0].x; + yprev = glyphs[0].y; + + cg_glyphs[0] = glyphs[0].index; + + for (i = 1; i < num_glyphs; i++) { + cairo_quartz_float_t xf = glyphs[i].x; + cairo_quartz_float_t yf = glyphs[i].y; + cg_glyphs[i] = glyphs[i].index; + cg_advances[i - 1] = CGSizeApplyAffineTransform(CGSizeMake (xf - xprev, yf - yprev), invTextTransform); + xprev = xf; + yprev = yf; + } + + CGContextShowGlyphsWithAdvances (state.context, + cg_glyphs, + cg_advances, + num_glyphs); + } + + CGContextSetCTM (state.context, ctm); + + if (state.action == DO_IMAGE || state.action == DO_TILED_IMAGE || + state.action == DO_LAYER) { + _cairo_quartz_draw_image (&state, op); + } else if (state.action == DO_SHADING) { + CGContextConcatCTM (state.context, state.transform); + CGContextDrawShading (state.context, state.shading); } BAIL: - _cairo_quartz_teardown_source (surface, source); - if (didForceFontSmoothing) - CGContextSetAllowsFontSmoothingPtr (surface->cgContext, FALSE); + CGContextSetAllowsFontSmoothingPtr (state.context, FALSE); - CGContextRestoreGState (surface->cgContext); + _cairo_quartz_teardown_state (&state); if (rv == CAIRO_STATUS_SUCCESS && cgfref && @@ -2645,10 +2888,17 @@ BAIL: ub.u.show_glyphs.isClipping = isClipping; ub.u.show_glyphs.cg_glyphs = cg_glyphs; - ub.u.show_glyphs.cg_advances = cg_advances; + if (CTFontDrawGlyphsPtr) { + /* we're using Core Text API: the cg_advances array was + reused (above) for glyph positions */ + CGPoint *cg_positions = (CGPoint*) cg_advances; + ub.u.show_glyphs.u.cg_positions = cg_positions; + } else { + ub.u.show_glyphs.u.cg_advances = cg_advances; + } ub.u.show_glyphs.nglyphs = num_glyphs; ub.u.show_glyphs.textTransform = textTransform; - ub.u.show_glyphs.font = cgfref; + ub.u.show_glyphs.scaled_font = scaled_font; ub.u.show_glyphs.origin = CGPointMake (glyphs[0].x, glyphs[0].y); _cairo_quartz_fixup_unbounded_operation (surface, &ub, scaled_font->options.antialias); @@ -2717,7 +2967,7 @@ _cairo_quartz_surface_mask_with_surface cairo_status_t status = CAIRO_STATUS_SUCCESS; CGAffineTransform ctm, mask_matrix; - status = _cairo_surface_to_cgimage ((cairo_surface_t *) surface, pat_surf, &img); + status = _cairo_surface_to_cgimage (pat_surf, &img); if (status) return status; if (img == NULL) { @@ -2820,7 +3070,9 @@ _cairo_quartz_surface_mask_cg (void *abs if (unlikely (rv)) return rv; - if (mask->type == CAIRO_PATTERN_TYPE_SOLID) { + /* Using CGContextSetAlpha to implement mask alpha doesn't work for all operators. */ + if (mask->type == CAIRO_PATTERN_TYPE_SOLID && + op == CAIRO_OPERATOR_OVER) { /* This is easy; we just need to paint with the alpha. */ cairo_solid_pattern_t *solid_mask = (cairo_solid_pattern_t *) mask; @@ -2834,8 +3086,11 @@ _cairo_quartz_surface_mask_cg (void *abs /* If we have CGContextClipToMask, we can do more complex masks */ if (CGContextClipToMaskPtr) { /* For these, we can skip creating a temporary surface, since we already have one */ - if (mask->type == CAIRO_PATTERN_TYPE_SURFACE && mask->extend == CAIRO_EXTEND_NONE) + /* For some reason this doesn't work reliably on OS X 10.5. See bug 721663. */ + if (_cairo_quartz_osx_version >= 0x1060 && mask->type == CAIRO_PATTERN_TYPE_SURFACE && + mask->extend == CAIRO_EXTEND_NONE) { return _cairo_quartz_surface_mask_with_surface (surface, op, source, (cairo_surface_pattern_t *) mask, clip); + } return _cairo_quartz_surface_mask_with_generic (surface, op, source, mask, clip); } @@ -2920,13 +3175,24 @@ _cairo_quartz_surface_clipper_intersect_ return CAIRO_STATUS_SUCCESS; } +static cairo_status_t +_cairo_quartz_surface_mark_dirty_rectangle (void *abstract_surface, + int x, int y, + int width, int height) +{ + cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; + _cairo_quartz_surface_will_change (surface); + return CAIRO_STATUS_SUCCESS; +} + + // XXXtodo implement show_page; need to figure out how to handle begin/end static const struct _cairo_surface_backend cairo_quartz_surface_backend = { CAIRO_SURFACE_TYPE_QUARTZ, _cairo_quartz_surface_create_similar, _cairo_quartz_surface_finish, - _cairo_quartz_surface_acquire_source_image, + _cairo_quartz_surface_acquire_image, _cairo_quartz_surface_release_source_image, _cairo_quartz_surface_acquire_dest_image, _cairo_quartz_surface_release_dest_image, @@ -2942,7 +3208,7 @@ static const struct _cairo_surface_backe NULL, /* old_show_glyphs */ NULL, /* get_font_options */ NULL, /* flush */ - NULL, /* mark_dirty_rectangle */ + _cairo_quartz_surface_mark_dirty_rectangle, NULL, /* scaled_font_fini */ NULL, /* scaled_glyph_fini */ @@ -2952,7 +3218,7 @@ static const struct _cairo_surface_backe _cairo_quartz_surface_fill, _cairo_quartz_surface_show_glyphs, - _cairo_quartz_surface_snapshot, + NULL, /* snapshot */ NULL, /* is_similar */ NULL /* fill_stroke */ }; @@ -3004,6 +3270,9 @@ _cairo_quartz_surface_create_internal (C surface->imageData = NULL; surface->imageSurfaceEquiv = NULL; + surface->bitmapContextImage = NULL; + surface->cgLayer = NULL; + surface->ownsData = TRUE; return surface; } @@ -3056,6 +3325,81 @@ cairo_quartz_surface_create_for_cg_conte } /** + * cairo_quartz_cglayer_surface_create_similar + * @surface: The returned surface can be efficiently drawn into this + * destination surface (if tiling is not used)." + * @content: the content type of the surface + * @width: width of the surface, in pixels + * @height: height of the surface, in pixels + * + * Creates a Quartz surface backed by a CGLayer, if the given surface + * is a Quartz surface; the CGLayer is created to match the surface's + * Quartz context. Otherwise just calls cairo_surface_create_similar. + * The returned surface can be efficiently blitted to the given surface, + * but tiling and 'extend' modes other than NONE are not so efficient. + * + * Return value: the newly created surface. + * + * Since: 1.10 + **/ +cairo_surface_t * +cairo_quartz_surface_create_cg_layer (cairo_surface_t *surface, + cairo_content_t content, + unsigned int width, + unsigned int height) +{ + cairo_quartz_surface_t *surf; + CGLayerRef layer; + CGContextRef ctx; + CGContextRef cgContext; + + cgContext = cairo_quartz_surface_get_cg_context (surface); + if (!cgContext) + return cairo_surface_create_similar (surface, content, + width, height); + + + if (!_cairo_quartz_verify_surface_size(width, height)) + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_SIZE)); + + /* If we pass zero width or height into CGLayerCreateWithContext below, + * it will fail. + */ + if (width == 0 || height == 0) { + return (cairo_surface_t*) + _cairo_quartz_surface_create_internal (NULL, content, + width, height); + } + + layer = CGLayerCreateWithContext (cgContext, + CGSizeMake (width, height), + NULL); + if (!layer) + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + + ctx = CGLayerGetContext (layer); + CGContextSetInterpolationQuality (ctx, kCGInterpolationNone); + /* Flip it when we draw into it, so that when we finally composite it + * to a flipped target, the directions match and Quartz will optimize + * the composition properly + */ + CGContextTranslateCTM (ctx, 0, height); + CGContextScaleCTM (ctx, 1, -1); + + CGContextRetain (ctx); + surf = _cairo_quartz_surface_create_internal (ctx, content, + width, height); + if (surf->base.status) { + CGLayerRelease (layer); + // create_internal will have set an error + return (cairo_surface_t*) surf; + } + surf->cgLayer = layer; + + return (cairo_surface_t *) surf; +} + +/** * cairo_quartz_surface_create * @format: format of pixels in the surface to create * @width: width of the surface, in pixels @@ -3075,13 +3419,93 @@ cairo_quartz_surface_create (cairo_forma unsigned int width, unsigned int height) { + int stride; + unsigned char *data; + + if (!_cairo_quartz_verify_surface_size(width, height)) + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_SIZE)); + + if (width == 0 || height == 0) { + return (cairo_surface_t*) _cairo_quartz_surface_create_internal (NULL, _cairo_content_from_format (format), + width, height); + } + + if (format == CAIRO_FORMAT_ARGB32 || + format == CAIRO_FORMAT_RGB24) + { + stride = width * 4; + } else if (format == CAIRO_FORMAT_A8) { + stride = width; + } 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. + */ + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT)); + } else { + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT)); + } + + /* The Apple docs say that for best performance, the stride and the data + * pointer should be 16-byte aligned. malloc already aligns to 16-bytes, + * so we don't have to anything special on allocation. + */ + stride = (stride + 15) & ~15; + + data = _cairo_malloc_ab (height, stride); + if (!data) { + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + } + + /* zero the memory to match the image surface behaviour */ + memset (data, 0, height * stride); + + cairo_quartz_surface_t *surf; + surf = (cairo_quartz_surface_t *) cairo_quartz_surface_create_for_data + (data, format, width, height, stride); + if (surf->base.status) { + free (data); + return (cairo_surface_t *) surf; + } + + // We created this data, so we can delete it. + surf->ownsData = TRUE; + + return (cairo_surface_t *) surf; +} + +/** + * cairo_quartz_surface_create_for_data + * @data: a pointer to a buffer supplied by the application in which + * to write contents. This pointer must be suitably aligned for any + * kind of variable, (for example, a pointer returned by malloc). + * @format: format of pixels in the surface to create + * @width: width of the surface, in pixels + * @height: height of the surface, in pixels + * + * Creates a Quartz surface backed by a CGBitmap. The surface is + * created using the Device RGB (or Device Gray, for A8) color space. + * All Cairo operations, including those that require software + * rendering, will succeed on this surface. + * + * Return value: the newly created surface. + * + * Since: 1.12 + **/ +cairo_surface_t * +cairo_quartz_surface_create_for_data (unsigned char *data, + cairo_format_t format, + unsigned int width, + unsigned int height, + unsigned int stride) +{ cairo_quartz_surface_t *surf; CGContextRef cgc; CGColorSpaceRef cgColorspace; CGBitmapInfo bitinfo; - void *imageData; - int stride; + void *imageData = data; int bitsPerComponent; + unsigned int i; // verify width and height of surface if (!_cairo_quartz_verify_surface_size(width, height)) @@ -3102,10 +3526,8 @@ cairo_quartz_surface_create (cairo_forma else bitinfo |= kCGImageAlphaNoneSkipFirst; bitsPerComponent = 8; - stride = width * 4; } else if (format == CAIRO_FORMAT_A8) { cgColorspace = NULL; - stride = width; bitinfo = kCGImageAlphaOnly; bitsPerComponent = 8; } else if (format == CAIRO_FORMAT_A1) { @@ -3118,21 +3540,6 @@ cairo_quartz_surface_create (cairo_forma return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT)); } - /* The Apple docs say that for best performance, the stride and the data - * pointer should be 16-byte aligned. malloc already aligns to 16-bytes, - * so we don't have to anything special on allocation. - */ - stride = (stride + 15) & ~15; - - imageData = _cairo_malloc_ab (height, stride); - if (!imageData) { - CGColorSpaceRelease (cgColorspace); - return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); - } - - /* zero the memory to match the image surface behaviour */ - memset (imageData, 0, height * stride); - cgc = CGBitmapContextCreate (imageData, width, height, @@ -3161,7 +3568,19 @@ cairo_quartz_surface_create (cairo_forma } surf->imageData = imageData; - surf->imageSurfaceEquiv = cairo_image_surface_create_for_data (imageData, format, width, height, stride); + + cairo_surface_t* tmpImageSurfaceEquiv = + cairo_image_surface_create_for_data (imageData, format, + width, height, stride); + + if (cairo_surface_status (tmpImageSurfaceEquiv)) { + // Tried & failed to create an imageSurfaceEquiv! + cairo_surface_destroy (tmpImageSurfaceEquiv); + surf->imageSurfaceEquiv = NULL; + } else { + surf->imageSurfaceEquiv = tmpImageSurfaceEquiv; + surf->ownsData = FALSE; + } return (cairo_surface_t *) surf; } @@ -3193,6 +3612,74 @@ _cairo_surface_is_quartz (const cairo_su return surface->backend == &cairo_quartz_surface_backend; } +CGContextRef +cairo_quartz_get_cg_context_with_clip (cairo_t *cr) +{ + + cairo_surface_t *surface = cr->gstate->target; + cairo_clip_t *clip = &cr->gstate->clip; + cairo_status_t status; + + cairo_quartz_surface_t *quartz = (cairo_quartz_surface_t*)surface; + + if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_QUARTZ) + return NULL; + + if (!clip->path) { + if (clip->all_clipped) { + /* Save the state before we set an empty clip rect so that + * our previous clip will be restored */ + + /* _cairo_surface_clipper_set_clip doesn't deal with + * clip->all_clipped because drawing is normally discarded earlier */ + CGRect empty = {{0,0}, {0,0}}; + CGContextClipToRect (quartz->cgContext, empty); + CGContextSaveGState (quartz->cgContext); + + return quartz->cgContext; + } + + /* an empty clip is represented by NULL */ + clip = NULL; + } + + status = _cairo_surface_clipper_set_clip (&quartz->clipper, clip); + + /* Save the state after we set the clip so that it persists + * after we restore */ + CGContextSaveGState (quartz->cgContext); + + if (unlikely (status)) + return NULL; + + return quartz->cgContext; +} + +void +cairo_quartz_finish_cg_context_with_clip (cairo_t *cr) +{ + cairo_surface_t *surface = cr->gstate->target; + + cairo_quartz_surface_t *quartz = (cairo_quartz_surface_t*)surface; + + if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_QUARTZ) + return; + + CGContextRestoreGState (quartz->cgContext); +} + +cairo_surface_t * +cairo_quartz_surface_get_image (cairo_surface_t *surface) +{ + cairo_quartz_surface_t *quartz = (cairo_quartz_surface_t *)surface; + cairo_image_surface_t *image; + + if (_cairo_quartz_get_image(quartz, &image)) + return NULL; + + return (cairo_surface_t *)image; +} + /* Debug stuff */ #ifdef QUARTZ_DEBUG --- a/src/cairo-quartz.h 2012-11-13 18:20:00.000000000 -0800 +++ b/src/cairo-quartz.h 2012-11-13 18:06:56.000000000 -0800 @@ -50,6 +50,19 @@ cairo_quartz_surface_create (cairo_forma unsigned int height); cairo_public cairo_surface_t * +cairo_quartz_surface_create_for_data (unsigned char *data, + cairo_format_t format, + unsigned int width, + unsigned int height, + unsigned int stride); + +cairo_public cairo_surface_t * +cairo_quartz_surface_create_cg_layer (cairo_surface_t *surface, + cairo_content_t content, + unsigned int width, + unsigned int height); + +cairo_public cairo_surface_t * cairo_quartz_surface_create_for_cg_context (CGContextRef cgContext, unsigned int width, unsigned int height); @@ -57,6 +70,15 @@ cairo_quartz_surface_create_for_cg_conte cairo_public CGContextRef cairo_quartz_surface_get_cg_context (cairo_surface_t *surface); +cairo_public CGContextRef +cairo_quartz_get_cg_context_with_clip (cairo_t *cr); + +cairo_public void +cairo_quartz_finish_cg_context_with_clip (cairo_t *cr); + +cairo_public cairo_surface_t * +cairo_quartz_surface_get_image (cairo_surface_t *surface); + #if CAIRO_HAS_QUARTZ_FONT /* @@ -66,8 +88,10 @@ cairo_quartz_surface_get_cg_context (cai cairo_public cairo_font_face_t * cairo_quartz_font_face_create_for_cgfont (CGFontRef font); +#ifndef __LP64__ cairo_public cairo_font_face_t * cairo_quartz_font_face_create_for_atsu_font_id (ATSUFontID font_id); +#endif #endif /* CAIRO_HAS_QUARTZ_FONT */