content/svg/content/src/nsSVGPathDataParser.cpp
author Jonathan Watt <jwatt@jwatt.org>
Thu, 24 Oct 2013 14:46:38 +0200
changeset 151962 17156fbebbc8
parent 124426 4a06106700ba
child 152880 cb6165af87e7
permissions -rw-r--r--
Bug 930455 - Convert nsSVGArcConverter to Moz2D. r=heycam

/* -*- Mode: C++; tab-width: 2; 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 "nsSVGPathDataParser.h"

#include "mozilla/gfx/Point.h"
#include "nsSVGDataParser.h"
#include "SVGPathData.h"
#include "SVGPathSegUtils.h"
#include <stdlib.h>
#include <math.h>

using namespace mozilla;
using namespace mozilla::gfx;

nsresult nsSVGPathDataParser::Match()
{
  return MatchSvgPath();
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchCoordPair(float* aX, float* aY)
{
  ENSURE_MATCHED(MatchCoord(aX));

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }

  ENSURE_MATCHED(MatchCoord(aY));

  return NS_OK;
}

bool nsSVGPathDataParser::IsTokenCoordPairStarter()
{
  return IsTokenCoordStarter();
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchCoord(float* aX)
{
  ENSURE_MATCHED(MatchNumber(aX));

  return NS_OK;
}

bool nsSVGPathDataParser::IsTokenCoordStarter()
{
  return IsTokenNumberStarter();
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchFlag(bool* f)
{
  switch (mTokenVal) {
    case '0':
      *f = false;
      break;
    case '1':
      *f = true;
      break;
    default:
      return NS_ERROR_FAILURE;
  }
  GetNextToken();
  return NS_OK;
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchSvgPath()
{
  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  if (IsTokenSubPathsStarter()) {
    ENSURE_MATCHED(MatchSubPaths());
  }

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }
  
  return NS_OK;
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchSubPaths()
{
  ENSURE_MATCHED(MatchSubPath());

  while (1) {
    const char* pos = mTokenPos;

    while (IsTokenWspStarter()) {
      ENSURE_MATCHED(MatchWsp());
    }

    if (IsTokenSubPathStarter()) {
      ENSURE_MATCHED(MatchSubPath());
    }
    else {
      if (pos != mTokenPos) RewindTo(pos);
      break;
    }
  }
  
  return NS_OK;
}

bool nsSVGPathDataParser::IsTokenSubPathsStarter()
{
  return IsTokenSubPathStarter();
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchSubPath()
{
  ENSURE_MATCHED(MatchMoveto());

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  if (IsTokenSubPathElementsStarter())
    ENSURE_MATCHED(MatchSubPathElements());
  
  return NS_OK;
}

bool nsSVGPathDataParser::IsTokenSubPathStarter()
{
  return (tolower(mTokenVal) == 'm');
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchSubPathElements()
{
  ENSURE_MATCHED(MatchSubPathElement());

  while (1) {
    const char* pos = mTokenPos;

    while (IsTokenWspStarter()) {
      ENSURE_MATCHED(MatchWsp());
    }


    if (IsTokenSubPathElementStarter()) {
        ENSURE_MATCHED(MatchSubPathElement());
    }
    else {
      if (pos != mTokenPos) RewindTo(pos);
      return NS_OK;
    }
  }
  
  return NS_OK;
}

bool nsSVGPathDataParser::IsTokenSubPathElementsStarter()
{
  return IsTokenSubPathElementStarter();
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchSubPathElement()
{
  switch (tolower(mTokenVal)) {
    case 'z':
      ENSURE_MATCHED(MatchClosePath());
      break;
    case 'l':
      ENSURE_MATCHED(MatchLineto());
      break;      
    case 'h':
      ENSURE_MATCHED(MatchHorizontalLineto());
      break;
    case 'v':
      ENSURE_MATCHED(MatchVerticalLineto());
      break;
    case 'c':
      ENSURE_MATCHED(MatchCurveto());
      break;      
    case 's':
      ENSURE_MATCHED(MatchSmoothCurveto());
      break;
    case 'q':
      ENSURE_MATCHED(MatchQuadBezierCurveto());
      break;
    case 't':
      ENSURE_MATCHED(MatchSmoothQuadBezierCurveto());
      break;
    case 'a':
      ENSURE_MATCHED(MatchEllipticalArc());
      break;
    default:
      return NS_ERROR_FAILURE;
      break;
  }
  return NS_OK;
}

bool nsSVGPathDataParser::IsTokenSubPathElementStarter()
{
  switch (tolower(mTokenVal)) {
    case 'z': case 'l': case 'h': case 'v': case 'c':
    case 's': case 'q': case 't': case 'a':
      return true;
      break;
    default:
      return false;
      break;
  }
  return false;
}  

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchMoveto()
{
  bool absCoords;
  
  switch (mTokenVal) {
    case 'M':
      absCoords = true;
      break;
    case 'm':
      absCoords = false;
      break;
    default:
      return NS_ERROR_FAILURE;
  }

  GetNextToken();

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  ENSURE_MATCHED(MatchMovetoArgSeq(absCoords));
  
  return NS_OK;
}

nsresult nsSVGPathDataParser::MatchMovetoArgSeq(bool absCoords)
{
  
  float x, y;
  ENSURE_MATCHED(MatchCoordPair(&x, &y));

  nsresult rv = StoreMoveTo(absCoords, x, y);
  NS_ENSURE_SUCCESS(rv, rv);
    
  const char* pos = mTokenPos;

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }

  if (IsTokenLinetoArgSeqStarter()) {
    ENSURE_MATCHED(MatchLinetoArgSeq(absCoords));
  }
  else {
    if (pos != mTokenPos) RewindTo(pos);
  }
  
  return NS_OK;
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchClosePath()
{
  switch (mTokenVal) {
    case 'Z':
    case 'z':
      GetNextToken();
      break;
    default:
      return NS_ERROR_FAILURE;
  }

  return StoreClosePath();
}

//----------------------------------------------------------------------
  
nsresult nsSVGPathDataParser::MatchLineto()
{
  bool absCoords;
  
  switch (mTokenVal) {
    case 'L':
      absCoords = true;
      break;
    case 'l':
      absCoords = false;
      break;
    default:
      return NS_ERROR_FAILURE;
  }

  GetNextToken();

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  ENSURE_MATCHED(MatchLinetoArgSeq(absCoords));
  
  return NS_OK;
}

nsresult nsSVGPathDataParser::MatchLinetoArgSeq(bool absCoords)
{
  while(1) {
    float x, y;
    ENSURE_MATCHED(MatchCoordPair(&x, &y));
    
    nsresult rv = StoreLineTo(absCoords, x, y);
    NS_ENSURE_SUCCESS(rv, rv);

    const char* pos = mTokenPos;

    if (IsTokenCommaWspStarter()) {
      ENSURE_MATCHED(MatchCommaWsp());
    }

    if (!IsTokenCoordPairStarter()) {
      if (pos != mTokenPos) RewindTo(pos);
      return NS_OK;
    }
  }
  
  return NS_OK;  
}

bool nsSVGPathDataParser::IsTokenLinetoArgSeqStarter()
{
  return IsTokenCoordPairStarter();
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchHorizontalLineto()
{
  bool absCoords;
  
  switch (mTokenVal) {
    case 'H':
      absCoords = true;
      break;
    case 'h':
      absCoords = false;
      break;
    default:
      return NS_ERROR_FAILURE;
  }

  GetNextToken();

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  ENSURE_MATCHED(MatchHorizontalLinetoArgSeq(absCoords));
  
  return NS_OK;
}
  
nsresult nsSVGPathDataParser::MatchHorizontalLinetoArgSeq(bool absCoords)
{
  while(1) {
    float x;
    ENSURE_MATCHED(MatchCoord(&x));
    
    nsresult rv = StoreHLineTo(absCoords, x);
    NS_ENSURE_SUCCESS(rv, rv);

    const char* pos = mTokenPos;

    if (IsTokenCommaWspStarter()) {
      ENSURE_MATCHED(MatchCommaWsp());
    }

    if (!IsTokenCoordStarter()) {
      if (pos != mTokenPos) RewindTo(pos);
      return NS_OK;
    }
  }
  
  return NS_OK;    
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchVerticalLineto()
{
  bool absCoords;
  
  switch (mTokenVal) {
    case 'V':
      absCoords = true;
      break;
    case 'v':
      absCoords = false;
      break;
    default:
      return NS_ERROR_FAILURE;
  }

  GetNextToken();

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  ENSURE_MATCHED(MatchVerticalLinetoArgSeq(absCoords));
  
  return NS_OK;
}

nsresult nsSVGPathDataParser::MatchVerticalLinetoArgSeq(bool absCoords)
{
  while(1) {
    float y;
    ENSURE_MATCHED(MatchCoord(&y));
    
    nsresult rv = StoreVLineTo(absCoords, y);
    NS_ENSURE_SUCCESS(rv, rv);

    const char* pos = mTokenPos;

    if (IsTokenCommaWspStarter()) {
      ENSURE_MATCHED(MatchCommaWsp());
    }

    if (!IsTokenCoordStarter()) {
      if (pos != mTokenPos) RewindTo(pos);
      return NS_OK;
    }
  }
  
  return NS_OK;      
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchCurveto()
{
  bool absCoords;
  
  switch (mTokenVal) {
    case 'C':
      absCoords = true;
      break;
    case 'c':
      absCoords = false;
      break;
    default:
      return NS_ERROR_FAILURE;
  }

  GetNextToken();

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  ENSURE_MATCHED(MatchCurvetoArgSeq(absCoords));
  
  return NS_OK;
}


nsresult nsSVGPathDataParser::MatchCurvetoArgSeq(bool absCoords)
{
  while(1) {
    float x, y, x1, y1, x2, y2;
    ENSURE_MATCHED(MatchCurvetoArg(&x, &y, &x1, &y1, &x2, &y2));

    nsresult rv = StoreCurveTo(absCoords, x, y, x1, y1, x2, y2);
    NS_ENSURE_SUCCESS(rv, rv);
    
    const char* pos = mTokenPos;

    if (IsTokenCommaWspStarter()) {
      ENSURE_MATCHED(MatchCommaWsp());
    }

    if (!IsTokenCurvetoArgStarter()) {
      if (pos != mTokenPos) RewindTo(pos);
      return NS_OK;
    }
  }
  
  return NS_OK;      
}

nsresult
nsSVGPathDataParser::MatchCurvetoArg(float* x, float* y, float* x1,
                                     float* y1, float* x2, float* y2)
{
  ENSURE_MATCHED(MatchCoordPair(x1, y1));

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }

  ENSURE_MATCHED(MatchCoordPair(x2, y2));

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }

  ENSURE_MATCHED(MatchCoordPair(x, y));

  return NS_OK;
}

bool nsSVGPathDataParser::IsTokenCurvetoArgStarter()
{
  return IsTokenCoordPairStarter();
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchSmoothCurveto()
{
  bool absCoords;
  
  switch (mTokenVal) {
    case 'S':
      absCoords = true;
      break;
    case 's':
      absCoords = false;
      break;
    default:
      return NS_ERROR_FAILURE;
  }

  GetNextToken();

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  ENSURE_MATCHED(MatchSmoothCurvetoArgSeq(absCoords));
  
  return NS_OK;
}

nsresult nsSVGPathDataParser::MatchSmoothCurvetoArgSeq(bool absCoords)
{
  while(1) {
    float x, y, x2, y2;
    ENSURE_MATCHED(MatchSmoothCurvetoArg(&x, &y, &x2, &y2));
    
    nsresult rv = StoreSmoothCurveTo(absCoords, x, y, x2, y2);
    NS_ENSURE_SUCCESS(rv, rv);
    
    const char* pos = mTokenPos;

    if (IsTokenCommaWspStarter()) {
      ENSURE_MATCHED(MatchCommaWsp());
    }

    if (!IsTokenSmoothCurvetoArgStarter()) {
      if (pos != mTokenPos) RewindTo(pos);
      return NS_OK;
    }
  }
  
  return NS_OK;        
}

nsresult nsSVGPathDataParser::MatchSmoothCurvetoArg(float* x, float* y, float* x2, float* y2)
{
  ENSURE_MATCHED(MatchCoordPair(x2, y2));

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }

  ENSURE_MATCHED(MatchCoordPair(x, y));

  return NS_OK;
}

bool nsSVGPathDataParser::IsTokenSmoothCurvetoArgStarter()
{
  return IsTokenCoordPairStarter();
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchQuadBezierCurveto()
{
  bool absCoords;
  
  switch (mTokenVal) {
    case 'Q':
      absCoords = true;
      break;
    case 'q':
      absCoords = false;
      break;
    default:
      return NS_ERROR_FAILURE;
  }

  GetNextToken();

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  ENSURE_MATCHED(MatchQuadBezierCurvetoArgSeq(absCoords));
  
  return NS_OK;
}

nsresult nsSVGPathDataParser::MatchQuadBezierCurvetoArgSeq(bool absCoords)
{
  while(1) {
    float x, y, x1, y1;
    ENSURE_MATCHED(MatchQuadBezierCurvetoArg(&x, &y, &x1, &y1));

    nsresult rv = StoreQuadCurveTo(absCoords, x, y, x1, y1);
    NS_ENSURE_SUCCESS(rv, rv);
    
    const char* pos = mTokenPos;

    if (IsTokenCommaWspStarter()) {
      ENSURE_MATCHED(MatchCommaWsp());
    }

    if (!IsTokenQuadBezierCurvetoArgStarter()) {
      if (pos != mTokenPos) RewindTo(pos);
      return NS_OK;
    }
  }
  
  return NS_OK;        
}

nsresult nsSVGPathDataParser::MatchQuadBezierCurvetoArg(float* x, float* y, float* x1, float* y1)
{
  ENSURE_MATCHED(MatchCoordPair(x1, y1));

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }

  ENSURE_MATCHED(MatchCoordPair(x, y));

  return NS_OK;  
}

bool nsSVGPathDataParser::IsTokenQuadBezierCurvetoArgStarter()
{
  return IsTokenCoordPairStarter();
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchSmoothQuadBezierCurveto()
{
  bool absCoords;
  
  switch (mTokenVal) {
    case 'T':
      absCoords = true;
      break;
    case 't':
      absCoords = false;
      break;
    default:
      return NS_ERROR_FAILURE;
  }

  GetNextToken();

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  ENSURE_MATCHED(MatchSmoothQuadBezierCurvetoArgSeq(absCoords));
  
  return NS_OK;
}

nsresult nsSVGPathDataParser::MatchSmoothQuadBezierCurvetoArgSeq(bool absCoords)
{
  while(1) {
    float x, y;
    ENSURE_MATCHED(MatchCoordPair(&x, &y));
   
    nsresult rv = StoreSmoothQuadCurveTo(absCoords, x, y);
    NS_ENSURE_SUCCESS(rv, rv);

    const char* pos = mTokenPos;

    if (IsTokenCommaWspStarter()) {
      ENSURE_MATCHED(MatchCommaWsp());
    }

    if (!IsTokenCoordPairStarter()) {
      if (pos != mTokenPos) RewindTo(pos);
      return NS_OK;
    }
  }
  
  return NS_OK;        
}

//----------------------------------------------------------------------

nsresult nsSVGPathDataParser::MatchEllipticalArc()
{
  bool absCoords;
  
  switch (mTokenVal) {
    case 'A':
      absCoords = true;
      break;
    case 'a':
      absCoords = false;
      break;
    default:
      return NS_ERROR_FAILURE;
  }

  GetNextToken();

  while (IsTokenWspStarter()) {
    ENSURE_MATCHED(MatchWsp());
  }

  ENSURE_MATCHED(MatchEllipticalArcArgSeq(absCoords));
  
  return NS_OK;
}


nsresult nsSVGPathDataParser::MatchEllipticalArcArgSeq(bool absCoords)
{
  while(1) {
    float x, y, r1, r2, angle;
    bool largeArcFlag, sweepFlag;
    
    ENSURE_MATCHED(MatchEllipticalArcArg(&x, &y, &r1, &r2, &angle, &largeArcFlag, &sweepFlag));

    nsresult rv = StoreEllipticalArc(absCoords, x, y, r1, r2, angle,
                                     largeArcFlag, sweepFlag);
    NS_ENSURE_SUCCESS(rv, rv);
    
    const char* pos = mTokenPos;

    if (IsTokenCommaWspStarter()) {
      ENSURE_MATCHED(MatchCommaWsp());
    }

    if (!IsTokenEllipticalArcArgStarter()) {
      if (pos != mTokenPos) RewindTo(pos);
      return NS_OK;
    }
  }
  
  return NS_OK;        
}

nsresult nsSVGPathDataParser::MatchEllipticalArcArg(float* x, float* y,
                                                    float* r1, float* r2, float* angle,
                                                    bool* largeArcFlag, bool* sweepFlag)
{
  ENSURE_MATCHED(MatchNumber(r1));

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }

  ENSURE_MATCHED(MatchNumber(r2));

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }

  ENSURE_MATCHED(MatchNumber(angle));

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }
  
  ENSURE_MATCHED(MatchFlag(largeArcFlag));

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }

  ENSURE_MATCHED(MatchFlag(sweepFlag));

  if (IsTokenCommaWspStarter()) {
    ENSURE_MATCHED(MatchCommaWsp());
  }

  ENSURE_MATCHED(MatchCoordPair(x, y));
  
  return NS_OK;  
  
}

bool nsSVGPathDataParser::IsTokenEllipticalArcArgStarter()
{
  return IsTokenNumberStarter();
}


//-----------------------------------------------------------------------




static double
CalcVectorAngle(double ux, double uy, double vx, double vy)
{
  double ta = atan2(uy, ux);
  double tb = atan2(vy, vx);
  if (tb >= ta)
    return tb-ta;
  return 2 * M_PI - (ta-tb);
}


nsSVGArcConverter::nsSVGArcConverter(const Point& from,
                                     const Point& to,
                                     const Point& radii,
                                     double angle,
                                     bool largeArcFlag,
                                     bool sweepFlag)
{
  const double radPerDeg = M_PI/180.0;
  mSegIndex = 0;

  if (from == to) {
    mNumSegs = 0;
    return;
  }

  // Convert to center parameterization as shown in
  // http://www.w3.org/TR/SVG/implnote.html
  mRx = fabs(radii.x);
  mRy = fabs(radii.y);

  mSinPhi = sin(angle*radPerDeg);
  mCosPhi = cos(angle*radPerDeg);

  double x1dash =  mCosPhi * (from.x-to.x)/2.0 + mSinPhi * (from.y-to.y)/2.0;
  double y1dash = -mSinPhi * (from.x-to.x)/2.0 + mCosPhi * (from.y-to.y)/2.0;

  double root;
  double numerator = mRx*mRx*mRy*mRy - mRx*mRx*y1dash*y1dash -
                     mRy*mRy*x1dash*x1dash;

  if (numerator < 0.0) {
    //  If mRx , mRy and are such that there is no solution (basically,
    //  the ellipse is not big enough to reach from 'from' to 'to'
    //  then the ellipse is scaled up uniformly until there is
    //  exactly one solution (until the ellipse is just big enough).

    // -> find factor s, such that numerator' with mRx'=s*mRx and
    //    mRy'=s*mRy becomes 0 :
    double s = sqrt(1.0 - numerator/(mRx*mRx*mRy*mRy));

    mRx *= s;
    mRy *= s;
    root = 0.0;

  }
  else {
    root = (largeArcFlag == sweepFlag ? -1.0 : 1.0) *
      sqrt( numerator/(mRx*mRx*y1dash*y1dash + mRy*mRy*x1dash*x1dash) );
  }

  double cxdash = root*mRx*y1dash/mRy;
  double cydash = -root*mRy*x1dash/mRx;

  mC.x = mCosPhi * cxdash - mSinPhi * cydash + (from.x+to.x)/2.0;
  mC.y = mSinPhi * cxdash + mCosPhi * cydash + (from.y+to.y)/2.0;
  mTheta = CalcVectorAngle(1.0, 0.0, (x1dash-cxdash)/mRx, (y1dash-cydash)/mRy);
  double dtheta = CalcVectorAngle((x1dash-cxdash)/mRx, (y1dash-cydash)/mRy,
                                  (-x1dash-cxdash)/mRx, (-y1dash-cydash)/mRy);
  if (!sweepFlag && dtheta>0)
    dtheta -= 2.0*M_PI;
  else if (sweepFlag && dtheta<0)
    dtheta += 2.0*M_PI;

  // Convert into cubic bezier segments <= 90deg
  mNumSegs = static_cast<int>(ceil(fabs(dtheta/(M_PI/2.0))));
  mDelta = dtheta/mNumSegs;
  mT = 8.0/3.0 * sin(mDelta/4.0) * sin(mDelta/4.0) / sin(mDelta/2.0);

  mFrom = from;
}

bool
nsSVGArcConverter::GetNextSegment(Point* cp1, Point* cp2, Point* to)
{
  if (mSegIndex == mNumSegs) {
    return false;
  }
  
  double cosTheta1 = cos(mTheta);
  double sinTheta1 = sin(mTheta);
  double theta2 = mTheta + mDelta;
  double cosTheta2 = cos(theta2);
  double sinTheta2 = sin(theta2);

  // a) calculate endpoint of the segment:
  to->x = mCosPhi * mRx*cosTheta2 - mSinPhi * mRy*sinTheta2 + mC.x;
  to->y = mSinPhi * mRx*cosTheta2 + mCosPhi * mRy*sinTheta2 + mC.y;

  // b) calculate gradients at start/end points of segment:
  cp1->x = mFrom.x + mT * ( - mCosPhi * mRx*sinTheta1 - mSinPhi * mRy*cosTheta1);
  cp1->y = mFrom.y + mT * ( - mSinPhi * mRx*sinTheta1 + mCosPhi * mRy*cosTheta1);

  cp2->x = to->x + mT * ( mCosPhi * mRx*sinTheta2 + mSinPhi * mRy*cosTheta2);
  cp2->y = to->y + mT * ( mSinPhi * mRx*sinTheta2 - mCosPhi * mRy*cosTheta2);

  // do next segment
  mTheta = theta2;
  mFrom = *to;
  ++mSegIndex;

  return true;
}


// ---------------------------------------------------------------
// nsSVGPathDataParserToInternal

nsresult
nsSVGPathDataParserToInternal::Parse(const nsAString &aValue)
{
  mPathSegList->Clear();
  return nsSVGPathDataParser::Parse(aValue);
}

nsresult
nsSVGPathDataParserToInternal::StoreMoveTo(bool absCoords, float x, float y)
{
  return mPathSegList->AppendSeg(absCoords ? PATHSEG_MOVETO_ABS : PATHSEG_MOVETO_REL, x, y);
}

nsresult
nsSVGPathDataParserToInternal::StoreClosePath()
{
  return mPathSegList->AppendSeg(PATHSEG_CLOSEPATH);
}

nsresult
nsSVGPathDataParserToInternal::StoreLineTo(bool absCoords, float x, float y)
{
  return mPathSegList->AppendSeg(absCoords ? PATHSEG_LINETO_ABS : PATHSEG_LINETO_REL, x, y);
}

nsresult
nsSVGPathDataParserToInternal::StoreHLineTo(bool absCoords, float x)
{
  return mPathSegList->AppendSeg(absCoords ? PATHSEG_LINETO_HORIZONTAL_ABS : PATHSEG_LINETO_HORIZONTAL_REL, x);
}

nsresult
nsSVGPathDataParserToInternal::StoreVLineTo(bool absCoords, float y)
{
  return mPathSegList->AppendSeg(absCoords ? PATHSEG_LINETO_VERTICAL_ABS : PATHSEG_LINETO_VERTICAL_REL, y);
}

nsresult
nsSVGPathDataParserToInternal::StoreCurveTo(bool absCoords,
                                            float x, float y,
                                            float x1, float y1,
                                            float x2, float y2)
{
  return mPathSegList->AppendSeg(absCoords ? PATHSEG_CURVETO_CUBIC_ABS : PATHSEG_CURVETO_CUBIC_REL,
                                 x1, y1, x2, y2, x, y);
}

nsresult
nsSVGPathDataParserToInternal::StoreSmoothCurveTo(bool absCoords,
                                                  float x, float y,
                                                  float x2, float y2)
{
  return mPathSegList->AppendSeg(absCoords ? PATHSEG_CURVETO_CUBIC_SMOOTH_ABS : PATHSEG_CURVETO_CUBIC_SMOOTH_REL,
                                 x2, y2, x, y);
}

nsresult
nsSVGPathDataParserToInternal::StoreQuadCurveTo(bool absCoords,
                                                float x, float y,
                                                float x1, float y1)
{
  return mPathSegList->AppendSeg(absCoords ? PATHSEG_CURVETO_QUADRATIC_ABS : PATHSEG_CURVETO_QUADRATIC_REL,
                                 x1, y1, x, y);
}

nsresult
nsSVGPathDataParserToInternal::StoreSmoothQuadCurveTo(bool absCoords,
                                                      float x, float y)
{
  return mPathSegList->AppendSeg(absCoords ? PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS : PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, x, y);
}

nsresult
nsSVGPathDataParserToInternal::StoreEllipticalArc(bool absCoords,
                                                  float x, float y,
                                                  float r1, float r2,
                                                  float angle,
                                                  bool largeArcFlag,
                                                  bool sweepFlag)
{
  // We can only pass floats after 'type', and per the SVG spec for arc,
  // non-zero args are treated at 'true'.
  return mPathSegList->AppendSeg(absCoords ? PATHSEG_ARC_ABS : PATHSEG_ARC_REL,
                                 r1, r2, angle,
                                 largeArcFlag ? 1.0f : 0.0f,
                                 sweepFlag ? 1.0f : 0.0f,
                                 x, y);
}