PathSegmentData SVGPathByteStreamSource::parseSegment() { ASSERT(hasMoreData()); PathSegmentData segment; segment.command = static_cast<SVGPathSegType>(readSVGSegmentType()); switch (segment.command) { case PathSegCurveToCubicRel: case PathSegCurveToCubicAbs: segment.point1 = readFloatPoint(); /* fall through */ case PathSegCurveToCubicSmoothRel: case PathSegCurveToCubicSmoothAbs: segment.point2 = readFloatPoint(); /* fall through */ case PathSegMoveToRel: case PathSegMoveToAbs: case PathSegLineToRel: case PathSegLineToAbs: case PathSegCurveToQuadraticSmoothRel: case PathSegCurveToQuadraticSmoothAbs: segment.targetPoint = readFloatPoint(); break; case PathSegLineToHorizontalRel: case PathSegLineToHorizontalAbs: segment.targetPoint.setX(readFloat()); break; case PathSegLineToVerticalRel: case PathSegLineToVerticalAbs: segment.targetPoint.setY(readFloat()); break; case PathSegClosePath: break; case PathSegCurveToQuadraticRel: case PathSegCurveToQuadraticAbs: segment.point1 = readFloatPoint(); segment.targetPoint = readFloatPoint(); break; case PathSegArcRel: case PathSegArcAbs: { segment.arcRadii() = readFloatPoint(); segment.setArcAngle(readFloat()); segment.arcLarge = readFlag(); segment.arcSweep = readFlag(); segment.targetPoint = readFloatPoint(); break; } default: ASSERT_NOT_REACHED(); } return segment; }
PathSegmentData consumeInterpolableArc(const InterpolableValue& value, SVGPathSegType segType, PathCoordinates& coordinates) { const InterpolableList& list = toInterpolableList(value); bool isAbsolute = isAbsolutePathSegType(segType); PathSegmentData segment; segment.command = segType; segment.targetPoint.setX(consumeInterpolableCoordinateAxis( list.get(0), isAbsolute, coordinates.currentX)); segment.targetPoint.setY(consumeInterpolableCoordinateAxis( list.get(1), isAbsolute, coordinates.currentY)); segment.arcRadii().setX(toInterpolableNumber(list.get(2))->value()); segment.arcRadii().setY(toInterpolableNumber(list.get(3))->value()); segment.setArcAngle(toInterpolableNumber(list.get(4))->value()); segment.arcLarge = toInterpolableBool(list.get(5))->value(); segment.arcSweep = toInterpolableBool(list.get(6))->value(); return segment; }
bool SVGPathBlender::BlendState::blendSegments( const PathSegmentData& fromSeg, const PathSegmentData& toSeg, PathSegmentData& blendedSegment) { if (!canBlend(fromSeg, toSeg)) return false; blendedSegment.command = m_isInFirstHalfOfAnimation ? fromSeg.command : toSeg.command; switch (toSeg.command) { case PathSegCurveToCubicRel: case PathSegCurveToCubicAbs: blendedSegment.point1 = blendAnimatedFloatPoint(fromSeg.point1, toSeg.point1); /* fall through */ case PathSegCurveToCubicSmoothRel: case PathSegCurveToCubicSmoothAbs: blendedSegment.point2 = blendAnimatedFloatPoint(fromSeg.point2, toSeg.point2); /* fall through */ case PathSegMoveToRel: case PathSegMoveToAbs: case PathSegLineToRel: case PathSegLineToAbs: case PathSegCurveToQuadraticSmoothRel: case PathSegCurveToQuadraticSmoothAbs: blendedSegment.targetPoint = blendAnimatedFloatPoint(fromSeg.targetPoint, toSeg.targetPoint); break; case PathSegLineToHorizontalRel: case PathSegLineToHorizontalAbs: blendedSegment.targetPoint.setX(blendAnimatedDimensonalFloat( fromSeg.targetPoint.x(), toSeg.targetPoint.x(), BlendHorizontal)); break; case PathSegLineToVerticalRel: case PathSegLineToVerticalAbs: blendedSegment.targetPoint.setY(blendAnimatedDimensonalFloat( fromSeg.targetPoint.y(), toSeg.targetPoint.y(), BlendVertical)); break; case PathSegClosePath: break; case PathSegCurveToQuadraticRel: case PathSegCurveToQuadraticAbs: blendedSegment.targetPoint = blendAnimatedFloatPoint(fromSeg.targetPoint, toSeg.targetPoint); blendedSegment.point1 = blendAnimatedFloatPoint(fromSeg.point1, toSeg.point1); break; case PathSegArcRel: case PathSegArcAbs: blendedSegment.targetPoint = blendAnimatedFloatPoint(fromSeg.targetPoint, toSeg.targetPoint); blendedSegment.point1 = blendAnimatedFloatPointSameCoordinates( fromSeg.arcRadii(), toSeg.arcRadii()); blendedSegment.point2 = blendAnimatedFloatPointSameCoordinates(fromSeg.point2, toSeg.point2); if (m_addTypesCount) { blendedSegment.arcLarge = fromSeg.arcLarge || toSeg.arcLarge; blendedSegment.arcSweep = fromSeg.arcSweep || toSeg.arcSweep; } else { blendedSegment.arcLarge = m_isInFirstHalfOfAnimation ? fromSeg.arcLarge : toSeg.arcLarge; blendedSegment.arcSweep = m_isInFirstHalfOfAnimation ? fromSeg.arcSweep : toSeg.arcSweep; } break; default: ASSERT_NOT_REACHED(); } updateCurrentPoint(m_fromSubPathPoint, m_fromCurrentPoint, fromSeg); updateCurrentPoint(m_toSubPathPoint, m_toCurrentPoint, toSeg); return true; }
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; }
// 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; }