michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include "BorrowedContext.h" michael@0: #include "DataSurfaceHelpers.h" michael@0: #include "DrawTargetCG.h" michael@0: #include "Logging.h" michael@0: #include "SourceSurfaceCG.h" michael@0: #include "Rect.h" michael@0: #include "ScaledFontMac.h" michael@0: #include "Tools.h" michael@0: #include michael@0: #include michael@0: #include "MacIOSurface.h" michael@0: #include "FilterNodeSoftware.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/Types.h" // for decltype michael@0: #include "mozilla/FloatingPoint.h" michael@0: michael@0: using namespace std; michael@0: michael@0: //CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode); michael@0: michael@0: // A private API that Cairo has been using for a long time michael@0: CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform); michael@0: michael@0: namespace mozilla { michael@0: namespace gfx { michael@0: michael@0: static CGRect RectToCGRect(Rect r) michael@0: { michael@0: return CGRectMake(r.x, r.y, r.width, r.height); michael@0: } michael@0: michael@0: CGBlendMode ToBlendMode(CompositionOp op) michael@0: { michael@0: CGBlendMode mode; michael@0: switch (op) { michael@0: case CompositionOp::OP_OVER: michael@0: mode = kCGBlendModeNormal; michael@0: break; michael@0: case CompositionOp::OP_ADD: michael@0: mode = kCGBlendModePlusLighter; michael@0: break; michael@0: case CompositionOp::OP_ATOP: michael@0: mode = kCGBlendModeSourceAtop; michael@0: break; michael@0: case CompositionOp::OP_OUT: michael@0: mode = kCGBlendModeSourceOut; michael@0: break; michael@0: case CompositionOp::OP_IN: michael@0: mode = kCGBlendModeSourceIn; michael@0: break; michael@0: case CompositionOp::OP_SOURCE: michael@0: mode = kCGBlendModeCopy; michael@0: break; michael@0: case CompositionOp::OP_DEST_IN: michael@0: mode = kCGBlendModeDestinationIn; michael@0: break; michael@0: case CompositionOp::OP_DEST_OUT: michael@0: mode = kCGBlendModeDestinationOut; michael@0: break; michael@0: case CompositionOp::OP_DEST_OVER: michael@0: mode = kCGBlendModeDestinationOver; michael@0: break; michael@0: case CompositionOp::OP_DEST_ATOP: michael@0: mode = kCGBlendModeDestinationAtop; michael@0: break; michael@0: case CompositionOp::OP_XOR: michael@0: mode = kCGBlendModeXOR; michael@0: break; michael@0: case CompositionOp::OP_MULTIPLY: michael@0: mode = kCGBlendModeMultiply; michael@0: break; michael@0: case CompositionOp::OP_SCREEN: michael@0: mode = kCGBlendModeScreen; michael@0: break; michael@0: case CompositionOp::OP_OVERLAY: michael@0: mode = kCGBlendModeOverlay; michael@0: break; michael@0: case CompositionOp::OP_DARKEN: michael@0: mode = kCGBlendModeDarken; michael@0: break; michael@0: case CompositionOp::OP_LIGHTEN: michael@0: mode = kCGBlendModeLighten; michael@0: break; michael@0: case CompositionOp::OP_COLOR_DODGE: michael@0: mode = kCGBlendModeColorDodge; michael@0: break; michael@0: case CompositionOp::OP_COLOR_BURN: michael@0: mode = kCGBlendModeColorBurn; michael@0: break; michael@0: case CompositionOp::OP_HARD_LIGHT: michael@0: mode = kCGBlendModeHardLight; michael@0: break; michael@0: case CompositionOp::OP_SOFT_LIGHT: michael@0: mode = kCGBlendModeSoftLight; michael@0: break; michael@0: case CompositionOp::OP_DIFFERENCE: michael@0: mode = kCGBlendModeDifference; michael@0: break; michael@0: case CompositionOp::OP_EXCLUSION: michael@0: mode = kCGBlendModeExclusion; michael@0: break; michael@0: case CompositionOp::OP_HUE: michael@0: mode = kCGBlendModeHue; michael@0: break; michael@0: case CompositionOp::OP_SATURATION: michael@0: mode = kCGBlendModeSaturation; michael@0: break; michael@0: case CompositionOp::OP_COLOR: michael@0: mode = kCGBlendModeColor; michael@0: break; michael@0: case CompositionOp::OP_LUMINOSITY: michael@0: mode = kCGBlendModeLuminosity; michael@0: break; michael@0: /* michael@0: case OP_CLEAR: michael@0: mode = kCGBlendModeClear; michael@0: break;*/ michael@0: default: michael@0: mode = kCGBlendModeNormal; michael@0: } michael@0: return mode; michael@0: } michael@0: michael@0: static CGInterpolationQuality michael@0: InterpolationQualityFromFilter(Filter aFilter) michael@0: { michael@0: switch (aFilter) { michael@0: default: michael@0: case Filter::LINEAR: michael@0: return kCGInterpolationLow; michael@0: case Filter::POINT: michael@0: return kCGInterpolationNone; michael@0: case Filter::GOOD: michael@0: return kCGInterpolationDefault; michael@0: } michael@0: } michael@0: michael@0: michael@0: DrawTargetCG::DrawTargetCG() : mCg(nullptr), mSnapshot(nullptr) michael@0: { michael@0: } michael@0: michael@0: DrawTargetCG::~DrawTargetCG() michael@0: { michael@0: MarkChanged(); michael@0: michael@0: // We need to conditionally release these because Init can fail without initializing these. michael@0: if (mColorSpace) michael@0: CGColorSpaceRelease(mColorSpace); michael@0: if (mCg) michael@0: CGContextRelease(mCg); michael@0: } michael@0: michael@0: BackendType michael@0: DrawTargetCG::GetType() const michael@0: { michael@0: // It may be worth spliting Bitmap and IOSurface DrawTarget michael@0: // into seperate classes. michael@0: if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) { michael@0: return BackendType::COREGRAPHICS_ACCELERATED; michael@0: } else { michael@0: return BackendType::COREGRAPHICS; michael@0: } michael@0: } michael@0: michael@0: TemporaryRef michael@0: DrawTargetCG::Snapshot() michael@0: { michael@0: if (!mSnapshot) { michael@0: if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) { michael@0: return new SourceSurfaceCGIOSurfaceContext(this); michael@0: } else { michael@0: mSnapshot = new SourceSurfaceCGBitmapContext(this); michael@0: } michael@0: } michael@0: michael@0: return mSnapshot; michael@0: } michael@0: michael@0: TemporaryRef michael@0: DrawTargetCG::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const michael@0: { michael@0: // XXX: in thebes we use CGLayers to do this kind of thing. It probably makes sense michael@0: // to add that in somehow, but at a higher level michael@0: RefPtr newTarget = new DrawTargetCG(); michael@0: if (newTarget->Init(GetType(), aSize, aFormat)) { michael@0: return newTarget; michael@0: } else { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: TemporaryRef michael@0: DrawTargetCG::CreateSourceSurfaceFromData(unsigned char *aData, michael@0: const IntSize &aSize, michael@0: int32_t aStride, michael@0: SurfaceFormat aFormat) const michael@0: { michael@0: RefPtr newSurf = new SourceSurfaceCG(); michael@0: michael@0: if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return newSurf; michael@0: } michael@0: michael@0: // This function returns a retained CGImage that needs to be released after michael@0: // use. The reason for this is that we want to either reuse an existing CGImage michael@0: // or create a new one. michael@0: static CGImageRef michael@0: GetRetainedImageFromSourceSurface(SourceSurface *aSurface) michael@0: { michael@0: if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE) michael@0: return CGImageRetain(static_cast(aSurface)->GetImage()); michael@0: else if (aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT) michael@0: return CGImageRetain(static_cast(aSurface)->GetImage()); michael@0: michael@0: if (aSurface->GetType() == SurfaceType::DATA) { michael@0: DataSourceSurface* dataSource = static_cast(aSurface); michael@0: return CreateCGImage(nullptr, dataSource->GetData(), dataSource->GetSize(), michael@0: dataSource->Stride(), dataSource->GetFormat()); michael@0: } michael@0: michael@0: MOZ_CRASH("unsupported source surface"); michael@0: } michael@0: michael@0: TemporaryRef michael@0: DrawTargetCG::OptimizeSourceSurface(SourceSurface *aSurface) const michael@0: { michael@0: if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE || michael@0: aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT) { michael@0: return aSurface; michael@0: } michael@0: return aSurface->GetDataSurface(); michael@0: } michael@0: michael@0: class UnboundnessFixer michael@0: { michael@0: CGRect mClipBounds; michael@0: CGLayerRef mLayer; michael@0: CGContextRef mCg; michael@0: public: michael@0: UnboundnessFixer() : mCg(nullptr) {} michael@0: michael@0: CGContextRef Check(CGContextRef baseCg, CompositionOp blend, const Rect* maskBounds = nullptr) michael@0: { michael@0: if (!IsOperatorBoundByMask(blend)) { michael@0: mClipBounds = CGContextGetClipBoundingBox(baseCg); michael@0: // If we're entirely clipped out or if the drawing operation covers the entire clip then michael@0: // we don't need to create a temporary surface. michael@0: if (CGRectIsEmpty(mClipBounds) || michael@0: (maskBounds && maskBounds->Contains(CGRectToRect(mClipBounds)))) { michael@0: return baseCg; michael@0: } michael@0: michael@0: // TransparencyLayers aren't blended using the blend mode so michael@0: // we are forced to use CGLayers michael@0: michael@0: //XXX: The size here is in default user space units, of the layer relative to the graphics context. michael@0: // is the clip bounds still correct if, for example, we have a scale applied to the context? michael@0: mLayer = CGLayerCreateWithContext(baseCg, mClipBounds.size, nullptr); michael@0: mCg = CGLayerGetContext(mLayer); michael@0: // CGContext's default to have the origin at the bottom left michael@0: // so flip it to the top left and adjust for the origin michael@0: // of the layer michael@0: CGContextTranslateCTM(mCg, -mClipBounds.origin.x, mClipBounds.origin.y + mClipBounds.size.height); michael@0: CGContextScaleCTM(mCg, 1, -1); michael@0: michael@0: return mCg; michael@0: } else { michael@0: return baseCg; michael@0: } michael@0: } michael@0: michael@0: void Fix(CGContextRef baseCg) michael@0: { michael@0: if (mCg) { michael@0: CGContextTranslateCTM(baseCg, 0, mClipBounds.size.height); michael@0: CGContextScaleCTM(baseCg, 1, -1); michael@0: mClipBounds.origin.y *= -1; michael@0: CGContextDrawLayerAtPoint(baseCg, mClipBounds.origin, mLayer); michael@0: CGContextRelease(mCg); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: void michael@0: DrawTargetCG::DrawSurface(SourceSurface *aSurface, michael@0: const Rect &aDest, michael@0: const Rect &aSource, michael@0: const DrawSurfaceOptions &aSurfOptions, michael@0: const DrawOptions &aDrawOptions) michael@0: { michael@0: MarkChanged(); michael@0: michael@0: CGContextSaveGState(mCg); michael@0: michael@0: CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); michael@0: UnboundnessFixer fixer; michael@0: CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aDest); michael@0: CGContextSetAlpha(cg, aDrawOptions.mAlpha); michael@0: CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); michael@0: michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: michael@0: CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(aSurfOptions.mFilter)); michael@0: michael@0: CGImageRef image = GetRetainedImageFromSourceSurface(aSurface); michael@0: michael@0: if (aSurfOptions.mFilter == Filter::POINT) { michael@0: CGImageRef subimage = CGImageCreateWithImageInRect(image, RectToCGRect(aSource)); michael@0: CGImageRelease(image); michael@0: michael@0: CGContextScaleCTM(cg, 1, -1); michael@0: michael@0: CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + aDest.height), michael@0: aDest.width, aDest.height); michael@0: michael@0: CGContextDrawImage(cg, flippedRect, subimage); michael@0: CGImageRelease(subimage); michael@0: } else { michael@0: CGRect destRect = CGRectMake(aDest.x, aDest.y, aDest.width, aDest.height); michael@0: CGContextClipToRect(cg, destRect); michael@0: michael@0: float xScale = aSource.width / aDest.width; michael@0: float yScale = aSource.height / aDest.height; michael@0: CGContextTranslateCTM(cg, aDest.x - aSource.x / xScale, aDest.y - aSource.y / yScale); michael@0: michael@0: CGRect adjustedDestRect = CGRectMake(0, 0, CGImageGetWidth(image) / xScale, michael@0: CGImageGetHeight(image) / yScale); michael@0: michael@0: CGContextTranslateCTM(cg, 0, CGRectGetHeight(adjustedDestRect)); michael@0: CGContextScaleCTM(cg, 1, -1); michael@0: michael@0: CGContextDrawImage(cg, adjustedDestRect, image); michael@0: CGImageRelease(image); michael@0: } michael@0: michael@0: fixer.Fix(mCg); michael@0: michael@0: CGContextRestoreGState(mCg); michael@0: } michael@0: michael@0: TemporaryRef michael@0: DrawTargetCG::CreateFilter(FilterType aType) michael@0: { michael@0: return FilterNodeSoftware::Create(aType); michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::DrawFilter(FilterNode *aNode, michael@0: const Rect &aSourceRect, michael@0: const Point &aDestPoint, michael@0: const DrawOptions &aOptions) michael@0: { michael@0: FilterNodeSoftware* filter = static_cast(aNode); michael@0: filter->Draw(this, aSourceRect, aDestPoint, aOptions); michael@0: } michael@0: michael@0: static CGColorRef ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColor) michael@0: { michael@0: CGFloat components[4] = {aColor.r, aColor.g, aColor.b, aColor.a}; michael@0: return CGColorCreate(aColorSpace, components); michael@0: } michael@0: michael@0: class GradientStopsCG : public GradientStops michael@0: { michael@0: public: michael@0: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsCG) michael@0: //XXX: The skia backend uses a vector and passes in aNumStops. It should do better michael@0: GradientStopsCG(GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) michael@0: { michael@0: mExtend = aExtendMode; michael@0: if (aExtendMode == ExtendMode::CLAMP) { michael@0: //XXX: do the stops need to be in any particular order? michael@0: // what should we do about the color space here? we certainly shouldn't be michael@0: // recreating it all the time michael@0: std::vector colors; michael@0: std::vector offsets; michael@0: colors.reserve(aNumStops*4); michael@0: offsets.reserve(aNumStops); michael@0: michael@0: for (uint32_t i = 0; i < aNumStops; i++) { michael@0: colors.push_back(aStops[i].color.r); michael@0: colors.push_back(aStops[i].color.g); michael@0: colors.push_back(aStops[i].color.b); michael@0: colors.push_back(aStops[i].color.a); michael@0: michael@0: offsets.push_back(aStops[i].offset); michael@0: } michael@0: michael@0: CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); michael@0: mGradient = CGGradientCreateWithColorComponents(colorSpace, michael@0: &colors.front(), michael@0: &offsets.front(), michael@0: aNumStops); michael@0: CGColorSpaceRelease(colorSpace); michael@0: } else { michael@0: mGradient = nullptr; michael@0: mStops.reserve(aNumStops); michael@0: for (uint32_t i = 0; i < aNumStops; i++) { michael@0: mStops.push_back(aStops[i]); michael@0: } michael@0: } michael@0: michael@0: } michael@0: virtual ~GradientStopsCG() { michael@0: if (mGradient) michael@0: CGGradientRelease(mGradient); michael@0: } michael@0: // Will always report BackendType::COREGRAPHICS, but it is compatible michael@0: // with BackendType::COREGRAPHICS_ACCELERATED michael@0: BackendType GetBackendType() const { return BackendType::COREGRAPHICS; } michael@0: // XXX this should be a union michael@0: CGGradientRef mGradient; michael@0: std::vector mStops; michael@0: ExtendMode mExtend; michael@0: }; michael@0: michael@0: TemporaryRef michael@0: DrawTargetCG::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, michael@0: ExtendMode aExtendMode) const michael@0: { michael@0: return new GradientStopsCG(aStops, aNumStops, aExtendMode); michael@0: } michael@0: michael@0: static void michael@0: UpdateLinearParametersToIncludePoint(double *min_t, double *max_t, michael@0: CGPoint *start, michael@0: double dx, double dy, michael@0: double x, double y) michael@0: { michael@0: MOZ_ASSERT(IsFinite(x) && IsFinite(y)); michael@0: michael@0: /** michael@0: * Compute a parameter t such that a line perpendicular to the (dx,dy) michael@0: * vector, passing through (start->x + dx*t, start->y + dy*t), also michael@0: * passes through (x,y). michael@0: * michael@0: * Let px = x - start->x, py = y - start->y. michael@0: * t is given by michael@0: * (px - dx*t)*dx + (py - dy*t)*dy = 0 michael@0: * michael@0: * Solving for t we get michael@0: * numerator = dx*px + dy*py michael@0: * denominator = dx^2 + dy^2 michael@0: * t = numerator/denominator michael@0: * michael@0: * In CalculateRepeatingGradientParams we know the length of (dx,dy) michael@0: * is not zero. (This is checked in DrawLinearRepeatingGradient.) michael@0: */ michael@0: double px = x - start->x; michael@0: double py = y - start->y; michael@0: double numerator = dx * px + dy * py; michael@0: double denominator = dx * dx + dy * dy; michael@0: double t = numerator / denominator; michael@0: michael@0: if (*min_t > t) { michael@0: *min_t = t; michael@0: } michael@0: if (*max_t < t) { michael@0: *max_t = t; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Repeat the gradient line such that lines extended perpendicular to the michael@0: * gradient line at both start and end would completely enclose the drawing michael@0: * extents. michael@0: */ michael@0: static void michael@0: CalculateRepeatingGradientParams(CGPoint *aStart, CGPoint *aEnd, michael@0: CGRect aExtents, int *aRepeatCount) michael@0: { michael@0: double t_min = INFINITY; michael@0: double t_max = -INFINITY; michael@0: double dx = aEnd->x - aStart->x; michael@0: double dy = aEnd->y - aStart->y; michael@0: michael@0: double bounds_x1 = aExtents.origin.x; michael@0: double bounds_y1 = aExtents.origin.y; michael@0: double bounds_x2 = aExtents.origin.x + aExtents.size.width; michael@0: double bounds_y2 = aExtents.origin.y + aExtents.size.height; michael@0: michael@0: UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy, michael@0: bounds_x1, bounds_y1); michael@0: UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy, michael@0: bounds_x2, bounds_y1); michael@0: UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy, michael@0: bounds_x2, bounds_y2); michael@0: UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy, michael@0: bounds_x1, bounds_y2); michael@0: michael@0: MOZ_ASSERT(!isinf(t_min) && !isinf(t_max), michael@0: "The first call to UpdateLinearParametersToIncludePoint should have made t_min and t_max non-infinite."); michael@0: michael@0: // Move t_min and t_max to the nearest usable integer to try to avoid michael@0: // subtle variations due to numerical instability, especially accidentally michael@0: // cutting off a pixel. Extending the gradient repetitions is always safe. michael@0: t_min = floor (t_min); michael@0: t_max = ceil (t_max); michael@0: aEnd->x = aStart->x + dx * t_max; michael@0: aEnd->y = aStart->y + dy * t_max; michael@0: aStart->x = aStart->x + dx * t_min; michael@0: aStart->y = aStart->y + dy * t_min; michael@0: michael@0: *aRepeatCount = t_max - t_min; michael@0: } michael@0: michael@0: static void michael@0: DrawLinearRepeatingGradient(CGContextRef cg, const LinearGradientPattern &aPattern, const CGRect &aExtents) michael@0: { michael@0: GradientStopsCG *stops = static_cast(aPattern.mStops.get()); michael@0: CGPoint startPoint = { aPattern.mBegin.x, aPattern.mBegin.y }; michael@0: CGPoint endPoint = { aPattern.mEnd.x, aPattern.mEnd.y }; michael@0: michael@0: int repeatCount = 1; michael@0: // if we don't have a line then we can't extend it michael@0: if (aPattern.mEnd.x != aPattern.mBegin.x || michael@0: aPattern.mEnd.y != aPattern.mBegin.y) { michael@0: CalculateRepeatingGradientParams(&startPoint, &endPoint, aExtents, michael@0: &repeatCount); michael@0: } michael@0: michael@0: double scale = 1./repeatCount; michael@0: michael@0: std::vector colors; michael@0: std::vector offsets; michael@0: colors.reserve(stops->mStops.size()*repeatCount*4); michael@0: offsets.reserve(stops->mStops.size()*repeatCount); michael@0: michael@0: for (int j = 0; j < repeatCount; j++) { michael@0: for (uint32_t i = 0; i < stops->mStops.size(); i++) { michael@0: colors.push_back(stops->mStops[i].color.r); michael@0: colors.push_back(stops->mStops[i].color.g); michael@0: colors.push_back(stops->mStops[i].color.b); michael@0: colors.push_back(stops->mStops[i].color.a); michael@0: michael@0: offsets.push_back((stops->mStops[i].offset + j)*scale); michael@0: } michael@0: } michael@0: michael@0: CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); michael@0: CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, michael@0: &colors.front(), michael@0: &offsets.front(), michael@0: repeatCount*stops->mStops.size()); michael@0: CGColorSpaceRelease(colorSpace); michael@0: michael@0: CGContextDrawLinearGradient(cg, gradient, startPoint, endPoint, michael@0: kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); michael@0: CGGradientRelease(gradient); michael@0: } michael@0: michael@0: static CGPoint CGRectTopLeft(CGRect a) michael@0: { return a.origin; } michael@0: static CGPoint CGRectBottomLeft(CGRect a) michael@0: { return CGPointMake(a.origin.x, a.origin.y + a.size.height); } michael@0: static CGPoint CGRectTopRight(CGRect a) michael@0: { return CGPointMake(a.origin.x + a.size.width, a.origin.y); } michael@0: static CGPoint CGRectBottomRight(CGRect a) michael@0: { return CGPointMake(a.origin.x + a.size.width, a.origin.y + a.size.height); } michael@0: michael@0: static CGFloat michael@0: CGPointDistance(CGPoint a, CGPoint b) michael@0: { michael@0: return hypot(a.x-b.x, a.y-b.y); michael@0: } michael@0: michael@0: static void michael@0: DrawRadialRepeatingGradient(CGContextRef cg, const RadialGradientPattern &aPattern, const CGRect &aExtents) michael@0: { michael@0: GradientStopsCG *stops = static_cast(aPattern.mStops.get()); michael@0: CGPoint startCenter = { aPattern.mCenter1.x, aPattern.mCenter1.y }; michael@0: CGFloat startRadius = aPattern.mRadius1; michael@0: CGPoint endCenter = { aPattern.mCenter2.x, aPattern.mCenter2.y }; michael@0: CGFloat endRadius = aPattern.mRadius2; michael@0: michael@0: // find the maximum distance from endCenter to a corner of aExtents michael@0: CGFloat minimumEndRadius = endRadius; michael@0: minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopLeft(aExtents))); michael@0: minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomLeft(aExtents))); michael@0: minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopRight(aExtents))); michael@0: minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomRight(aExtents))); michael@0: michael@0: CGFloat length = endRadius - startRadius; michael@0: int repeatCount = 1; michael@0: while (endRadius < minimumEndRadius) { michael@0: endRadius += length; michael@0: repeatCount++; michael@0: } michael@0: michael@0: while (startRadius-length >= 0) { michael@0: startRadius -= length; michael@0: repeatCount++; michael@0: } michael@0: michael@0: double scale = 1./repeatCount; michael@0: michael@0: std::vector colors; michael@0: std::vector offsets; michael@0: colors.reserve(stops->mStops.size()*repeatCount*4); michael@0: offsets.reserve(stops->mStops.size()*repeatCount); michael@0: for (int j = 0; j < repeatCount; j++) { michael@0: for (uint32_t i = 0; i < stops->mStops.size(); i++) { michael@0: colors.push_back(stops->mStops[i].color.r); michael@0: colors.push_back(stops->mStops[i].color.g); michael@0: colors.push_back(stops->mStops[i].color.b); michael@0: colors.push_back(stops->mStops[i].color.a); michael@0: michael@0: offsets.push_back((stops->mStops[i].offset + j)*scale); michael@0: } michael@0: } michael@0: michael@0: CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); michael@0: CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, michael@0: &colors.front(), michael@0: &offsets.front(), michael@0: repeatCount*stops->mStops.size()); michael@0: CGColorSpaceRelease(colorSpace); michael@0: michael@0: //XXX: are there degenerate radial gradients that we should avoid drawing? michael@0: CGContextDrawRadialGradient(cg, gradient, startCenter, startRadius, endCenter, endRadius, michael@0: kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); michael@0: CGGradientRelease(gradient); michael@0: } michael@0: michael@0: static void michael@0: DrawGradient(CGContextRef cg, const Pattern &aPattern, const CGRect &aExtents) michael@0: { michael@0: if (CGRectIsEmpty(aExtents)) { michael@0: return; michael@0: } michael@0: michael@0: if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) { michael@0: const LinearGradientPattern& pat = static_cast(aPattern); michael@0: GradientStopsCG *stops = static_cast(pat.mStops.get()); michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix)); michael@0: if (stops->mExtend == ExtendMode::CLAMP) { michael@0: michael@0: // XXX: we should take the m out of the properties of LinearGradientPatterns michael@0: CGPoint startPoint = { pat.mBegin.x, pat.mBegin.y }; michael@0: CGPoint endPoint = { pat.mEnd.x, pat.mEnd.y }; michael@0: michael@0: // Canvas spec states that we should avoid drawing degenerate gradients (XXX: should this be in common code?) michael@0: //if (startPoint.x == endPoint.x && startPoint.y == endPoint.y) michael@0: // return; michael@0: michael@0: CGContextDrawLinearGradient(cg, stops->mGradient, startPoint, endPoint, michael@0: kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); michael@0: } else if (stops->mExtend == ExtendMode::REPEAT) { michael@0: DrawLinearRepeatingGradient(cg, pat, aExtents); michael@0: } michael@0: } else if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) { michael@0: const RadialGradientPattern& pat = static_cast(aPattern); michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix)); michael@0: GradientStopsCG *stops = static_cast(pat.mStops.get()); michael@0: if (stops->mExtend == ExtendMode::CLAMP) { michael@0: michael@0: // XXX: we should take the m out of the properties of RadialGradientPatterns michael@0: CGPoint startCenter = { pat.mCenter1.x, pat.mCenter1.y }; michael@0: CGFloat startRadius = pat.mRadius1; michael@0: CGPoint endCenter = { pat.mCenter2.x, pat.mCenter2.y }; michael@0: CGFloat endRadius = pat.mRadius2; michael@0: michael@0: //XXX: are there degenerate radial gradients that we should avoid drawing? michael@0: CGContextDrawRadialGradient(cg, stops->mGradient, startCenter, startRadius, endCenter, endRadius, michael@0: kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); michael@0: } else if (stops->mExtend == ExtendMode::REPEAT) { michael@0: DrawRadialRepeatingGradient(cg, pat, aExtents); michael@0: } michael@0: } else { michael@0: assert(0); michael@0: } michael@0: michael@0: } michael@0: michael@0: static void michael@0: drawPattern(void *info, CGContextRef context) michael@0: { michael@0: CGImageRef image = static_cast(info); michael@0: CGRect rect = {{0, 0}, michael@0: {static_cast(CGImageGetWidth(image)), michael@0: static_cast(CGImageGetHeight(image))}}; michael@0: CGContextDrawImage(context, rect, image); michael@0: } michael@0: michael@0: static void michael@0: releaseInfo(void *info) michael@0: { michael@0: CGImageRef image = static_cast(info); michael@0: CGImageRelease(image); michael@0: } michael@0: michael@0: CGPatternCallbacks patternCallbacks = { michael@0: 0, michael@0: drawPattern, michael@0: releaseInfo michael@0: }; michael@0: michael@0: static bool michael@0: isGradient(const Pattern &aPattern) michael@0: { michael@0: return aPattern.GetType() == PatternType::LINEAR_GRADIENT || aPattern.GetType() == PatternType::RADIAL_GRADIENT; michael@0: } michael@0: michael@0: /* CoreGraphics patterns ignore the userspace transform so michael@0: * we need to multiply it in */ michael@0: static CGPatternRef michael@0: CreateCGPattern(const Pattern &aPattern, CGAffineTransform aUserSpace) michael@0: { michael@0: const SurfacePattern& pat = static_cast(aPattern); michael@0: // XXX: is .get correct here? michael@0: CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get()); michael@0: CGFloat xStep, yStep; michael@0: switch (pat.mExtendMode) { michael@0: case ExtendMode::CLAMP: michael@0: // The 1 << 22 comes from Webkit see Pattern::createPlatformPattern() in PatternCG.cpp for more info michael@0: xStep = static_cast(1 << 22); michael@0: yStep = static_cast(1 << 22); michael@0: break; michael@0: case ExtendMode::REFLECT: michael@0: assert(0); michael@0: case ExtendMode::REPEAT: michael@0: xStep = static_cast(CGImageGetWidth(image)); michael@0: yStep = static_cast(CGImageGetHeight(image)); michael@0: // webkit uses wkCGPatternCreateWithImageAndTransform a wrapper around CGPatternCreateWithImage2 michael@0: // this is done to avoid pixel-cracking along pattern boundaries michael@0: // (see https://bugs.webkit.org/show_bug.cgi?id=53055) michael@0: // typedef enum { michael@0: // wkPatternTilingNoDistortion, michael@0: // wkPatternTilingConstantSpacingMinimalDistortion, michael@0: // wkPatternTilingConstantSpacing michael@0: // } wkPatternTiling; michael@0: // extern CGPatternRef (*wkCGPatternCreateWithImageAndTransform)(CGImageRef, CGAffineTransform, int); michael@0: } michael@0: michael@0: //XXX: We should be using CGContextDrawTiledImage when we can. Even though it michael@0: // creates a pattern, it seems to go down a faster path than using a delegate michael@0: // like we do below michael@0: CGRect bounds = { michael@0: {0, 0,}, michael@0: {static_cast(CGImageGetWidth(image)), static_cast(CGImageGetHeight(image))} michael@0: }; michael@0: CGAffineTransform transform = michael@0: CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformMakeScale(1, michael@0: -1), michael@0: GfxMatrixToCGAffineTransform(pat.mMatrix)), michael@0: aUserSpace); michael@0: transform = CGAffineTransformTranslate(transform, 0, -static_cast(CGImageGetHeight(image))); michael@0: return CGPatternCreate(image, bounds, transform, xStep, yStep, kCGPatternTilingConstantSpacing, michael@0: true, &patternCallbacks); michael@0: } michael@0: michael@0: static void michael@0: SetFillFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern) michael@0: { michael@0: assert(!isGradient(aPattern)); michael@0: if (aPattern.GetType() == PatternType::COLOR) { michael@0: michael@0: const Color& color = static_cast(aPattern).mColor; michael@0: //XXX: we should cache colors michael@0: CGColorRef cgcolor = ColorToCGColor(aColorSpace, color); michael@0: CGContextSetFillColorWithColor(cg, cgcolor); michael@0: CGColorRelease(cgcolor); michael@0: } else if (aPattern.GetType() == PatternType::SURFACE) { michael@0: michael@0: CGColorSpaceRef patternSpace; michael@0: patternSpace = CGColorSpaceCreatePattern (nullptr); michael@0: CGContextSetFillColorSpace(cg, patternSpace); michael@0: CGColorSpaceRelease(patternSpace); michael@0: michael@0: CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg)); michael@0: const SurfacePattern& pat = static_cast(aPattern); michael@0: CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter)); michael@0: CGFloat alpha = 1.; michael@0: CGContextSetFillPattern(cg, pattern, &alpha); michael@0: CGPatternRelease(pattern); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: SetStrokeFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern) michael@0: { michael@0: assert(!isGradient(aPattern)); michael@0: if (aPattern.GetType() == PatternType::COLOR) { michael@0: const Color& color = static_cast(aPattern).mColor; michael@0: //XXX: we should cache colors michael@0: CGColorRef cgcolor = ColorToCGColor(aColorSpace, color); michael@0: CGContextSetStrokeColorWithColor(cg, cgcolor); michael@0: CGColorRelease(cgcolor); michael@0: } else if (aPattern.GetType() == PatternType::SURFACE) { michael@0: CGColorSpaceRef patternSpace; michael@0: patternSpace = CGColorSpaceCreatePattern (nullptr); michael@0: CGContextSetStrokeColorSpace(cg, patternSpace); michael@0: CGColorSpaceRelease(patternSpace); michael@0: michael@0: CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg)); michael@0: const SurfacePattern& pat = static_cast(aPattern); michael@0: CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter)); michael@0: CGFloat alpha = 1.; michael@0: CGContextSetStrokePattern(cg, pattern, &alpha); michael@0: CGPatternRelease(pattern); michael@0: } michael@0: michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::MaskSurface(const Pattern &aSource, michael@0: SourceSurface *aMask, michael@0: Point aOffset, michael@0: const DrawOptions &aDrawOptions) michael@0: { michael@0: MarkChanged(); michael@0: michael@0: CGContextSaveGState(mCg); michael@0: michael@0: CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); michael@0: UnboundnessFixer fixer; michael@0: CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); michael@0: CGContextSetAlpha(cg, aDrawOptions.mAlpha); michael@0: CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); michael@0: michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: CGImageRef image = GetRetainedImageFromSourceSurface(aMask); michael@0: michael@0: // use a negative-y so that the mask image draws right ways up michael@0: CGContextScaleCTM(cg, 1, -1); michael@0: michael@0: IntSize size = aMask->GetSize(); michael@0: michael@0: CGContextClipToMask(cg, CGRectMake(aOffset.x, -(aOffset.y + size.height), size.width, size.height), image); michael@0: michael@0: CGContextScaleCTM(cg, 1, -1); michael@0: if (isGradient(aSource)) { michael@0: // we shouldn't need to clip to an additional rectangle michael@0: // as the cliping to the mask should be sufficient. michael@0: DrawGradient(cg, aSource, CGRectMake(aOffset.x, aOffset.y, size.width, size.height)); michael@0: } else { michael@0: SetFillFromPattern(cg, mColorSpace, aSource); michael@0: CGContextFillRect(cg, CGRectMake(aOffset.x, aOffset.y, size.width, size.height)); michael@0: } michael@0: michael@0: CGImageRelease(image); michael@0: michael@0: fixer.Fix(mCg); michael@0: michael@0: CGContextRestoreGState(mCg); michael@0: } michael@0: michael@0: michael@0: michael@0: void michael@0: DrawTargetCG::FillRect(const Rect &aRect, michael@0: const Pattern &aPattern, michael@0: const DrawOptions &aDrawOptions) michael@0: { michael@0: MarkChanged(); michael@0: michael@0: CGContextSaveGState(mCg); michael@0: michael@0: UnboundnessFixer fixer; michael@0: CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aRect); michael@0: CGContextSetAlpha(mCg, aDrawOptions.mAlpha); michael@0: CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); michael@0: CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); michael@0: michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: michael@0: if (isGradient(aPattern)) { michael@0: CGContextClipToRect(cg, RectToCGRect(aRect)); michael@0: CGRect clipBounds = CGContextGetClipBoundingBox(cg); michael@0: DrawGradient(cg, aPattern, clipBounds); michael@0: } else { michael@0: if (aPattern.GetType() == PatternType::SURFACE && static_cast(aPattern).mExtendMode != ExtendMode::REPEAT) { michael@0: // SetFillFromPattern can handle this case but using CGContextDrawImage michael@0: // should give us better performance, better output, smaller PDF and michael@0: // matches what cairo does. michael@0: const SurfacePattern& pat = static_cast(aPattern); michael@0: CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get()); michael@0: CGContextClipToRect(cg, RectToCGRect(aRect)); michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix)); michael@0: CGContextTranslateCTM(cg, 0, CGImageGetHeight(image)); michael@0: CGContextScaleCTM(cg, 1, -1); michael@0: michael@0: CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)); michael@0: michael@0: CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter)); michael@0: michael@0: CGContextDrawImage(cg, imageRect, image); michael@0: CGImageRelease(image); michael@0: } else { michael@0: SetFillFromPattern(cg, mColorSpace, aPattern); michael@0: CGContextFillRect(cg, RectToCGRect(aRect)); michael@0: } michael@0: } michael@0: michael@0: fixer.Fix(mCg); michael@0: CGContextRestoreGState(mCg); michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions) michael@0: { michael@0: if (!std::isfinite(p1.x) || michael@0: !std::isfinite(p1.y) || michael@0: !std::isfinite(p2.x) || michael@0: !std::isfinite(p2.y)) { michael@0: return; michael@0: } michael@0: michael@0: MarkChanged(); michael@0: michael@0: CGContextSaveGState(mCg); michael@0: michael@0: UnboundnessFixer fixer; michael@0: CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); michael@0: CGContextSetAlpha(mCg, aDrawOptions.mAlpha); michael@0: CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); michael@0: CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); michael@0: michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: michael@0: CGContextBeginPath(cg); michael@0: CGContextMoveToPoint(cg, p1.x, p1.y); michael@0: CGContextAddLineToPoint(cg, p2.x, p2.y); michael@0: michael@0: SetStrokeOptions(cg, aStrokeOptions); michael@0: michael@0: if (isGradient(aPattern)) { michael@0: CGContextReplacePathWithStrokedPath(cg); michael@0: CGRect extents = CGContextGetPathBoundingBox(cg); michael@0: //XXX: should we use EO clip here? michael@0: CGContextClip(cg); michael@0: DrawGradient(cg, aPattern, extents); michael@0: } else { michael@0: SetStrokeFromPattern(cg, mColorSpace, aPattern); michael@0: CGContextStrokePath(cg); michael@0: } michael@0: michael@0: fixer.Fix(mCg); michael@0: CGContextRestoreGState(mCg); michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::StrokeRect(const Rect &aRect, michael@0: const Pattern &aPattern, michael@0: const StrokeOptions &aStrokeOptions, michael@0: const DrawOptions &aDrawOptions) michael@0: { michael@0: if (!aRect.IsFinite()) { michael@0: return; michael@0: } michael@0: michael@0: MarkChanged(); michael@0: michael@0: CGContextSaveGState(mCg); michael@0: michael@0: UnboundnessFixer fixer; michael@0: CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); michael@0: CGContextSetAlpha(mCg, aDrawOptions.mAlpha); michael@0: CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); michael@0: CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); michael@0: michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: michael@0: SetStrokeOptions(cg, aStrokeOptions); michael@0: michael@0: if (isGradient(aPattern)) { michael@0: // There's no CGContextClipStrokeRect so we do it by hand michael@0: CGContextBeginPath(cg); michael@0: CGContextAddRect(cg, RectToCGRect(aRect)); michael@0: CGContextReplacePathWithStrokedPath(cg); michael@0: CGRect extents = CGContextGetPathBoundingBox(cg); michael@0: //XXX: should we use EO clip here? michael@0: CGContextClip(cg); michael@0: DrawGradient(cg, aPattern, extents); michael@0: } else { michael@0: SetStrokeFromPattern(cg, mColorSpace, aPattern); michael@0: CGContextStrokeRect(cg, RectToCGRect(aRect)); michael@0: } michael@0: michael@0: fixer.Fix(mCg); michael@0: CGContextRestoreGState(mCg); michael@0: } michael@0: michael@0: michael@0: void michael@0: DrawTargetCG::ClearRect(const Rect &aRect) michael@0: { michael@0: MarkChanged(); michael@0: michael@0: CGContextSaveGState(mCg); michael@0: CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: michael@0: CGContextClearRect(mCg, RectToCGRect(aRect)); michael@0: michael@0: CGContextRestoreGState(mCg); michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions) michael@0: { michael@0: if (!aPath->GetBounds().IsFinite()) { michael@0: return; michael@0: } michael@0: michael@0: MarkChanged(); michael@0: michael@0: CGContextSaveGState(mCg); michael@0: michael@0: UnboundnessFixer fixer; michael@0: CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); michael@0: CGContextSetAlpha(mCg, aDrawOptions.mAlpha); michael@0: CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); michael@0: CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); michael@0: michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: michael@0: michael@0: CGContextBeginPath(cg); michael@0: michael@0: assert(aPath->GetBackendType() == BackendType::COREGRAPHICS); michael@0: const PathCG *cgPath = static_cast(aPath); michael@0: CGContextAddPath(cg, cgPath->GetPath()); michael@0: michael@0: SetStrokeOptions(cg, aStrokeOptions); michael@0: michael@0: if (isGradient(aPattern)) { michael@0: CGContextReplacePathWithStrokedPath(cg); michael@0: CGRect extents = CGContextGetPathBoundingBox(cg); michael@0: //XXX: should we use EO clip here? michael@0: CGContextClip(cg); michael@0: DrawGradient(cg, aPattern, extents); michael@0: } else { michael@0: // XXX: we could put fill mode into the path fill rule if we wanted michael@0: michael@0: SetStrokeFromPattern(cg, mColorSpace, aPattern); michael@0: CGContextStrokePath(cg); michael@0: } michael@0: michael@0: fixer.Fix(mCg); michael@0: CGContextRestoreGState(mCg); michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aDrawOptions) michael@0: { michael@0: MarkChanged(); michael@0: michael@0: assert(aPath->GetBackendType() == BackendType::COREGRAPHICS); michael@0: michael@0: CGContextSaveGState(mCg); michael@0: michael@0: CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); michael@0: UnboundnessFixer fixer; michael@0: CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); michael@0: CGContextSetAlpha(cg, aDrawOptions.mAlpha); michael@0: CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); michael@0: michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: michael@0: CGContextBeginPath(cg); michael@0: // XXX: we could put fill mode into the path fill rule if we wanted michael@0: const PathCG *cgPath = static_cast(aPath); michael@0: michael@0: if (isGradient(aPattern)) { michael@0: // setup a clip to draw the gradient through michael@0: CGRect extents; michael@0: if (CGPathIsEmpty(cgPath->GetPath())) { michael@0: // Adding an empty path will cause us not to clip michael@0: // so clip everything explicitly michael@0: CGContextClipToRect(mCg, CGRectZero); michael@0: extents = CGRectZero; michael@0: } else { michael@0: CGContextAddPath(cg, cgPath->GetPath()); michael@0: extents = CGContextGetPathBoundingBox(cg); michael@0: if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD) michael@0: CGContextEOClip(mCg); michael@0: else michael@0: CGContextClip(mCg); michael@0: } michael@0: michael@0: DrawGradient(cg, aPattern, extents); michael@0: } else { michael@0: CGContextAddPath(cg, cgPath->GetPath()); michael@0: michael@0: SetFillFromPattern(cg, mColorSpace, aPattern); michael@0: michael@0: if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD) michael@0: CGContextEOFillPath(cg); michael@0: else michael@0: CGContextFillPath(cg); michael@0: } michael@0: michael@0: fixer.Fix(mCg); michael@0: CGContextRestoreGState(mCg); michael@0: } michael@0: michael@0: CGRect ComputeGlyphsExtents(CGRect *bboxes, CGPoint *positions, CFIndex count, float scale) michael@0: { michael@0: CGFloat x1, x2, y1, y2; michael@0: if (count < 1) michael@0: return CGRectZero; michael@0: michael@0: x1 = bboxes[0].origin.x + positions[0].x; michael@0: x2 = bboxes[0].origin.x + positions[0].x + scale*bboxes[0].size.width; michael@0: y1 = bboxes[0].origin.y + positions[0].y; michael@0: y2 = bboxes[0].origin.y + positions[0].y + scale*bboxes[0].size.height; michael@0: michael@0: // accumulate max and minimum coordinates michael@0: for (int i = 1; i < count; i++) { michael@0: x1 = min(x1, bboxes[i].origin.x + positions[i].x); michael@0: y1 = min(y1, bboxes[i].origin.y + positions[i].y); michael@0: x2 = max(x2, bboxes[i].origin.x + positions[i].x + scale*bboxes[i].size.width); michael@0: y2 = max(y2, bboxes[i].origin.y + positions[i].y + scale*bboxes[i].size.height); michael@0: } michael@0: michael@0: CGRect extents = {{x1, y1}, {x2-x1, y2-y1}}; michael@0: return extents; michael@0: } michael@0: michael@0: michael@0: void michael@0: DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aDrawOptions, michael@0: const GlyphRenderingOptions*) michael@0: { michael@0: MarkChanged(); michael@0: michael@0: assert(aBuffer.mNumGlyphs); michael@0: CGContextSaveGState(mCg); michael@0: michael@0: CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); michael@0: UnboundnessFixer fixer; michael@0: CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); michael@0: CGContextSetAlpha(cg, aDrawOptions.mAlpha); michael@0: CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); michael@0: if (aDrawOptions.mAntialiasMode != AntialiasMode::DEFAULT) { michael@0: CGContextSetShouldSmoothFonts(cg, aDrawOptions.mAntialiasMode == AntialiasMode::SUBPIXEL); michael@0: } michael@0: michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: michael@0: ScaledFontMac* macFont = static_cast(aFont); michael@0: michael@0: //XXX: we should use a stack vector here when we have a class like that michael@0: std::vector glyphs; michael@0: std::vector positions; michael@0: glyphs.resize(aBuffer.mNumGlyphs); michael@0: positions.resize(aBuffer.mNumGlyphs); michael@0: michael@0: // Handle the flip michael@0: CGContextScaleCTM(cg, 1, -1); michael@0: // CGContextSetTextMatrix works differently with kCGTextClip && kCGTextFill michael@0: // It seems that it transforms the positions with TextFill and not with TextClip michael@0: // Therefore we'll avoid it. See also: michael@0: // http://cgit.freedesktop.org/cairo/commit/?id=9c0d761bfcdd28d52c83d74f46dd3c709ae0fa69 michael@0: michael@0: for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { michael@0: glyphs[i] = aBuffer.mGlyphs[i].mIndex; michael@0: // XXX: CGPointMake might not be inlined michael@0: positions[i] = CGPointMake(aBuffer.mGlyphs[i].mPosition.x, michael@0: -aBuffer.mGlyphs[i].mPosition.y); michael@0: } michael@0: michael@0: //XXX: CGContextShowGlyphsAtPositions is 10.5+ for older versions use CGContextShowGlyphsWithAdvances michael@0: if (isGradient(aPattern)) { michael@0: CGContextSetTextDrawingMode(cg, kCGTextClip); michael@0: CGRect extents; michael@0: if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) { michael@0: CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs]; michael@0: CTFontGetBoundingRectsForGlyphs(macFont->mCTFont, kCTFontDefaultOrientation, michael@0: &glyphs.front(), bboxes, aBuffer.mNumGlyphs); michael@0: extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, 1.0f); michael@0: ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(), michael@0: &positions.front(), aBuffer.mNumGlyphs, cg); michael@0: delete bboxes; michael@0: } else { michael@0: CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs]; michael@0: CGFontGetGlyphBBoxes(macFont->mFont, &glyphs.front(), aBuffer.mNumGlyphs, bboxes); michael@0: extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, macFont->mSize); michael@0: michael@0: CGContextSetFont(cg, macFont->mFont); michael@0: CGContextSetFontSize(cg, macFont->mSize); michael@0: CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(), michael@0: aBuffer.mNumGlyphs); michael@0: delete bboxes; michael@0: } michael@0: CGContextScaleCTM(cg, 1, -1); michael@0: DrawGradient(cg, aPattern, extents); michael@0: } else { michael@0: //XXX: with CoreGraphics we can stroke text directly instead of going michael@0: // through GetPath. It would be nice to add support for using that michael@0: CGContextSetTextDrawingMode(cg, kCGTextFill); michael@0: SetFillFromPattern(cg, mColorSpace, aPattern); michael@0: if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) { michael@0: ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(), michael@0: &positions.front(), michael@0: aBuffer.mNumGlyphs, cg); michael@0: } else { michael@0: CGContextSetFont(cg, macFont->mFont); michael@0: CGContextSetFontSize(cg, macFont->mSize); michael@0: CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(), michael@0: aBuffer.mNumGlyphs); michael@0: } michael@0: } michael@0: michael@0: fixer.Fix(mCg); michael@0: CGContextRestoreGState(cg); michael@0: } michael@0: michael@0: extern "C" { michael@0: void michael@0: CGContextResetClip(CGContextRef); michael@0: }; michael@0: michael@0: void michael@0: DrawTargetCG::CopySurface(SourceSurface *aSurface, michael@0: const IntRect& aSourceRect, michael@0: const IntPoint &aDestination) michael@0: { michael@0: MarkChanged(); michael@0: michael@0: if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE || michael@0: aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT || michael@0: aSurface->GetType() == SurfaceType::DATA) { michael@0: CGImageRef image = GetRetainedImageFromSourceSurface(aSurface); michael@0: michael@0: // XXX: it might be more efficient for us to do the copy directly if we have access to the bits michael@0: michael@0: CGContextSaveGState(mCg); michael@0: michael@0: // CopySurface ignores the clip, so we need to use private API to temporarily reset it michael@0: CGContextResetClip(mCg); michael@0: CGRect destRect = CGRectMake(aDestination.x, aDestination.y, michael@0: aSourceRect.width, aSourceRect.height); michael@0: CGContextClipToRect(mCg, destRect); michael@0: michael@0: CGContextSetBlendMode(mCg, kCGBlendModeCopy); michael@0: michael@0: CGContextScaleCTM(mCg, 1, -1); michael@0: michael@0: CGRect flippedRect = CGRectMake(aDestination.x - aSourceRect.x, -(aDestination.y - aSourceRect.y + double(CGImageGetHeight(image))), michael@0: CGImageGetWidth(image), CGImageGetHeight(image)); michael@0: michael@0: // Quartz seems to copy A8 surfaces incorrectly if we don't initialize them michael@0: // to transparent first. michael@0: if (mFormat == SurfaceFormat::A8) { michael@0: CGContextClearRect(mCg, flippedRect); michael@0: } michael@0: CGContextDrawImage(mCg, flippedRect, image); michael@0: michael@0: CGContextRestoreGState(mCg); michael@0: CGImageRelease(image); michael@0: } michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, const Color &aColor, const Point &aOffset, Float aSigma, CompositionOp aOperator) michael@0: { michael@0: MarkChanged(); michael@0: michael@0: CGImageRef image = GetRetainedImageFromSourceSurface(aSurface); michael@0: michael@0: IntSize size = aSurface->GetSize(); michael@0: CGContextSaveGState(mCg); michael@0: //XXX do we need to do the fixup here? michael@0: CGContextSetBlendMode(mCg, ToBlendMode(aOperator)); michael@0: michael@0: CGContextScaleCTM(mCg, 1, -1); michael@0: michael@0: CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + size.height), michael@0: size.width, size.height); michael@0: michael@0: CGColorRef color = ColorToCGColor(mColorSpace, aColor); michael@0: CGSize offset = {aOffset.x, -aOffset.y}; michael@0: // CoreGraphics needs twice sigma as it's amount of blur michael@0: CGContextSetShadowWithColor(mCg, offset, 2*aSigma, color); michael@0: CGColorRelease(color); michael@0: michael@0: CGContextDrawImage(mCg, flippedRect, image); michael@0: michael@0: CGImageRelease(image); michael@0: CGContextRestoreGState(mCg); michael@0: michael@0: } michael@0: michael@0: bool michael@0: DrawTargetCG::Init(BackendType aType, michael@0: unsigned char* aData, michael@0: const IntSize &aSize, michael@0: int32_t aStride, michael@0: SurfaceFormat aFormat) michael@0: { michael@0: // XXX: we should come up with some consistent semantics for dealing michael@0: // with zero area drawtargets michael@0: if (aSize.width <= 0 || aSize.height <= 0 || michael@0: // 32767 is the maximum size supported by cairo michael@0: // we clamp to that to make it easier to interoperate michael@0: aSize.width > 32767 || aSize.height > 32767) { michael@0: gfxWarning() << "Failed to Init() DrawTargetCG because of bad size."; michael@0: mColorSpace = nullptr; michael@0: mCg = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: //XXX: handle SurfaceFormat michael@0: michael@0: //XXX: we'd be better off reusing the Colorspace across draw targets michael@0: mColorSpace = CGColorSpaceCreateDeviceRGB(); michael@0: michael@0: if (aData == nullptr && aType != BackendType::COREGRAPHICS_ACCELERATED) { michael@0: // XXX: Currently, Init implicitly clears, that can often be a waste of time michael@0: size_t bufLen = BufferSizeFromStrideAndHeight(aStride, aSize.height); michael@0: if (bufLen == 0) { michael@0: mColorSpace = nullptr; michael@0: mCg = nullptr; michael@0: return false; michael@0: } michael@0: static_assert(sizeof(decltype(mData[0])) == 1, michael@0: "mData.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen"); michael@0: mData.Realloc(/* actually an object count */ bufLen); michael@0: aData = static_cast(mData); michael@0: memset(aData, 0, bufLen); michael@0: } michael@0: michael@0: mSize = aSize; michael@0: michael@0: if (aType == BackendType::COREGRAPHICS_ACCELERATED) { michael@0: RefPtr ioSurface = MacIOSurface::CreateIOSurface(aSize.width, aSize.height); michael@0: mCg = ioSurface->CreateIOSurfaceContext(); michael@0: // If we don't have the symbol for 'CreateIOSurfaceContext' mCg will be null michael@0: // and we will fallback to software below michael@0: } michael@0: michael@0: mFormat = SurfaceFormat::B8G8R8A8; michael@0: michael@0: if (!mCg || aType == BackendType::COREGRAPHICS) { michael@0: int bitsPerComponent = 8; michael@0: michael@0: CGBitmapInfo bitinfo; michael@0: if (aFormat == SurfaceFormat::A8) { michael@0: if (mColorSpace) michael@0: CGColorSpaceRelease(mColorSpace); michael@0: mColorSpace = nullptr; michael@0: bitinfo = kCGImageAlphaOnly; michael@0: mFormat = SurfaceFormat::A8; michael@0: } else { michael@0: bitinfo = kCGBitmapByteOrder32Host; michael@0: if (aFormat == SurfaceFormat::B8G8R8X8) { michael@0: bitinfo |= kCGImageAlphaNoneSkipFirst; michael@0: mFormat = aFormat; michael@0: } else { michael@0: bitinfo |= kCGImageAlphaPremultipliedFirst; michael@0: } michael@0: } michael@0: // XXX: what should we do if this fails? michael@0: mCg = CGBitmapContextCreate (aData, michael@0: mSize.width, michael@0: mSize.height, michael@0: bitsPerComponent, michael@0: aStride, michael@0: mColorSpace, michael@0: bitinfo); michael@0: } michael@0: michael@0: assert(mCg); michael@0: // CGContext's default to have the origin at the bottom left michael@0: // so flip it to the top left michael@0: CGContextTranslateCTM(mCg, 0, mSize.height); michael@0: CGContextScaleCTM(mCg, 1, -1); michael@0: // See Bug 722164 for performance details michael@0: // Medium or higher quality lead to expensive interpolation michael@0: // for canvas we want to use low quality interpolation michael@0: // to have competitive performance with other canvas michael@0: // implementation. michael@0: // XXX: Create input parameter to control interpolation and michael@0: // use the default for content. michael@0: CGContextSetInterpolationQuality(mCg, kCGInterpolationLow); michael@0: michael@0: michael@0: if (aType == BackendType::COREGRAPHICS_ACCELERATED) { michael@0: // The bitmap backend uses callac to clear, we can't do that without michael@0: // reading back the surface. This should trigger something equivilent michael@0: // to glClear. michael@0: ClearRect(Rect(0, 0, mSize.width, mSize.height)); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::Flush() michael@0: { michael@0: if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) { michael@0: CGContextFlush(mCg); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize) michael@0: { michael@0: // XXX: we should come up with some consistent semantics for dealing michael@0: // with zero area drawtargets michael@0: if (aSize.width == 0 || aSize.height == 0) { michael@0: mColorSpace = nullptr; michael@0: mCg = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: //XXX: handle SurfaceFormat michael@0: michael@0: //XXX: we'd be better off reusing the Colorspace across draw targets michael@0: mColorSpace = CGColorSpaceCreateDeviceRGB(); michael@0: michael@0: mSize = aSize; michael@0: michael@0: mCg = cgContext; michael@0: CGContextRetain(mCg); michael@0: michael@0: assert(mCg); michael@0: michael@0: // CGContext's default to have the origin at the bottom left. michael@0: // However, currently the only use of this function is to construct a michael@0: // DrawTargetCG around a CGContextRef from a cairo quartz surface which michael@0: // already has it's origin adjusted. michael@0: // michael@0: // CGContextTranslateCTM(mCg, 0, mSize.height); michael@0: // CGContextScaleCTM(mCg, 1, -1); michael@0: michael@0: mFormat = SurfaceFormat::B8G8R8A8; michael@0: if (GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) { michael@0: CGColorSpaceRef colorspace; michael@0: CGBitmapInfo bitinfo = CGBitmapContextGetBitmapInfo(mCg); michael@0: colorspace = CGBitmapContextGetColorSpace (mCg); michael@0: if (CGColorSpaceGetNumberOfComponents(colorspace) == 1) { michael@0: mFormat = SurfaceFormat::A8; michael@0: } else if ((bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaNoneSkipFirst) { michael@0: mFormat = SurfaceFormat::B8G8R8X8; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: DrawTargetCG::Init(BackendType aType, const IntSize &aSize, SurfaceFormat &aFormat) michael@0: { michael@0: int32_t stride = GetAlignedStride<16>(aSize.width * BytesPerPixel(aFormat)); michael@0: michael@0: // Calling Init with aData == nullptr will allocate. michael@0: return Init(aType, nullptr, aSize, stride, aFormat); michael@0: } michael@0: michael@0: TemporaryRef michael@0: DrawTargetCG::CreatePathBuilder(FillRule aFillRule) const michael@0: { michael@0: RefPtr pb = new PathBuilderCG(aFillRule); michael@0: return pb; michael@0: } michael@0: michael@0: void* michael@0: DrawTargetCG::GetNativeSurface(NativeSurfaceType aType) michael@0: { michael@0: if ((aType == NativeSurfaceType::CGCONTEXT && GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) || michael@0: (aType == NativeSurfaceType::CGCONTEXT_ACCELERATED && GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE)) { michael@0: return mCg; michael@0: } else { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::Mask(const Pattern &aSource, michael@0: const Pattern &aMask, michael@0: const DrawOptions &aDrawOptions) michael@0: { michael@0: MarkChanged(); michael@0: michael@0: CGContextSaveGState(mCg); michael@0: michael@0: if (isGradient(aMask)) { michael@0: assert(0); michael@0: } else { michael@0: if (aMask.GetType() == PatternType::COLOR) { michael@0: DrawOptions drawOptions(aDrawOptions); michael@0: const Color& color = static_cast(aMask).mColor; michael@0: drawOptions.mAlpha *= color.a; michael@0: assert(0); michael@0: // XXX: we need to get a rect that when transformed covers the entire surface michael@0: //Rect michael@0: //FillRect(rect, aSource, drawOptions); michael@0: } else if (aMask.GetType() == PatternType::SURFACE) { michael@0: const SurfacePattern& pat = static_cast(aMask); michael@0: CGImageRef mask = GetRetainedImageFromSourceSurface(pat.mSurface.get()); michael@0: Rect rect(0,0, CGImageGetWidth(mask), CGImageGetHeight(mask)); michael@0: // XXX: probably we need to do some flipping of the image or something michael@0: CGContextClipToMask(mCg, RectToCGRect(rect), mask); michael@0: FillRect(rect, aSource, aDrawOptions); michael@0: CGImageRelease(mask); michael@0: } michael@0: } michael@0: michael@0: CGContextRestoreGState(mCg); michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::PushClipRect(const Rect &aRect) michael@0: { michael@0: CGContextSaveGState(mCg); michael@0: michael@0: /* We go through a bit of trouble to temporarilly set the transform michael@0: * while we add the path */ michael@0: CGAffineTransform previousTransform = CGContextGetCTM(mCg); michael@0: CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: CGContextClipToRect(mCg, RectToCGRect(aRect)); michael@0: CGContextSetCTM(mCg, previousTransform); michael@0: } michael@0: michael@0: michael@0: void michael@0: DrawTargetCG::PushClip(const Path *aPath) michael@0: { michael@0: CGContextSaveGState(mCg); michael@0: michael@0: CGContextBeginPath(mCg); michael@0: assert(aPath->GetBackendType() == BackendType::COREGRAPHICS); michael@0: michael@0: const PathCG *cgPath = static_cast(aPath); michael@0: michael@0: // Weirdly, CoreGraphics clips empty paths as all shown michael@0: // but emtpy rects as all clipped. We detect this situation and michael@0: // workaround it appropriately michael@0: if (CGPathIsEmpty(cgPath->GetPath())) { michael@0: // XXX: should we return here? michael@0: CGContextClipToRect(mCg, CGRectZero); michael@0: } michael@0: michael@0: michael@0: /* We go through a bit of trouble to temporarilly set the transform michael@0: * while we add the path. XXX: this could be improved if we keep michael@0: * the CTM as resident state on the DrawTarget. */ michael@0: CGContextSaveGState(mCg); michael@0: CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform)); michael@0: CGContextAddPath(mCg, cgPath->GetPath()); michael@0: CGContextRestoreGState(mCg); michael@0: michael@0: if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD) michael@0: CGContextEOClip(mCg); michael@0: else michael@0: CGContextClip(mCg); michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::PopClip() michael@0: { michael@0: CGContextRestoreGState(mCg); michael@0: } michael@0: michael@0: void michael@0: DrawTargetCG::MarkChanged() michael@0: { michael@0: if (mSnapshot) { michael@0: if (mSnapshot->refCount() > 1) { michael@0: // We only need to worry about snapshots that someone else knows about michael@0: mSnapshot->DrawTargetWillChange(); michael@0: } michael@0: mSnapshot = nullptr; michael@0: } michael@0: } michael@0: michael@0: CGContextRef michael@0: BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget *aDT) michael@0: { michael@0: if (aDT->GetType() == BackendType::COREGRAPHICS || aDT->GetType() == BackendType::COREGRAPHICS_ACCELERATED) { michael@0: DrawTargetCG* cgDT = static_cast(aDT); michael@0: cgDT->MarkChanged(); michael@0: michael@0: // swap out the context michael@0: CGContextRef cg = cgDT->mCg; michael@0: cgDT->mCg = nullptr; michael@0: michael@0: // save the state to make it easier for callers to avoid mucking with things michael@0: CGContextSaveGState(cg); michael@0: michael@0: CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(cgDT->mTransform)); michael@0: michael@0: return cg; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg) michael@0: { michael@0: DrawTargetCG* cgDT = static_cast(aDT); michael@0: michael@0: CGContextRestoreGState(cg); michael@0: cgDT->mCg = cg; michael@0: } michael@0: michael@0: michael@0: } michael@0: }