/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "PathCG.h" #include #include "Logging.h" #include "PathHelpers.h" namespace mozilla { namespace gfx { static inline Rect CGRectToRect(CGRect rect) { return Rect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); } static inline Point CGPointToPoint(CGPoint point) { return Point(point.x, point.y); } static inline void SetStrokeOptions(CGContextRef cg, const StrokeOptions &aStrokeOptions) { switch (aStrokeOptions.mLineCap) { case CapStyle::BUTT: CGContextSetLineCap(cg, kCGLineCapButt); break; case CapStyle::ROUND: CGContextSetLineCap(cg, kCGLineCapRound); break; case CapStyle::SQUARE: CGContextSetLineCap(cg, kCGLineCapSquare); break; } switch (aStrokeOptions.mLineJoin) { case JoinStyle::BEVEL: CGContextSetLineJoin(cg, kCGLineJoinBevel); break; case JoinStyle::ROUND: CGContextSetLineJoin(cg, kCGLineJoinRound); break; case JoinStyle::MITER: case JoinStyle::MITER_OR_BEVEL: CGContextSetLineJoin(cg, kCGLineJoinMiter); break; } CGContextSetLineWidth(cg, aStrokeOptions.mLineWidth); CGContextSetMiterLimit(cg, aStrokeOptions.mMiterLimit); // XXX: rename mDashLength to dashLength if (aStrokeOptions.mDashLength > 0) { // we use a regular array instead of a std::vector here because we don't want to leak the include CGFloat *dashes = new CGFloat[aStrokeOptions.mDashLength]; for (size_t i=0; i PathBuilderCG::Finish() { return MakeAndAddRef(mCGPath, mFillRule); } already_AddRefed PathCG::CopyToBuilder(FillRule aFillRule) const { CGMutablePathRef path = CGPathCreateMutableCopy(mPath); return MakeAndAddRef(path, aFillRule); } already_AddRefed PathCG::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const { // 10.7 adds CGPathCreateMutableCopyByTransformingPath it might be faster than doing // this by hand struct TransformApplier { CGMutablePathRef path; CGAffineTransform transform; static void TranformCGPathApplierFunc(void *vinfo, const CGPathElement *element) { TransformApplier *info = reinterpret_cast(vinfo); switch (element->type) { case kCGPathElementMoveToPoint: { CGPoint pt = element->points[0]; CGPathMoveToPoint(info->path, &info->transform, pt.x, pt.y); break; } case kCGPathElementAddLineToPoint: { CGPoint pt = element->points[0]; CGPathAddLineToPoint(info->path, &info->transform, pt.x, pt.y); break; } case kCGPathElementAddQuadCurveToPoint: { CGPoint cpt = element->points[0]; CGPoint pt = element->points[1]; CGPathAddQuadCurveToPoint(info->path, &info->transform, cpt.x, cpt.y, pt.x, pt.y); break; } case kCGPathElementAddCurveToPoint: { CGPoint cpt1 = element->points[0]; CGPoint cpt2 = element->points[1]; CGPoint pt = element->points[2]; CGPathAddCurveToPoint(info->path, &info->transform, cpt1.x, cpt1.y, cpt2.x, cpt2.y, pt.x, pt.y); break; } case kCGPathElementCloseSubpath: { CGPathCloseSubpath(info->path); break; } } } }; TransformApplier ta; ta.path = CGPathCreateMutable(); ta.transform = GfxMatrixToCGAffineTransform(aTransform); CGPathApply(mPath, &ta, TransformApplier::TranformCGPathApplierFunc); return MakeAndAddRef(ta.path, aFillRule); } static void StreamPathToSinkApplierFunc(void *vinfo, const CGPathElement *element) { PathSink *sink = reinterpret_cast(vinfo); switch (element->type) { case kCGPathElementMoveToPoint: { CGPoint pt = element->points[0]; sink->MoveTo(CGPointToPoint(pt)); break; } case kCGPathElementAddLineToPoint: { CGPoint pt = element->points[0]; sink->LineTo(CGPointToPoint(pt)); break; } case kCGPathElementAddQuadCurveToPoint: { CGPoint cpt = element->points[0]; CGPoint pt = element->points[1]; sink->QuadraticBezierTo(CGPointToPoint(cpt), CGPointToPoint(pt)); break; } case kCGPathElementAddCurveToPoint: { CGPoint cpt1 = element->points[0]; CGPoint cpt2 = element->points[1]; CGPoint pt = element->points[2]; sink->BezierTo(CGPointToPoint(cpt1), CGPointToPoint(cpt2), CGPointToPoint(pt)); break; } case kCGPathElementCloseSubpath: { sink->Close(); break; } } } void PathCG::StreamToSink(PathSink *aSink) const { CGPathApply(mPath, aSink, StreamPathToSinkApplierFunc); } bool PathCG::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const { Matrix inverse = aTransform; inverse.Invert(); Point transformedPoint = inverse.TransformPoint(aPoint); // We could probably drop the input transform and just transform the point at the caller? CGPoint point = {transformedPoint.x, transformedPoint.y}; // The transform parameter of CGPathContainsPoint doesn't seem to work properly on OS X 10.5 // so we transform aPoint ourselves. return CGPathContainsPoint(mPath, nullptr, point, mFillRule == FillRule::FILL_EVEN_ODD); } static size_t PutBytesNull(void *info, const void *buffer, size_t count) { return count; } /* The idea of a scratch context comes from WebKit */ static CGContextRef CreateScratchContext() { CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr}; CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks); CGContextRef cg = CGPDFContextCreate(consumer, nullptr, nullptr); CGDataConsumerRelease(consumer); return cg; } static CGContextRef ScratchContext() { static CGContextRef cg = CreateScratchContext(); return cg; } bool PathCG::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, const Point &aPoint, const Matrix &aTransform) const { Matrix inverse = aTransform; inverse.Invert(); Point transformedPoint = inverse.TransformPoint(aPoint); // We could probably drop the input transform and just transform the point at the caller? CGPoint point = {transformedPoint.x, transformedPoint.y}; CGContextRef cg = ScratchContext(); CGContextSaveGState(cg); CGContextBeginPath(cg); CGContextAddPath(cg, mPath); SetStrokeOptions(cg, aStrokeOptions); CGContextReplacePathWithStrokedPath(cg); CGContextRestoreGState(cg); CGPathRef sPath = CGContextCopyPath(cg); bool inStroke = CGPathContainsPoint(sPath, nullptr, point, false); CGPathRelease(sPath); return inStroke; } //XXX: what should these functions return for an empty path? // currently they return CGRectNull {inf,inf, 0, 0} Rect PathCG::GetBounds(const Matrix &aTransform) const { //XXX: are these bounds tight enough Rect bounds = CGRectToRect(CGPathGetBoundingBox(mPath)); //XXX: currently this returns the bounds of the transformed bounds // this is strictly looser than the bounds of the transformed path return aTransform.TransformBounds(bounds); } Rect PathCG::GetStrokedBounds(const StrokeOptions &aStrokeOptions, const Matrix &aTransform) const { // 10.7 has CGPathCreateCopyByStrokingPath which we could use // instead of this scratch context business CGContextRef cg = ScratchContext(); CGContextSaveGState(cg); CGContextBeginPath(cg); CGContextAddPath(cg, mPath); SetStrokeOptions(cg, aStrokeOptions); CGContextReplacePathWithStrokedPath(cg); Rect bounds = CGRectToRect(CGContextGetPathBoundingBox(cg)); CGContextRestoreGState(cg); if (!bounds.IsFinite()) { return Rect(); } return aTransform.TransformBounds(bounds); } } // namespace gfx } // namespace mozilla