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: michael@0: #include "PathCG.h" michael@0: #include michael@0: #include "DrawTargetCG.h" michael@0: #include "Logging.h" michael@0: michael@0: namespace mozilla { michael@0: namespace gfx { michael@0: michael@0: PathBuilderCG::~PathBuilderCG() michael@0: { michael@0: CGPathRelease(mCGPath); michael@0: } michael@0: michael@0: void michael@0: PathBuilderCG::MoveTo(const Point &aPoint) michael@0: { michael@0: CGPathMoveToPoint(mCGPath, nullptr, aPoint.x, aPoint.y); michael@0: } michael@0: michael@0: void michael@0: PathBuilderCG::LineTo(const Point &aPoint) michael@0: { michael@0: if (CGPathIsEmpty(mCGPath)) michael@0: MoveTo(aPoint); michael@0: else michael@0: CGPathAddLineToPoint(mCGPath, nullptr, aPoint.x, aPoint.y); michael@0: } michael@0: michael@0: void michael@0: PathBuilderCG::BezierTo(const Point &aCP1, michael@0: const Point &aCP2, michael@0: const Point &aCP3) michael@0: { michael@0: michael@0: if (CGPathIsEmpty(mCGPath)) michael@0: MoveTo(aCP1); michael@0: CGPathAddCurveToPoint(mCGPath, nullptr, michael@0: aCP1.x, aCP1.y, michael@0: aCP2.x, aCP2.y, michael@0: aCP3.x, aCP3.y); michael@0: michael@0: } michael@0: michael@0: void michael@0: PathBuilderCG::QuadraticBezierTo(const Point &aCP1, michael@0: const Point &aCP2) michael@0: { michael@0: if (CGPathIsEmpty(mCGPath)) michael@0: MoveTo(aCP1); michael@0: CGPathAddQuadCurveToPoint(mCGPath, nullptr, michael@0: aCP1.x, aCP1.y, michael@0: aCP2.x, aCP2.y); michael@0: } michael@0: michael@0: void michael@0: PathBuilderCG::Close() michael@0: { michael@0: if (!CGPathIsEmpty(mCGPath)) michael@0: CGPathCloseSubpath(mCGPath); michael@0: } michael@0: michael@0: void michael@0: PathBuilderCG::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, michael@0: Float aEndAngle, bool aAntiClockwise) michael@0: { michael@0: // Core Graphic's initial coordinate system is y-axis up, whereas Moz2D's is michael@0: // y-axis down. Core Graphics therefore considers "clockwise" to mean "sweep michael@0: // in the direction of decreasing angle" whereas Moz2D considers it to mean michael@0: // "sweep in the direction of increasing angle". In other words if this michael@0: // Moz2D method is instructed to sweep anti-clockwise we need to tell michael@0: // CGPathAddArc to sweep clockwise, and vice versa. Hence why we pass the michael@0: // value of aAntiClockwise directly to CGPathAddArc's "clockwise" bool michael@0: // parameter. michael@0: CGPathAddArc(mCGPath, nullptr, michael@0: aOrigin.x, aOrigin.y, michael@0: aRadius, michael@0: aStartAngle, michael@0: aEndAngle, michael@0: aAntiClockwise); michael@0: } michael@0: michael@0: Point michael@0: PathBuilderCG::CurrentPoint() const michael@0: { michael@0: CGPoint pt = CGPathGetCurrentPoint(mCGPath); michael@0: Point ret(pt.x, pt.y); michael@0: return ret; michael@0: } michael@0: michael@0: void michael@0: PathBuilderCG::EnsureActive(const Point &aPoint) michael@0: { michael@0: } michael@0: michael@0: TemporaryRef michael@0: PathBuilderCG::Finish() michael@0: { michael@0: RefPtr path = new PathCG(mCGPath, mFillRule); michael@0: return path; michael@0: } michael@0: michael@0: TemporaryRef michael@0: PathCG::CopyToBuilder(FillRule aFillRule) const michael@0: { michael@0: CGMutablePathRef path = CGPathCreateMutableCopy(mPath); michael@0: RefPtr builder = new PathBuilderCG(path, aFillRule); michael@0: return builder; michael@0: } michael@0: michael@0: michael@0: michael@0: TemporaryRef michael@0: PathCG::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const michael@0: { michael@0: // 10.7 adds CGPathCreateMutableCopyByTransformingPath it might be faster than doing michael@0: // this by hand michael@0: michael@0: struct TransformApplier { michael@0: CGMutablePathRef path; michael@0: CGAffineTransform transform; michael@0: static void michael@0: TranformCGPathApplierFunc(void *vinfo, const CGPathElement *element) michael@0: { michael@0: TransformApplier *info = reinterpret_cast(vinfo); michael@0: switch (element->type) { michael@0: case kCGPathElementMoveToPoint: michael@0: { michael@0: CGPoint pt = element->points[0]; michael@0: CGPathMoveToPoint(info->path, &info->transform, pt.x, pt.y); michael@0: break; michael@0: } michael@0: case kCGPathElementAddLineToPoint: michael@0: { michael@0: CGPoint pt = element->points[0]; michael@0: CGPathAddLineToPoint(info->path, &info->transform, pt.x, pt.y); michael@0: break; michael@0: } michael@0: case kCGPathElementAddQuadCurveToPoint: michael@0: { michael@0: CGPoint cpt = element->points[0]; michael@0: CGPoint pt = element->points[1]; michael@0: CGPathAddQuadCurveToPoint(info->path, &info->transform, cpt.x, cpt.y, pt.x, pt.y); michael@0: break; michael@0: } michael@0: case kCGPathElementAddCurveToPoint: michael@0: { michael@0: CGPoint cpt1 = element->points[0]; michael@0: CGPoint cpt2 = element->points[1]; michael@0: CGPoint pt = element->points[2]; michael@0: CGPathAddCurveToPoint(info->path, &info->transform, cpt1.x, cpt1.y, cpt2.x, cpt2.y, pt.x, pt.y); michael@0: break; michael@0: } michael@0: case kCGPathElementCloseSubpath: michael@0: { michael@0: CGPathCloseSubpath(info->path); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: }; michael@0: michael@0: TransformApplier ta; michael@0: ta.path = CGPathCreateMutable(); michael@0: ta.transform = GfxMatrixToCGAffineTransform(aTransform); michael@0: michael@0: CGPathApply(mPath, &ta, TransformApplier::TranformCGPathApplierFunc); michael@0: RefPtr builder = new PathBuilderCG(ta.path, aFillRule); michael@0: return builder; michael@0: } michael@0: michael@0: static void michael@0: StreamPathToSinkApplierFunc(void *vinfo, const CGPathElement *element) michael@0: { michael@0: PathSink *sink = reinterpret_cast(vinfo); michael@0: switch (element->type) { michael@0: case kCGPathElementMoveToPoint: michael@0: { michael@0: CGPoint pt = element->points[0]; michael@0: sink->MoveTo(CGPointToPoint(pt)); michael@0: break; michael@0: } michael@0: case kCGPathElementAddLineToPoint: michael@0: { michael@0: CGPoint pt = element->points[0]; michael@0: sink->LineTo(CGPointToPoint(pt)); michael@0: break; michael@0: } michael@0: case kCGPathElementAddQuadCurveToPoint: michael@0: { michael@0: CGPoint cpt = element->points[0]; michael@0: CGPoint pt = element->points[1]; michael@0: sink->QuadraticBezierTo(CGPointToPoint(cpt), michael@0: CGPointToPoint(pt)); michael@0: break; michael@0: } michael@0: case kCGPathElementAddCurveToPoint: michael@0: { michael@0: CGPoint cpt1 = element->points[0]; michael@0: CGPoint cpt2 = element->points[1]; michael@0: CGPoint pt = element->points[2]; michael@0: sink->BezierTo(CGPointToPoint(cpt1), michael@0: CGPointToPoint(cpt2), michael@0: CGPointToPoint(pt)); michael@0: break; michael@0: } michael@0: case kCGPathElementCloseSubpath: michael@0: { michael@0: sink->Close(); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: PathCG::StreamToSink(PathSink *aSink) const michael@0: { michael@0: CGPathApply(mPath, aSink, StreamPathToSinkApplierFunc); michael@0: } michael@0: michael@0: bool michael@0: PathCG::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const michael@0: { michael@0: Matrix inverse = aTransform; michael@0: inverse.Invert(); michael@0: Point transformedPoint = inverse*aPoint; michael@0: // We could probably drop the input transform and just transform the point at the caller? michael@0: CGPoint point = {transformedPoint.x, transformedPoint.y}; michael@0: michael@0: // The transform parameter of CGPathContainsPoint doesn't seem to work properly on OS X 10.5 michael@0: // so we transform aPoint ourselves. michael@0: return CGPathContainsPoint(mPath, nullptr, point, mFillRule == FillRule::FILL_EVEN_ODD); michael@0: } michael@0: michael@0: static size_t michael@0: PutBytesNull(void *info, const void *buffer, size_t count) michael@0: { michael@0: return count; michael@0: } michael@0: michael@0: /* The idea of a scratch context comes from WebKit */ michael@0: static CGContextRef michael@0: CreateScratchContext() michael@0: { michael@0: CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr}; michael@0: CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks); michael@0: CGContextRef cg = CGPDFContextCreate(consumer, nullptr, nullptr); michael@0: CGDataConsumerRelease(consumer); michael@0: return cg; michael@0: } michael@0: michael@0: static CGContextRef michael@0: ScratchContext() michael@0: { michael@0: static CGContextRef cg = CreateScratchContext(); michael@0: return cg; michael@0: } michael@0: michael@0: bool michael@0: PathCG::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, michael@0: const Point &aPoint, michael@0: const Matrix &aTransform) const michael@0: { michael@0: Matrix inverse = aTransform; michael@0: inverse.Invert(); michael@0: Point transformedPoint = inverse*aPoint; michael@0: // We could probably drop the input transform and just transform the point at the caller? michael@0: CGPoint point = {transformedPoint.x, transformedPoint.y}; michael@0: michael@0: CGContextRef cg = ScratchContext(); michael@0: michael@0: CGContextSaveGState(cg); michael@0: michael@0: CGContextBeginPath(cg); michael@0: CGContextAddPath(cg, mPath); michael@0: michael@0: SetStrokeOptions(cg, aStrokeOptions); michael@0: michael@0: CGContextReplacePathWithStrokedPath(cg); michael@0: CGContextRestoreGState(cg); michael@0: michael@0: CGPathRef sPath = CGContextCopyPath(cg); michael@0: bool inStroke = CGPathContainsPoint(sPath, nullptr, point, false); michael@0: CGPathRelease(sPath); michael@0: michael@0: return inStroke; michael@0: } michael@0: michael@0: //XXX: what should these functions return for an empty path? michael@0: // currently they return CGRectNull {inf,inf, 0, 0} michael@0: Rect michael@0: PathCG::GetBounds(const Matrix &aTransform) const michael@0: { michael@0: //XXX: are these bounds tight enough michael@0: Rect bounds = CGRectToRect(CGPathGetBoundingBox(mPath)); michael@0: michael@0: //XXX: currently this returns the bounds of the transformed bounds michael@0: // this is strictly looser than the bounds of the transformed path michael@0: return aTransform.TransformBounds(bounds); michael@0: } michael@0: michael@0: Rect michael@0: PathCG::GetStrokedBounds(const StrokeOptions &aStrokeOptions, michael@0: const Matrix &aTransform) const michael@0: { michael@0: // 10.7 has CGPathCreateCopyByStrokingPath which we could use michael@0: // instead of this scratch context business michael@0: CGContextRef cg = ScratchContext(); michael@0: michael@0: CGContextSaveGState(cg); michael@0: michael@0: CGContextBeginPath(cg); michael@0: CGContextAddPath(cg, mPath); michael@0: michael@0: SetStrokeOptions(cg, aStrokeOptions); michael@0: michael@0: CGContextReplacePathWithStrokedPath(cg); michael@0: Rect bounds = CGRectToRect(CGContextGetPathBoundingBox(cg)); michael@0: michael@0: CGContextRestoreGState(cg); michael@0: michael@0: if (!bounds.IsFinite()) { michael@0: return Rect(); michael@0: } michael@0: michael@0: return aTransform.TransformBounds(bounds); michael@0: } michael@0: michael@0: michael@0: } michael@0: michael@0: }