PathCairo.cpp (9483B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "PathCairo.h" 8 #include <math.h> 9 #include "DrawTargetCairo.h" 10 #include "Logging.h" 11 #include "PathHelpers.h" 12 #include "HelpersCairo.h" 13 14 namespace mozilla { 15 namespace gfx { 16 17 already_AddRefed<PathBuilder> PathBuilderCairo::Create(FillRule aFillRule) { 18 return MakeAndAddRef<PathBuilderCairo>(aFillRule); 19 } 20 21 PathBuilderCairo::PathBuilderCairo(FillRule aFillRule) : mFillRule(aFillRule) {} 22 23 void PathBuilderCairo::MoveTo(const Point& aPoint) { 24 cairo_path_data_t data; 25 data.header.type = CAIRO_PATH_MOVE_TO; 26 data.header.length = 2; 27 mPathData.push_back(data); 28 data.point.x = aPoint.x; 29 data.point.y = aPoint.y; 30 mPathData.push_back(data); 31 32 mBeginPoint = mCurrentPoint = aPoint; 33 } 34 35 void PathBuilderCairo::LineTo(const Point& aPoint) { 36 cairo_path_data_t data; 37 data.header.type = CAIRO_PATH_LINE_TO; 38 data.header.length = 2; 39 mPathData.push_back(data); 40 data.point.x = aPoint.x; 41 data.point.y = aPoint.y; 42 mPathData.push_back(data); 43 44 mCurrentPoint = aPoint; 45 } 46 47 void PathBuilderCairo::BezierTo(const Point& aCP1, const Point& aCP2, 48 const Point& aCP3) { 49 cairo_path_data_t data; 50 data.header.type = CAIRO_PATH_CURVE_TO; 51 data.header.length = 4; 52 mPathData.push_back(data); 53 data.point.x = aCP1.x; 54 data.point.y = aCP1.y; 55 mPathData.push_back(data); 56 data.point.x = aCP2.x; 57 data.point.y = aCP2.y; 58 mPathData.push_back(data); 59 data.point.x = aCP3.x; 60 data.point.y = aCP3.y; 61 mPathData.push_back(data); 62 63 mCurrentPoint = aCP3; 64 } 65 66 void PathBuilderCairo::QuadraticBezierTo(const Point& aCP1, const Point& aCP2) { 67 // We need to elevate the degree of this quadratic Bézier to cubic, so we're 68 // going to add an intermediate control point, and recompute control point 1. 69 // The first and last control points remain the same. 70 // This formula can be found on http://fontforge.sourceforge.net/bezier.html 71 Point CP0 = CurrentPoint(); 72 Point CP1 = (CP0 + aCP1 * 2.0) / 3.0; 73 Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0; 74 Point CP3 = aCP2; 75 76 cairo_path_data_t data; 77 data.header.type = CAIRO_PATH_CURVE_TO; 78 data.header.length = 4; 79 mPathData.push_back(data); 80 data.point.x = CP1.x; 81 data.point.y = CP1.y; 82 mPathData.push_back(data); 83 data.point.x = CP2.x; 84 data.point.y = CP2.y; 85 mPathData.push_back(data); 86 data.point.x = CP3.x; 87 data.point.y = CP3.y; 88 mPathData.push_back(data); 89 90 mCurrentPoint = aCP2; 91 } 92 93 void PathBuilderCairo::Close() { 94 cairo_path_data_t data; 95 data.header.type = CAIRO_PATH_CLOSE_PATH; 96 data.header.length = 1; 97 mPathData.push_back(data); 98 99 mCurrentPoint = mBeginPoint; 100 } 101 102 void PathBuilderCairo::Arc(const Point& aOrigin, float aRadius, 103 float aStartAngle, float aEndAngle, 104 bool aAntiClockwise) { 105 ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, 106 aAntiClockwise); 107 } 108 109 already_AddRefed<Path> PathBuilderCairo::Finish() { 110 return MakeAndAddRef<PathCairo>(mFillRule, mPathData, mCurrentPoint, 111 mBeginPoint); 112 } 113 114 PathCairo::PathCairo(FillRule aFillRule, 115 std::vector<cairo_path_data_t>& aPathData, 116 const Point& aCurrentPoint, const Point& aBeginPoint) 117 : mFillRule(aFillRule), 118 mContainingContext(nullptr), 119 mCurrentPoint(aCurrentPoint), 120 mBeginPoint(aBeginPoint) { 121 mPathData.swap(aPathData); 122 } 123 124 PathCairo::PathCairo(cairo_t* aContext) 125 : mFillRule(FillRule::FILL_WINDING), mContainingContext(nullptr) { 126 cairo_path_t* path = cairo_copy_path(aContext); 127 128 // XXX - mCurrentPoint is not properly set here, the same is true for the 129 // D2D Path code, we never require current point when hitting this codepath 130 // but this should be fixed. 131 for (int i = 0; i < path->num_data; i++) { 132 mPathData.push_back(path->data[i]); 133 } 134 135 cairo_path_destroy(path); 136 } 137 138 PathCairo::~PathCairo() { 139 if (mContainingContext) { 140 cairo_destroy(mContainingContext); 141 } 142 } 143 144 already_AddRefed<PathBuilder> PathCairo::CopyToBuilder( 145 FillRule aFillRule) const { 146 RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule); 147 148 builder->mPathData = mPathData; 149 builder->mCurrentPoint = mCurrentPoint; 150 builder->mBeginPoint = mBeginPoint; 151 152 return builder.forget(); 153 } 154 155 already_AddRefed<PathBuilder> PathCairo::TransformedCopyToBuilder( 156 const Matrix& aTransform, FillRule aFillRule) const { 157 RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule); 158 159 AppendPathToBuilder(builder, &aTransform); 160 builder->mCurrentPoint = aTransform.TransformPoint(mCurrentPoint); 161 builder->mBeginPoint = aTransform.TransformPoint(mBeginPoint); 162 163 return builder.forget(); 164 } 165 166 bool PathCairo::ContainsPoint(const Point& aPoint, 167 const Matrix& aTransform) const { 168 Matrix inverse = aTransform; 169 inverse.Invert(); 170 Point transformed = inverse.TransformPoint(aPoint); 171 172 EnsureContainingContext(aTransform); 173 174 return cairo_in_fill(mContainingContext, transformed.x, transformed.y); 175 } 176 177 bool PathCairo::StrokeContainsPoint(const StrokeOptions& aStrokeOptions, 178 const Point& aPoint, 179 const Matrix& aTransform) const { 180 Matrix inverse = aTransform; 181 inverse.Invert(); 182 Point transformed = inverse.TransformPoint(aPoint); 183 184 EnsureContainingContext(aTransform); 185 186 SetCairoStrokeOptions(mContainingContext, aStrokeOptions); 187 188 return cairo_in_stroke(mContainingContext, transformed.x, transformed.y); 189 } 190 191 Rect PathCairo::GetBounds(const Matrix& aTransform) const { 192 EnsureContainingContext(aTransform); 193 194 double x1, y1, x2, y2; 195 196 cairo_path_extents(mContainingContext, &x1, &y1, &x2, &y2); 197 Rect bounds(Float(x1), Float(y1), Float(x2 - x1), Float(y2 - y1)); 198 return aTransform.TransformBounds(bounds); 199 } 200 201 Rect PathCairo::GetStrokedBounds(const StrokeOptions& aStrokeOptions, 202 const Matrix& aTransform) const { 203 EnsureContainingContext(aTransform); 204 205 double x1, y1, x2, y2; 206 207 SetCairoStrokeOptions(mContainingContext, aStrokeOptions); 208 209 cairo_stroke_extents(mContainingContext, &x1, &y1, &x2, &y2); 210 Rect bounds((Float)x1, (Float)y1, (Float)(x2 - x1), (Float)(y2 - y1)); 211 return aTransform.TransformBounds(bounds); 212 } 213 214 void PathCairo::StreamToSink(PathSink* aSink) const { 215 for (size_t i = 0; i < mPathData.size(); i++) { 216 switch (mPathData[i].header.type) { 217 case CAIRO_PATH_MOVE_TO: 218 i++; 219 aSink->MoveTo(Point(mPathData[i].point.x, mPathData[i].point.y)); 220 break; 221 case CAIRO_PATH_LINE_TO: 222 i++; 223 aSink->LineTo(Point(mPathData[i].point.x, mPathData[i].point.y)); 224 break; 225 case CAIRO_PATH_CURVE_TO: 226 aSink->BezierTo( 227 Point(mPathData[i + 1].point.x, mPathData[i + 1].point.y), 228 Point(mPathData[i + 2].point.x, mPathData[i + 2].point.y), 229 Point(mPathData[i + 3].point.x, mPathData[i + 3].point.y)); 230 i += 3; 231 break; 232 case CAIRO_PATH_CLOSE_PATH: 233 aSink->Close(); 234 break; 235 default: 236 // Corrupt path data! 237 MOZ_ASSERT(false); 238 } 239 } 240 } 241 242 bool PathCairo::IsEmpty() const { 243 for (size_t i = 0; i < mPathData.size(); i++) { 244 switch (mPathData[i].header.type) { 245 case CAIRO_PATH_MOVE_TO: 246 break; 247 case CAIRO_PATH_CLOSE_PATH: 248 break; 249 default: 250 return false; 251 } 252 } 253 return true; 254 } 255 256 void PathCairo::EnsureContainingContext(const Matrix& aTransform) const { 257 if (mContainingContext) { 258 if (mContainingTransform.ExactlyEquals(aTransform)) { 259 return; 260 } 261 } else { 262 mContainingContext = cairo_create(DrawTargetCairo::GetDummySurface()); 263 } 264 265 mContainingTransform = aTransform; 266 267 cairo_matrix_t mat; 268 GfxMatrixToCairoMatrix(mContainingTransform, mat); 269 cairo_set_matrix(mContainingContext, &mat); 270 271 SetPathOnContext(mContainingContext); 272 } 273 274 void PathCairo::SetPathOnContext(cairo_t* aContext) const { 275 // Needs the correct fill rule set. 276 cairo_set_fill_rule(aContext, GfxFillRuleToCairoFillRule(mFillRule)); 277 278 cairo_new_path(aContext); 279 280 if (!mPathData.empty()) { 281 cairo_path_t path; 282 path.data = const_cast<cairo_path_data_t*>(&mPathData.front()); 283 path.num_data = mPathData.size(); 284 path.status = CAIRO_STATUS_SUCCESS; 285 cairo_append_path(aContext, &path); 286 } 287 } 288 289 void PathCairo::AppendPathToBuilder(PathBuilderCairo* aBuilder, 290 const Matrix* aTransform) const { 291 if (aTransform) { 292 size_t i = 0; 293 while (i < mPathData.size()) { 294 uint32_t pointCount = mPathData[i].header.length - 1; 295 aBuilder->mPathData.push_back(mPathData[i]); 296 i++; 297 for (uint32_t c = 0; c < pointCount; c++) { 298 cairo_path_data_t data; 299 Point newPoint = aTransform->TransformPoint( 300 Point(mPathData[i].point.x, mPathData[i].point.y)); 301 data.point.x = newPoint.x; 302 data.point.y = newPoint.y; 303 aBuilder->mPathData.push_back(data); 304 i++; 305 } 306 } 307 } else { 308 for (size_t i = 0; i < mPathData.size(); i++) { 309 aBuilder->mPathData.push_back(mPathData[i]); 310 } 311 } 312 } 313 314 } // namespace gfx 315 } // namespace mozilla