std::unique_ptr<InterpolableValue> consumeArc(const PathSegmentData& segment,
                                              PathCoordinates& coordinates) {
  bool isAbsolute = isAbsolutePathSegType(segment.command);
  std::unique_ptr<InterpolableList> result = InterpolableList::create(7);
  result->set(
      0, consumeCoordinateAxis(segment.x(), isAbsolute, coordinates.currentX));
  result->set(
      1, consumeCoordinateAxis(segment.y(), isAbsolute, coordinates.currentY));
  result->set(2, InterpolableNumber::create(segment.r1()));
  result->set(3, InterpolableNumber::create(segment.r2()));
  result->set(4, InterpolableNumber::create(segment.arcAngle()));
  result->set(5, InterpolableBool::create(segment.largeArcFlag()));
  result->set(6, InterpolableBool::create(segment.sweepFlag()));
  return std::move(result);
}
void SVGPathBuilder::emitSegment(const PathSegmentData& segment)
{
    switch (segment.command) {
    case PathSegClosePath:
        emitClose();
        break;
    case PathSegMoveToAbs:
        emitMoveTo(
            segment.targetPoint);
        break;
    case PathSegMoveToRel:
        emitMoveTo(
            m_currentPoint + segment.targetPoint);
        break;
    case PathSegLineToAbs:
        emitLineTo(
            segment.targetPoint);
        break;
    case PathSegLineToRel:
        emitLineTo(
            m_currentPoint + segment.targetPoint);
        break;
    case PathSegLineToHorizontalAbs:
        emitLineTo(
            FloatPoint(segment.targetPoint.x(), m_currentPoint.y()));
        break;
    case PathSegLineToHorizontalRel:
        emitLineTo(
            m_currentPoint + FloatSize(segment.targetPoint.x(), 0));
        break;
    case PathSegLineToVerticalAbs:
        emitLineTo(
            FloatPoint(m_currentPoint.x(), segment.targetPoint.y()));
        break;
    case PathSegLineToVerticalRel:
        emitLineTo(
            m_currentPoint + FloatSize(0, segment.targetPoint.y()));
        break;
    case PathSegCurveToQuadraticAbs:
        emitQuadTo(
            segment.point1,
            segment.targetPoint);
        break;
    case PathSegCurveToQuadraticRel:
        emitQuadTo(
            m_currentPoint + segment.point1,
            m_currentPoint + segment.targetPoint);
        break;
    case PathSegCurveToQuadraticSmoothAbs:
        emitSmoothQuadTo(
            segment.targetPoint);
        break;
    case PathSegCurveToQuadraticSmoothRel:
        emitSmoothQuadTo(
            m_currentPoint + segment.targetPoint);
        break;
    case PathSegCurveToCubicAbs:
        emitCubicTo(
            segment.point1,
            segment.point2,
            segment.targetPoint);
        break;
    case PathSegCurveToCubicRel:
        emitCubicTo(
            m_currentPoint + segment.point1,
            m_currentPoint + segment.point2,
            m_currentPoint + segment.targetPoint);
        break;
    case PathSegCurveToCubicSmoothAbs:
        emitSmoothCubicTo(
            segment.point2,
            segment.targetPoint);
        break;
    case PathSegCurveToCubicSmoothRel:
        emitSmoothCubicTo(
            m_currentPoint + segment.point2,
            m_currentPoint + segment.targetPoint);
        break;
    case PathSegArcAbs:
        emitArcTo(
            segment.targetPoint,
            toFloatSize(segment.arcRadii()),
            segment.arcAngle(),
            segment.largeArcFlag(),
            segment.sweepFlag());
        break;
    case PathSegArcRel:
        emitArcTo(
            m_currentPoint + segment.targetPoint,
            toFloatSize(segment.arcRadii()),
            segment.arcAngle(),
            segment.largeArcFlag(),
            segment.sweepFlag());
        break;
    default:
        ASSERT_NOT_REACHED();
    }

    m_lastCommand = segment.command;
}
Example #3
0
// This works by converting the SVG arc to "simple" beziers.
// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
// See also SVG implementation notes:
// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
bool SVGPathNormalizer::decomposeArcToCubic(const FloatPoint& currentPoint,
                                            const PathSegmentData& arcSegment) {
  // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a
  // "lineto") joining the endpoints.
  // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
  float rx = fabsf(arcSegment.arcRadii().x());
  float ry = fabsf(arcSegment.arcRadii().y());
  if (!rx || !ry)
    return false;

  // If the current point and target point for the arc are identical, it should
  // be treated as a zero length path. This ensures continuity in animations.
  if (arcSegment.targetPoint == currentPoint)
    return false;

  float angle = arcSegment.arcAngle();

  FloatSize midPointDistance = currentPoint - arcSegment.targetPoint;
  midPointDistance.scale(0.5f);

  AffineTransform pointTransform;
  pointTransform.rotate(-angle);

  FloatPoint transformedMidPoint = pointTransform.mapPoint(
      FloatPoint(midPointDistance.width(), midPointDistance.height()));
  float squareRx = rx * rx;
  float squareRy = ry * ry;
  float squareX = transformedMidPoint.x() * transformedMidPoint.x();
  float squareY = transformedMidPoint.y() * transformedMidPoint.y();

  // Check if the radii are big enough to draw the arc, scale radii if not.
  // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
  float radiiScale = squareX / squareRx + squareY / squareRy;
  if (radiiScale > 1) {
    rx *= sqrtf(radiiScale);
    ry *= sqrtf(radiiScale);
  }

  pointTransform.makeIdentity();
  pointTransform.scale(1 / rx, 1 / ry);
  pointTransform.rotate(-angle);

  FloatPoint point1 = pointTransform.mapPoint(currentPoint);
  FloatPoint point2 = pointTransform.mapPoint(arcSegment.targetPoint);
  FloatSize delta = point2 - point1;

  float d = delta.width() * delta.width() + delta.height() * delta.height();
  float scaleFactorSquared = std::max(1 / d - 0.25f, 0.f);

  float scaleFactor = sqrtf(scaleFactorSquared);
  if (arcSegment.arcSweep == arcSegment.arcLarge)
    scaleFactor = -scaleFactor;

  delta.scale(scaleFactor);
  FloatPoint centerPoint = point1 + point2;
  centerPoint.scale(0.5f, 0.5f);
  centerPoint.move(-delta.height(), delta.width());

  float theta1 = FloatPoint(point1 - centerPoint).slopeAngleRadians();
  float theta2 = FloatPoint(point2 - centerPoint).slopeAngleRadians();

  float thetaArc = theta2 - theta1;
  if (thetaArc < 0 && arcSegment.arcSweep)
    thetaArc += twoPiFloat;
  else if (thetaArc > 0 && !arcSegment.arcSweep)
    thetaArc -= twoPiFloat;

  pointTransform.makeIdentity();
  pointTransform.rotate(angle);
  pointTransform.scale(rx, ry);

  // Some results of atan2 on some platform implementations are not exact
  // enough. So that we get more cubic curves than expected here. Adding 0.001f
  // reduces the count of sgements to the correct count.
  int segments = ceilf(fabsf(thetaArc / (piOverTwoFloat + 0.001f)));
  for (int i = 0; i < segments; ++i) {
    float startTheta = theta1 + i * thetaArc / segments;
    float endTheta = theta1 + (i + 1) * thetaArc / segments;

    float t = (8 / 6.f) * tanf(0.25f * (endTheta - startTheta));
    if (!std::isfinite(t))
      return false;
    float sinStartTheta = sinf(startTheta);
    float cosStartTheta = cosf(startTheta);
    float sinEndTheta = sinf(endTheta);
    float cosEndTheta = cosf(endTheta);

    point1 = FloatPoint(cosStartTheta - t * sinStartTheta,
                        sinStartTheta + t * cosStartTheta);
    point1.move(centerPoint.x(), centerPoint.y());
    FloatPoint targetPoint = FloatPoint(cosEndTheta, sinEndTheta);
    targetPoint.move(centerPoint.x(), centerPoint.y());
    point2 = targetPoint;
    point2.move(t * sinEndTheta, -t * cosEndTheta);

    PathSegmentData cubicSegment;
    cubicSegment.command = PathSegCurveToCubicAbs;
    cubicSegment.point1 = pointTransform.mapPoint(point1);
    cubicSegment.point2 = pointTransform.mapPoint(point2);
    cubicSegment.targetPoint = pointTransform.mapPoint(targetPoint);

    m_consumer->emitSegment(cubicSegment);
  }
  return true;
}