gfx/2d/Path.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/gfx/2d/Path.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,517 @@
     1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "2D.h"
    1.10 +#include "PathAnalysis.h"
    1.11 +#include "PathHelpers.h"
    1.12 +
    1.13 +namespace mozilla {
    1.14 +namespace gfx {
    1.15 +
    1.16 +static float CubicRoot(float aValue) {
    1.17 +  if (aValue < 0.0) {
    1.18 +    return -CubicRoot(-aValue);
    1.19 +  }
    1.20 +  else {
    1.21 +    return powf(aValue, 1.0f / 3.0f);
    1.22 +  }
    1.23 +}
    1.24 +
    1.25 +struct BezierControlPoints
    1.26 +{
    1.27 +  BezierControlPoints() {}
    1.28 +  BezierControlPoints(const Point &aCP1, const Point &aCP2,
    1.29 +                      const Point &aCP3, const Point &aCP4)
    1.30 +    : mCP1(aCP1), mCP2(aCP2), mCP3(aCP3), mCP4(aCP4)
    1.31 +  {
    1.32 +  }
    1.33 +
    1.34 +  Point mCP1, mCP2, mCP3, mCP4;
    1.35 +};
    1.36 +
    1.37 +void
    1.38 +FlattenBezier(const BezierControlPoints &aPoints,
    1.39 +              PathSink *aSink, Float aTolerance);
    1.40 +
    1.41 +
    1.42 +Path::Path()
    1.43 +{
    1.44 +}
    1.45 +
    1.46 +Path::~Path()
    1.47 +{
    1.48 +}
    1.49 +
    1.50 +Float
    1.51 +Path::ComputeLength()
    1.52 +{
    1.53 +  EnsureFlattenedPath();
    1.54 +  return mFlattenedPath->ComputeLength();
    1.55 +}
    1.56 +
    1.57 +Point
    1.58 +Path::ComputePointAtLength(Float aLength, Point* aTangent)
    1.59 +{
    1.60 +  EnsureFlattenedPath();
    1.61 +  return mFlattenedPath->ComputePointAtLength(aLength, aTangent);
    1.62 +}
    1.63 +
    1.64 +void
    1.65 +Path::EnsureFlattenedPath()
    1.66 +{
    1.67 +  if (!mFlattenedPath) {
    1.68 +    mFlattenedPath = new FlattenedPath();
    1.69 +    StreamToSink(mFlattenedPath);
    1.70 +  }
    1.71 +}
    1.72 +
    1.73 +// This is the maximum deviation we allow (with an additional ~20% margin of
    1.74 +// error) of the approximation from the actual Bezier curve.
    1.75 +const Float kFlatteningTolerance = 0.0001f;
    1.76 +
    1.77 +void
    1.78 +FlattenedPath::MoveTo(const Point &aPoint)
    1.79 +{
    1.80 +  MOZ_ASSERT(!mCalculatedLength);
    1.81 +  FlatPathOp op;
    1.82 +  op.mType = FlatPathOp::OP_MOVETO;
    1.83 +  op.mPoint = aPoint;
    1.84 +  mPathOps.push_back(op);
    1.85 +
    1.86 +  mLastMove = aPoint;
    1.87 +}
    1.88 +
    1.89 +void
    1.90 +FlattenedPath::LineTo(const Point &aPoint)
    1.91 +{
    1.92 +  MOZ_ASSERT(!mCalculatedLength);
    1.93 +  FlatPathOp op;
    1.94 +  op.mType = FlatPathOp::OP_LINETO;
    1.95 +  op.mPoint = aPoint;
    1.96 +  mPathOps.push_back(op);
    1.97 +}
    1.98 +
    1.99 +void
   1.100 +FlattenedPath::BezierTo(const Point &aCP1,
   1.101 +                        const Point &aCP2,
   1.102 +                        const Point &aCP3)
   1.103 +{
   1.104 +  MOZ_ASSERT(!mCalculatedLength);
   1.105 +  FlattenBezier(BezierControlPoints(CurrentPoint(), aCP1, aCP2, aCP3), this, kFlatteningTolerance);
   1.106 +}
   1.107 +
   1.108 +void
   1.109 +FlattenedPath::QuadraticBezierTo(const Point &aCP1,
   1.110 +                                 const Point &aCP2)
   1.111 +{
   1.112 +  MOZ_ASSERT(!mCalculatedLength);
   1.113 +  // We need to elevate the degree of this quadratic Bézier to cubic, so we're
   1.114 +  // going to add an intermediate control point, and recompute control point 1.
   1.115 +  // The first and last control points remain the same.
   1.116 +  // This formula can be found on http://fontforge.sourceforge.net/bezier.html
   1.117 +  Point CP0 = CurrentPoint();
   1.118 +  Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
   1.119 +  Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
   1.120 +  Point CP3 = aCP2;
   1.121 +
   1.122 +  BezierTo(CP1, CP2, CP3);
   1.123 +}
   1.124 +
   1.125 +void
   1.126 +FlattenedPath::Close()
   1.127 +{
   1.128 +  MOZ_ASSERT(!mCalculatedLength);
   1.129 +  LineTo(mLastMove);
   1.130 +}
   1.131 +
   1.132 +void
   1.133 +FlattenedPath::Arc(const Point &aOrigin, float aRadius, float aStartAngle,
   1.134 +                   float aEndAngle, bool aAntiClockwise)
   1.135 +{
   1.136 +  ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, aAntiClockwise);
   1.137 +}
   1.138 +
   1.139 +Float
   1.140 +FlattenedPath::ComputeLength()
   1.141 +{
   1.142 +  if (!mCalculatedLength) {
   1.143 +    Point currentPoint;
   1.144 +
   1.145 +    for (uint32_t i = 0; i < mPathOps.size(); i++) {
   1.146 +      if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) {
   1.147 +        currentPoint = mPathOps[i].mPoint;
   1.148 +      } else {
   1.149 +        mCachedLength += Distance(currentPoint, mPathOps[i].mPoint);
   1.150 +        currentPoint = mPathOps[i].mPoint;
   1.151 +      }
   1.152 +    }
   1.153 +
   1.154 +    mCalculatedLength =  true;
   1.155 +  }
   1.156 +
   1.157 +  return mCachedLength;
   1.158 +}
   1.159 +
   1.160 +Point
   1.161 +FlattenedPath::ComputePointAtLength(Float aLength, Point *aTangent)
   1.162 +{
   1.163 +  // We track the last point that -wasn't- in the same place as the current
   1.164 +  // point so if we pass the edge of the path with a bunch of zero length
   1.165 +  // paths we still get the correct tangent vector.
   1.166 +  Point lastPointSinceMove;
   1.167 +  Point currentPoint;
   1.168 +  for (uint32_t i = 0; i < mPathOps.size(); i++) {
   1.169 +    if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) {
   1.170 +      if (Distance(currentPoint, mPathOps[i].mPoint)) {
   1.171 +        lastPointSinceMove = currentPoint;
   1.172 +      }
   1.173 +      currentPoint = mPathOps[i].mPoint;
   1.174 +    } else {
   1.175 +      Float segmentLength = Distance(currentPoint, mPathOps[i].mPoint);
   1.176 +
   1.177 +      if (segmentLength) {
   1.178 +        lastPointSinceMove = currentPoint;
   1.179 +        if (segmentLength > aLength) {
   1.180 +          Point currentVector = mPathOps[i].mPoint - currentPoint;
   1.181 +          Point tangent = currentVector / segmentLength;
   1.182 +          if (aTangent) {
   1.183 +            *aTangent = tangent;
   1.184 +          }
   1.185 +          return currentPoint + tangent * aLength;
   1.186 +        }
   1.187 +      }
   1.188 +
   1.189 +      aLength -= segmentLength;
   1.190 +      currentPoint = mPathOps[i].mPoint;
   1.191 +    }
   1.192 +  }
   1.193 +
   1.194 +  Point currentVector = currentPoint - lastPointSinceMove;
   1.195 +  if (aTangent) {
   1.196 +    if (hypotf(currentVector.x, currentVector.y)) {
   1.197 +      *aTangent = currentVector / hypotf(currentVector.x, currentVector.y);
   1.198 +    } else {
   1.199 +      *aTangent = Point();
   1.200 +    }
   1.201 +  }
   1.202 +  return currentPoint;
   1.203 +}
   1.204 +
   1.205 +// This function explicitly permits aControlPoints to refer to the same object
   1.206 +// as either of the other arguments.
   1.207 +static void 
   1.208 +SplitBezier(const BezierControlPoints &aControlPoints,
   1.209 +            BezierControlPoints *aFirstSegmentControlPoints,
   1.210 +            BezierControlPoints *aSecondSegmentControlPoints,
   1.211 +            Float t)
   1.212 +{
   1.213 +  MOZ_ASSERT(aSecondSegmentControlPoints);
   1.214 +  
   1.215 +  *aSecondSegmentControlPoints = aControlPoints;
   1.216 +
   1.217 +  Point cp1a = aControlPoints.mCP1 + (aControlPoints.mCP2 - aControlPoints.mCP1) * t;
   1.218 +  Point cp2a = aControlPoints.mCP2 + (aControlPoints.mCP3 - aControlPoints.mCP2) * t;
   1.219 +  Point cp1aa = cp1a + (cp2a - cp1a) * t;
   1.220 +  Point cp3a = aControlPoints.mCP3 + (aControlPoints.mCP4 - aControlPoints.mCP3) * t;
   1.221 +  Point cp2aa = cp2a + (cp3a - cp2a) * t;
   1.222 +  Point cp1aaa = cp1aa + (cp2aa - cp1aa) * t;
   1.223 +  aSecondSegmentControlPoints->mCP4 = aControlPoints.mCP4;
   1.224 +
   1.225 +  if(aFirstSegmentControlPoints) {
   1.226 +    aFirstSegmentControlPoints->mCP1 = aControlPoints.mCP1;
   1.227 +    aFirstSegmentControlPoints->mCP2 = cp1a;
   1.228 +    aFirstSegmentControlPoints->mCP3 = cp1aa;
   1.229 +    aFirstSegmentControlPoints->mCP4 = cp1aaa;
   1.230 +  }
   1.231 +  aSecondSegmentControlPoints->mCP1 = cp1aaa;
   1.232 +  aSecondSegmentControlPoints->mCP2 = cp2aa;
   1.233 +  aSecondSegmentControlPoints->mCP3 = cp3a;
   1.234 +}
   1.235 +
   1.236 +static void
   1.237 +FlattenBezierCurveSegment(const BezierControlPoints &aControlPoints,
   1.238 +                          PathSink *aSink,
   1.239 +                          Float aTolerance)
   1.240 +{
   1.241 +  /* The algorithm implemented here is based on:
   1.242 +   * http://cis.usouthal.edu/~hain/general/Publications/Bezier/Bezier%20Offset%20Curves.pdf
   1.243 +   *
   1.244 +   * The basic premise is that for a small t the third order term in the
   1.245 +   * equation of a cubic bezier curve is insignificantly small. This can
   1.246 +   * then be approximated by a quadratic equation for which the maximum
   1.247 +   * difference from a linear approximation can be much more easily determined.
   1.248 +   */
   1.249 +  BezierControlPoints currentCP = aControlPoints;
   1.250 +
   1.251 +  Float t = 0;
   1.252 +  while (t < 1.0f) {
   1.253 +    Point cp21 = currentCP.mCP2 - currentCP.mCP3;
   1.254 +    Point cp31 = currentCP.mCP3 - currentCP.mCP1;
   1.255 +
   1.256 +    Float s3 = (cp31.x * cp21.y - cp31.y * cp21.x) / hypotf(cp21.x, cp21.y);
   1.257 +
   1.258 +    t = 2 * Float(sqrt(aTolerance / (3. * abs(s3))));
   1.259 +
   1.260 +    if (t >= 1.0f) {
   1.261 +      aSink->LineTo(aControlPoints.mCP4);
   1.262 +      break;
   1.263 +    }
   1.264 +
   1.265 +    Point prevCP2, prevCP3, nextCP1, nextCP2, nextCP3;
   1.266 +    SplitBezier(currentCP, nullptr, &currentCP, t);
   1.267 +
   1.268 +    aSink->LineTo(currentCP.mCP1);
   1.269 +  }
   1.270 +}
   1.271 +
   1.272 +static inline void
   1.273 +FindInflectionApproximationRange(BezierControlPoints aControlPoints,
   1.274 +                                 Float *aMin, Float *aMax, Float aT,
   1.275 +                                 Float aTolerance)
   1.276 +{
   1.277 +    SplitBezier(aControlPoints, nullptr, &aControlPoints, aT);
   1.278 +
   1.279 +    Point cp21 = aControlPoints.mCP2 - aControlPoints.mCP1;
   1.280 +    Point cp41 = aControlPoints.mCP4 - aControlPoints.mCP1;
   1.281 +
   1.282 +    if (cp21.x == 0 && cp21.y == 0) {
   1.283 +      // In this case s3 becomes lim[n->0] (cp41.x * n) / n - (cp41.y * n) / n = cp41.x - cp41.y.
   1.284 +
   1.285 +      // Use the absolute value so that Min and Max will correspond with the
   1.286 +      // minimum and maximum of the range.
   1.287 +      *aMin = aT - CubicRoot(abs(aTolerance / (cp41.x - cp41.y)));
   1.288 +      *aMax = aT + CubicRoot(abs(aTolerance / (cp41.x - cp41.y)));
   1.289 +      return;
   1.290 +    }
   1.291 +
   1.292 +    Float s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / hypotf(cp21.x, cp21.y);
   1.293 +
   1.294 +    if (s3 == 0) {
   1.295 +      // This means within the precision we have it can be approximated
   1.296 +      // infinitely by a linear segment. Deal with this by specifying the
   1.297 +      // approximation range as extending beyond the entire curve.
   1.298 +      *aMin = -1.0f;
   1.299 +      *aMax = 2.0f;
   1.300 +      return;
   1.301 +    }
   1.302 +
   1.303 +    Float tf = CubicRoot(abs(aTolerance / s3));
   1.304 +
   1.305 +    *aMin = aT - tf * (1 - aT);
   1.306 +    *aMax = aT + tf * (1 - aT);
   1.307 +}
   1.308 +
   1.309 +/* Find the inflection points of a bezier curve. Will return false if the
   1.310 + * curve is degenerate in such a way that it is best approximated by a straight
   1.311 + * line.
   1.312 + *
   1.313 + * The below algorithm was written by Jeff Muizelaar <jmuizelaar@mozilla.com>, explanation follows:
   1.314 + *
   1.315 + * The lower inflection point is returned in aT1, the higher one in aT2. In the
   1.316 + * case of a single inflection point this will be in aT1.
   1.317 + *
   1.318 + * The method is inspired by the algorithm in "analysis of in?ection points for planar cubic bezier curve"
   1.319 + *
   1.320 + * Here are some differences between this algorithm and versions discussed elsewhere in the literature:
   1.321 + *
   1.322 + * zhang et. al compute a0, d0 and e0 incrementally using the follow formula:
   1.323 + *
   1.324 + * Point a0 = CP2 - CP1
   1.325 + * Point a1 = CP3 - CP2
   1.326 + * Point a2 = CP4 - CP1
   1.327 + *
   1.328 + * Point d0 = a1 - a0
   1.329 + * Point d1 = a2 - a1
   1.330 + 
   1.331 + * Point e0 = d1 - d0
   1.332 + *
   1.333 + * this avoids any multiplications and may or may not be faster than the approach take below.
   1.334 + *
   1.335 + * "fast, precise flattening of cubic bezier path and ofset curves" by hain et. al
   1.336 + * Point a = CP1 + 3 * CP2 - 3 * CP3 + CP4
   1.337 + * Point b = 3 * CP1 - 6 * CP2 + 3 * CP3
   1.338 + * Point c = -3 * CP1 + 3 * CP2
   1.339 + * Point d = CP1
   1.340 + * the a, b, c, d can be expressed in terms of a0, d0 and e0 defined above as:
   1.341 + * c = 3 * a0
   1.342 + * b = 3 * d0
   1.343 + * a = e0
   1.344 + *
   1.345 + *
   1.346 + * a = 3a = a.y * b.x - a.x * b.y
   1.347 + * b = 3b = a.y * c.x - a.x * c.y
   1.348 + * c = 9c = b.y * c.x - b.x * c.y
   1.349 + *
   1.350 + * The additional multiples of 3 cancel each other out as show below:
   1.351 + *
   1.352 + * x = (-b + sqrt(b * b - 4 * a * c)) / (2 * a)
   1.353 + * x = (-3 * b + sqrt(3 * b * 3 * b - 4 * a * 3 * 9 * c / 3)) / (2 * 3 * a)
   1.354 + * x = 3 * (-b + sqrt(b * b - 4 * a * c)) / (2 * 3 * a)
   1.355 + * x = (-b + sqrt(b * b - 4 * a * c)) / (2 * a)
   1.356 + *
   1.357 + * I haven't looked into whether the formulation of the quadratic formula in
   1.358 + * hain has any numerical advantages over the one used below.
   1.359 + */
   1.360 +static inline void
   1.361 +FindInflectionPoints(const BezierControlPoints &aControlPoints,
   1.362 +                     Float *aT1, Float *aT2, uint32_t *aCount)
   1.363 +{
   1.364 +  // Find inflection points.
   1.365 +  // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation
   1.366 +  // of this approach.
   1.367 +  Point A = aControlPoints.mCP2 - aControlPoints.mCP1;
   1.368 +  Point B = aControlPoints.mCP3 - (aControlPoints.mCP2 * 2) + aControlPoints.mCP1;
   1.369 +  Point C = aControlPoints.mCP4 - (aControlPoints.mCP3 * 3) + (aControlPoints.mCP2 * 3) - aControlPoints.mCP1;
   1.370 +
   1.371 +  Float a = Float(B.x) * C.y - Float(B.y) * C.x;
   1.372 +  Float b = Float(A.x) * C.y - Float(A.y) * C.x;
   1.373 +  Float c = Float(A.x) * B.y - Float(A.y) * B.x;
   1.374 +
   1.375 +  if (a == 0) {
   1.376 +    // Not a quadratic equation.
   1.377 +    if (b == 0) {
   1.378 +      // Instead of a linear acceleration change we have a constant
   1.379 +      // acceleration change. This means the equation has no solution
   1.380 +      // and there are no inflection points, unless the constant is 0.
   1.381 +      // In that case the curve is a straight line, but we'll let
   1.382 +      // FlattenBezierCurveSegment deal with this.
   1.383 +      *aCount = 0;
   1.384 +      return;
   1.385 +    }
   1.386 +    *aT1 = -c / b;
   1.387 +    *aCount = 1;
   1.388 +    return;
   1.389 +  } else {
   1.390 +    Float discriminant = b * b - 4 * a * c;
   1.391 +
   1.392 +    if (discriminant < 0) {
   1.393 +      // No inflection points.
   1.394 +      *aCount = 0;
   1.395 +    } else if (discriminant == 0) {
   1.396 +      *aCount = 1;
   1.397 +      *aT1 = -b / (2 * a);
   1.398 +    } else {
   1.399 +      /* Use the following formula for computing the roots:
   1.400 +       *
   1.401 +       * q = -1/2 * (b + sign(b) * sqrt(b^2 - 4ac))
   1.402 +       * t1 = q / a
   1.403 +       * t2 = c / q
   1.404 +       */
   1.405 +      Float q = sqrtf(discriminant);
   1.406 +      if (b < 0) {
   1.407 +        q = b - q;
   1.408 +      } else {
   1.409 +        q = b + q;
   1.410 +      }
   1.411 +      q *= Float(-1./2);
   1.412 +
   1.413 +      *aT1 = q / a;
   1.414 +      *aT2 = c / q;
   1.415 +      if (*aT1 > *aT2) {
   1.416 +        std::swap(*aT1, *aT2);
   1.417 +      }
   1.418 +      *aCount = 2;
   1.419 +    }
   1.420 +  }
   1.421 +
   1.422 +  return;
   1.423 +}
   1.424 +
   1.425 +void
   1.426 +FlattenBezier(const BezierControlPoints &aControlPoints,
   1.427 +              PathSink *aSink, Float aTolerance)
   1.428 +{
   1.429 +  Float t1;
   1.430 +  Float t2;
   1.431 +  uint32_t count;
   1.432 +
   1.433 +  FindInflectionPoints(aControlPoints, &t1, &t2, &count);
   1.434 +
   1.435 +  // Check that at least one of the inflection points is inside [0..1]
   1.436 +  if (count == 0 || ((t1 < 0 || t1 > 1.0) && ((t2 < 0 || t2 > 1.0) || count == 1)) ) {
   1.437 +    FlattenBezierCurveSegment(aControlPoints, aSink, aTolerance);
   1.438 +    return;
   1.439 +  }
   1.440 +
   1.441 +  Float t1min = t1, t1max = t1, t2min = t2, t2max = t2;
   1.442 +
   1.443 +  BezierControlPoints remainingCP = aControlPoints;
   1.444 +
   1.445 +  // For both inflection points, calulate the range where they can be linearly
   1.446 +  // approximated if they are positioned within [0,1]
   1.447 +  if (count > 0 && t1 >= 0 && t1 < 1.0) {
   1.448 +    FindInflectionApproximationRange(aControlPoints, &t1min, &t1max, t1, aTolerance);
   1.449 +  }
   1.450 +  if (count > 1 && t2 >= 0 && t2 < 1.0) {
   1.451 +    FindInflectionApproximationRange(aControlPoints, &t2min, &t2max, t2, aTolerance);
   1.452 +  }
   1.453 +  BezierControlPoints nextCPs = aControlPoints;
   1.454 +  BezierControlPoints prevCPs;
   1.455 +
   1.456 +  // Process ranges. [t1min, t1max] and [t2min, t2max] are approximated by line
   1.457 +  // segments.
   1.458 +  if (t1min > 0) {
   1.459 +    // Flatten the Bezier up until the first inflection point's approximation
   1.460 +    // point.
   1.461 +    SplitBezier(aControlPoints, &prevCPs,
   1.462 +                &remainingCP, t1min);
   1.463 +    FlattenBezierCurveSegment(prevCPs, aSink, aTolerance);
   1.464 +  }
   1.465 +  if (t1max >= 0 && t1max < 1.0 && (count == 1 || t2min > t1max)) {
   1.466 +    // The second inflection point's approximation range begins after the end
   1.467 +    // of the first, approximate the first inflection point by a line and
   1.468 +    // subsequently flatten up until the end or the next inflection point.
   1.469 +    SplitBezier(aControlPoints, nullptr, &nextCPs, t1max);
   1.470 +
   1.471 +    aSink->LineTo(nextCPs.mCP1);
   1.472 +
   1.473 +    if (count == 1 || (count > 1 && t2min >= 1.0)) {
   1.474 +      // No more inflection points to deal with, flatten the rest of the curve.
   1.475 +      FlattenBezierCurveSegment(nextCPs, aSink, aTolerance);
   1.476 +    }
   1.477 +  } else if (count > 1 && t2min > 1.0) {
   1.478 +    // We've already concluded t2min <= t1max, so if this is true the
   1.479 +    // approximation range for the first inflection point runs past the
   1.480 +    // end of the curve, draw a line to the end and we're done.
   1.481 +    aSink->LineTo(aControlPoints.mCP4);
   1.482 +    return;
   1.483 +  }
   1.484 +
   1.485 +  if (count > 1 && t2min < 1.0 && t2max > 0) {
   1.486 +    if (t2min > 0 && t2min < t1max) {
   1.487 +      // In this case the t2 approximation range starts inside the t1
   1.488 +      // approximation range.
   1.489 +      SplitBezier(aControlPoints, nullptr, &nextCPs, t1max);
   1.490 +      aSink->LineTo(nextCPs.mCP1);
   1.491 +    } else if (t2min > 0 && t1max > 0) {
   1.492 +      SplitBezier(aControlPoints, nullptr, &nextCPs, t1max);
   1.493 +
   1.494 +      // Find a control points describing the portion of the curve between t1max and t2min.
   1.495 +      Float t2mina = (t2min - t1max) / (1 - t1max);
   1.496 +      SplitBezier(nextCPs, &prevCPs, &nextCPs, t2mina);
   1.497 +      FlattenBezierCurveSegment(prevCPs, aSink, aTolerance);
   1.498 +    } else if (t2min > 0) {
   1.499 +      // We have nothing interesting before t2min, find that bit and flatten it.
   1.500 +      SplitBezier(aControlPoints, &prevCPs, &nextCPs, t2min);
   1.501 +      FlattenBezierCurveSegment(prevCPs, aSink, aTolerance);
   1.502 +    }
   1.503 +    if (t2max < 1.0) {
   1.504 +      // Flatten the portion of the curve after t2max
   1.505 +      SplitBezier(aControlPoints, nullptr, &nextCPs, t2max);
   1.506 +
   1.507 +      // Draw a line to the start, this is the approximation between t2min and
   1.508 +      // t2max.
   1.509 +      aSink->LineTo(nextCPs.mCP1);
   1.510 +      FlattenBezierCurveSegment(nextCPs, aSink, aTolerance);
   1.511 +    } else {
   1.512 +      // Our approximation range extends beyond the end of the curve.
   1.513 +      aSink->LineTo(aControlPoints.mCP4);
   1.514 +      return;
   1.515 +    }
   1.516 +  }
   1.517 +}
   1.518 +
   1.519 +}
   1.520 +}

mercurial