
/* path_process.cpp - path methods for processing commands of the path. */

/* Requires the OpenGL Utility Toolkit (GLUT) and Cg runtime (version
   2.0 or higher). */

//#include "gs_bezier_config.h"  // configure path renderers to use

#include "sg/path.hpp"
//#include "sg/scene.hpp"

#include <Cg/iostream.hpp>

#include <Cg/abs.hpp>
#include <Cg/acos.hpp>
#include <Cg/all.hpp>
#include <Cg/any.hpp>
#include <Cg/clamp.hpp>
#include <Cg/distance.hpp>
#include <Cg/dot.hpp>
#include <Cg/isinf.hpp>
#include <Cg/isnan.hpp>
#include <Cg/length.hpp>
//#include <Cg/lerp.hpp>  // This has problems with Visual Studio 2008
#define lerp(a,b,t) ((a) + (t)*((b)-(a)))
#include <Cg/max.hpp>
#include <Cg/mul.hpp>
#include <Cg/normalize.hpp>
#include <Cg/radians.hpp>
#include <Cg/sign.hpp>
#include <Cg/sqrt.hpp>
#include <Cg/transpose.hpp>

#define _USE_MATH_DEFINES
#include <math.h>

#if !defined(OPENGL_ES) && USE_OPENGL
#include "opengl/shaders_gl.hpp"
#endif

#include "EndPointArc.hpp"

#include <GL/glew.h>

//#include "util/logprint.h"
#define LOG_PRINTN(x)  // ignore
#define LOG_PRINTFN(x)  // ignore

using namespace Cg;

// Be more robust about finding an initial tangent
// of a Bezier curve.
//
// The analytical initial tangent for a cubic curve
// is algebraically normalize(P1-P0), but if P1 equals P0, the
// tangent is degenerate.
//
// This routine avoids degenerate tangents in the presence
// of duplicate control points.
template <int numPoints, typename Tvec>
Tvec startTangent(const Tvec cp[])
{
    assert(numPoints >= 2);
    for (int i=1; i<numPoints; i++) {
        const Tvec diff = cp[i]-cp[0];
        const double len = length(double2(diff));
        if (len > 0) {
            return Tvec(diff/len);
        }
    }
    return Tvec(0);
}

// Be more robust about finding a final tangent
// of a Bezier curve.
//
// The analytical initial tangent for a cubic curve
// is algebraically normalize(Pn-Pm), but if the final
// control point Pn equals the next to final control
// point Pm, the tangent is degenerate.
//
// This routine avoids degenerate tangents in the presence
// of duplicate control points.
template <int numPoints, typename Tvec>
Tvec endTangent(const Tvec cp[])
{
    assert(numPoints >= 2);
    for (int i=numPoints-2; i>=0; i--) {
        const Tvec diff = cp[numPoints-1]-cp[i];
        const double len = length(double2(diff));
        if (len > 0) {
            return Tvec(diff/len);
        }
    }
    return Tvec(0);
}

void PathSegmentProcessor::processSegments(PathPtr path, bool implicit_close_policy)
{
    const size_t n = path->getCmdSize();
    const vector<char>& cmd = path->getCmds();
    const float* coord = &path->getCoords()[0];

    implicit_close = implicit_close_policy;

    restartPathState();

    beginPath(path);
    for (size_t i=0; i<n; i++) {
        unsigned char c = cmd[i];
        const size_t next_cmd_ndx = i+1;
        const bool next_cmd_is_spline = next_cmd_ndx<n ? commandProvidesSplineControlPoint(path, next_cmd_ndx) : false;

        // For each command, the idea is to call one-and-exactly-one
        // virtual process function.
        switch (c) {

        case GL_PRIME_SPLINE_TO_NV:
        case GL_RELATIVE_PRIME_SPLINE_TO_NV:
            {
                // Shouldn't need to be any pending spline!
                assert(last_cmd_type != LCT_SPLINE_NEEDING_END_TANGENT);
                primed_point = float2(coord[0], coord[1]);
                coord += 2;
                if (c == GL_RELATIVE_PRIME_SPLINE_TO_NV) {  // relative
                    primed_point += current;
                }
                primeSplineTo(primed_point);
                // The "prime spline-to" command is a "virtual command" that does
                // nothing except update the primed point.
                current = current;  // Current stays unchanged!
                last_cmd_type = last_cmd_type;  // Last command type stays unchanged!
            }
            break;

        case GL_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
        case GL_RELATIVE_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
            setSharedEdge();
            c ^= GL_SHARED_EDGE_NV;
            // fallthrough
        case GL_SPLINE_TO_NV:
        case GL_RELATIVE_SPLINE_TO_NV:
            {
                // Shouldn't need to be any pending spline!
                assert(last_cmd_type != LCT_SPLINE_NEEDING_END_TANGENT);
                pending_spline[1] = current;
                pending_spline[2] = float2(coord[0], coord[1]);
                if (c == GL_RELATIVE_SPLINE_TO_NV) {  // relative
                    pending_spline[2] += current;
                }
                coord += 2;
                current = pending_spline[2];

                start_usage = USE_AS_CONTROL_POINT;
                // Is there a prior command?
                if (i >= 1) {
                    const size_t prev_cmd_ndx = i-1;
                    const size_t prev_prev_cmd_ndx = i-2;
                    // Is the prior command a spline-to command?
                    if (commandSetsPrimedPoint(path, prev_cmd_ndx)) {
                        // Use last primed point.
                        pending_spline[0] = primed_point;
                    }
                    // Or is the prior prior command a spline-to priming command?
                    else if (i >= 2 && isPrimeSplineToCommand(path, prev_prev_cmd_ndx)) {
                        // Use last primed point.
                        pending_spline[0] = primed_point;
                    }
                    // Otherwise use the prior non-spline-to command's end tangent...
                    else {
                        start_usage = USE_AS_TANGENT;
                        pending_spline[0] = spline_start_tangent;
                    }
                } else {
                    // Very first command; duplicate control point.
                    pending_spline[0] = pending_spline[1];
                }

                // Is the next command a spline-to command?
                last_cmd_type = LCT_OTHER;
                if (next_cmd_is_spline) {
                    assert(next_cmd_ndx < n);
                    assert(commandProvidesSplineControlPoint(path, next_cmd_ndx));
                    // coord has already been advanced to next command's coordinate start...
                    assert(&coord[1] < &coord[path->getCoordSize()]);
                    pending_spline[3] = float2(coord[0], coord[1]);
                    if (cmd[next_cmd_ndx] & 1) {  // relative
                        // NOTE: current has already been updated.
                        pending_spline[3] += current;
                    }
                } else {
                    // Treat this spline segment as pending on the next
                    // command's start tangent which will be used as this
                    // spline's end tangent.
                    last_cmd_type = LCT_SPLINE_NEEDING_END_TANGENT;
                    // SKIPS the centripetalCatmullRomSpline call below!
                    break;
                }
                SplinePointUsage end_usage = USE_AS_CONTROL_POINT;
                centripetalCatmullRomSpline(pending_spline, start_usage, end_usage, false);
                primed_point = pending_spline[1];
                // No longer need to collect next command's start tangent.
                needs_last_end_tangent = false;
            }
            break;

        case GL_BEGIN_CLOSED_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
        case GL_RELATIVE_BEGIN_CLOSED_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
            c ^= GL_SHARED_EDGE_NV;
            setSharedEdge();
            // fallthrough
        case GL_BEGIN_CLOSED_SPLINE_TO_NV:
        case GL_RELATIVE_BEGIN_CLOSED_SPLINE_TO_NV:
            {
                // Beginning a closed spline acts like a combination of
                // GL_PRIME_SPLINE_TO_NV and GL_MOVE_TO_NV with the additional
                // behavior of setting second_closed_spline_point & end_closed_spline_point
                // and within_closed_spline to true.
                // 
                // next_cmd_is_spline=false because this command acts as a MoveTo but
                // also starts a new spline so no point in computing the closing line
                // segment tangent.
                implicitCloseHelper(false);

                // First (x,y) primes the spline and is rendering-wise the
                // last point of the spline loop (see: closed_spline_point)
                // though this point is listed first.  ("The last shall be first.")
                primed_point = float2(coord[0], coord[1]);
                // Seocond (x,y) starts the spline loop.
                float2 plist[2] = {
                    current,
                    float2(coord[2], coord[3])
                };
                coord += 4;
                assert(coord <= &coord[path->getCoordSize()]);
                if (c == GL_RELATIVE_BEGIN_CLOSED_SPLINE_TO_NV) {  // relative
                    primed_point += current;
                    // 2nd point relative to the 1st!
                    plist[1] += primed_point;
                    // NOTE: This is unlike the Bezier commands 
                    // (GL_CUBIC_CURVE_TO_NV, etc.) where those
                    // command's multiple control points are all
                    // relative to current.
                }
                moveTo(plist);
                current = plist[1];
                initial_contour_point = plist[1];
                end_closed_spline_point = primed_point;
                if (next_cmd_is_spline) {
                    assert(next_cmd_ndx < n);
                    assert(commandProvidesSplineControlPoint(path, next_cmd_ndx));
                    last_cmd_type = LCT_OTHER;
                    // coord has already been advanced to next command's coordinate start...
                    assert(&coord[1] < &coord[path->getCoordSize()]);
                    second_closed_spline_point = float2(coord[0], coord[1]);
                    if (cmd[next_cmd_ndx] & 1) {  // relative
                        // NOTE: current has already been updated.
                        second_closed_spline_point += current;
                    }
                    needs_last_end_tangent = false;
                } else {
                    last_cmd_type = LCT_SPLINE_NEEDING_END_TANGENT;
                    second_closed_spline_point = initial_contour_point;
                    needs_last_end_tangent = true;
                }
                last_end_usage = USE_AS_CONTROL_POINT;
                within_closed_spline = true;
            }
            break;

        // No relative version of GL_END_CLOSED_SPLINE_TO_NV command because
        // takes no coordinates.
        case GL_END_CLOSED_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
            c ^= GL_SHARED_EDGE_NV;
            setSharedEdge();
            // fallthrough
        case GL_END_CLOSED_SPLINE_TO_NV:
            if (within_closed_spline) {
                // If pending spline needs and end tangent, excluding
                // the the initial begin-closed-spline-to command.
                if (!needs_last_end_tangent && last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
                    // Since actually ending a closed spline, switch from
                    // needing a tangent to using the end_closed_spline_point!
                    pending_spline[3] = end_closed_spline_point;
                    const SplinePointUsage end_usage = USE_AS_CONTROL_POINT;
                    centripetalCatmullRomSpline(pending_spline, start_usage, end_usage, false);
                    primed_point = pending_spline[1];
                }
                pending_spline[0] = primed_point;
                pending_spline[1] = current;
                pending_spline[2] = end_closed_spline_point;
                pending_spline[3] = initial_contour_point;
                // Indicate GetArcLengthPathSegmentProcessor should not be saving a
                // command index for the next two centripetalCatmullRomSpline calls;
                // instead, the close with complete the path command arc length
                // acounting.
                const bool more_processing = true;
                centripetalCatmullRomSpline(pending_spline, USE_AS_CONTROL_POINT, USE_AS_CONTROL_POINT, more_processing);
                pending_spline[0] = current;
                pending_spline[1] = end_closed_spline_point;
                pending_spline[2] = initial_contour_point;
                pending_spline[3] = second_closed_spline_point;
                centripetalCatmullRomSpline(pending_spline, USE_AS_CONTROL_POINT, last_end_usage, more_processing);
                within_closed_spline = false;
                last_cmd_type = LCT_OTHER;  // Same as GL_CLOSE_PATH_NV.
                pending_spline[3] = initial_contour_point;
                close(&pending_spline[2]);
            } else {
                // Ignore command if outside a closed spline.
                degenerate();
            }
            break;


        case GL_MOVE_TO_NV:
        case GL_RELATIVE_MOVE_TO_NV:
        case 'M':
        case 'm':
            {
                implicitCloseHelper(next_cmd_is_spline);
                float2 plist[2] = {
                    current,
                    float2(coord[0], coord[1])
                };
                coord += 2;
                if (c == 'm' || c == GL_RELATIVE_MOVE_TO_NV) {  // relative
                    plist[1] += current;
                }
                moveTo(plist);
                if (next_cmd_is_spline) {
                    spline_start_tangent = float2(0,0);
                }
                last_cmd_type = LCT_OTHER;
                current = plist[1];
                initial_contour_point = plist[1];
            }
            break;
        case GL_HORIZONTAL_LINE_TO_NV:
        case GL_RELATIVE_HORIZONTAL_LINE_TO_NV:
        case 'H':
        case 'h':
            {
                float2 plist[2] = {
                    current,
                    float2(coord[0], current.y)
                };
                coord += 1;
                if (c == 'h' || c == GL_RELATIVE_HORIZONTAL_LINE_TO_NV) {  // relative
                    plist[1].x += current.x;
                }
                if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
                    const float xdiff = plist[1].x - plist[0].x;
                    const float2 start_tangent = float2(sign(xdiff),0);
                    handlePendingSpline(start_tangent);
                }
                lineTo(plist);
                if (next_cmd_is_spline) {
                    const float xdiff = plist[1].x - plist[0].x;
                    const float2 end_tangent = float2(sign(xdiff),0);
                    spline_start_tangent = end_tangent;
                }
                last_cmd_type = LCT_OTHER;
                current = plist[1];
            }
            break;
        case GL_VERTICAL_LINE_TO_NV:
        case GL_RELATIVE_VERTICAL_LINE_TO_NV:
        case 'V':
        case 'v':
            {
                float2 plist[2] = {
                    current,
                    float2(current.x, coord[0])
                };
                coord += 1;
                if (c == 'v' || c == GL_RELATIVE_VERTICAL_LINE_TO_NV) {  // relative
                    plist[1].y += current.y;
                }
                if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
                    const float ydiff = plist[1].y - plist[0].y;
                    const float2 start_tangent = float2(0,sign(ydiff));
                    handlePendingSpline(start_tangent);
                }
                lineTo(plist);
                if (next_cmd_is_spline) {
                    const float ydiff = plist[1].y - plist[0].y;
                    const float2 end_tangent = float2(0,sign(ydiff));
                    spline_start_tangent = end_tangent;
                }
                last_cmd_type = LCT_OTHER;
                current = plist[1];
            }
            break;
        case GL_LINE_TO_NV:
        case GL_RELATIVE_LINE_TO_NV:
        case 'L':
        case 'l':
            {
                float2 plist[2] = {
                    current,
                    float2(coord[0], coord[1])
                };
                coord += 2;
                if (c == 'l' || c == GL_RELATIVE_LINE_TO_NV) {  // relative
                    plist[1] += current;
                }
                if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
                    const double2 diff = double2(plist[1] - plist[0]);
                    const double len = length(diff);
                    const float2 start_tangent = (len > 0) ? float2(diff/len) : float2(0,0);
                    handlePendingSpline(start_tangent);
                }
                lineTo(plist);
                if (next_cmd_is_spline) {
                    const double2 diff = double2(plist[1] - plist[0]);
                    const double len = length(diff);
                    const float2 end_tangent = (len > 0) ? float2(diff/len) : float2(0,0);
                    spline_start_tangent = end_tangent;
                }
                last_cmd_type = LCT_OTHER;
                current = plist[1];
            }
            break;
        case GL_QUADRATIC_CURVE_TO_NV:
        case GL_RELATIVE_QUADRATIC_CURVE_TO_NV:
        case 'Q':
        case 'q':
            {
                float2 plist[3] = {
                    current,
                    float2(coord[0], coord[1]),
                    float2(coord[2], coord[3])
                };
                coord += 2*2;
                if (c == 'q' || c == GL_RELATIVE_QUADRATIC_CURVE_TO_NV) {  // relative
                    plist[1] += current;
                    plist[2] += current;
                }
                if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
                    const float2 start_tangent = startTangent<3,float2>(plist);
                    handlePendingSpline(start_tangent);
                }
                quadraticCurveTo(plist);
                if (next_cmd_is_spline) {
                    const float2 end_tangent = endTangent<3,float2>(plist);
                    spline_start_tangent = end_tangent;
                }
                last_cmd_type = LCT_QUADRATIC;
                current = plist[2];
                prior_curve_point = plist[1];
            }
            break;
        case GL_SMOOTH_QUADRATIC_CURVE_TO_NV:
        case GL_RELATIVE_SMOOTH_QUADRATIC_CURVE_TO_NV:
        case 'T':
        case 't':
            {
                float2 plist[3] = {
                    current,
                    // "The control point is assumed to be the reflection
                    // of the control point on the previous command
                    // relative to the current point. (If there is no
                    // previous command or if the previous command was
                    // not a Q, q, T or t, assume the control point is
                    // coincident with the current point.)"
                    (last_cmd_type == LCT_QUADRATIC) ?
                        2*current - prior_curve_point : current,
                    float2(coord[0], coord[1])
                };
                coord += 2*1;
                if (c == 't' || c == GL_RELATIVE_SMOOTH_QUADRATIC_CURVE_TO_NV) {  // relative
                    plist[2] += current;
                }
                if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
                    const float2 start_tangent = startTangent<3,float2>(plist);
                    handlePendingSpline(start_tangent);
                }
                quadraticCurveTo(plist);
                if (next_cmd_is_spline) {
                    const float2 end_tangent = endTangent<3,float2>(plist);
                    spline_start_tangent = end_tangent;
                }
                last_cmd_type = LCT_QUADRATIC;
                current = plist[2];
                prior_curve_point = plist[1];
            }
            break;
        case GL_CUBIC_CURVE_TO_NV:
        case GL_RELATIVE_CUBIC_CURVE_TO_NV:
        case 'C':
        case 'c':
            {
                float2 plist[4] = {
                    current,
                    float2(coord[0], coord[1]),
                    float2(coord[2], coord[3]),
                    float2(coord[4], coord[5])
                };
                coord += 2*3;
                if (c == 'c' || c == GL_RELATIVE_CUBIC_CURVE_TO_NV) {  // relative
                    plist[1] += current;
                    plist[2] += current;
                    plist[3] += current;
                }
                if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
                    const float2 start_tangent = startTangent<4,float2>(plist);
                    handlePendingSpline(start_tangent);
                }
                cubicCurveTo(plist);
                if (next_cmd_is_spline) {
                    const float2 end_tangent = endTangent<4,float2>(plist);
                    spline_start_tangent = end_tangent;
                }
                last_cmd_type = LCT_CUBIC;
                current = plist[3];
                prior_curve_point = plist[2];
            }
            break;
        case GL_SMOOTH_CUBIC_CURVE_TO_NV:
        case GL_RELATIVE_SMOOTH_CUBIC_CURVE_TO_NV:
        case 'S':
        case 's':
            {
                float2 plist[4] = {
                    current,
                    (last_cmd_type == LCT_CUBIC) ?
                        2*current - prior_curve_point : current,
                    // "The first control point is assumed to be the
                    // reflection of the second control point on the
                    // previous command relative to the current
                    // point. (If there is no previous command or if the
                    // previous command was not an C, c, S or s, assume
                    // the first control point is coincident with the
                    // current point.)"
                    float2(coord[0], coord[1]),
                    float2(coord[2], coord[3])
                };
                coord += 2*2;
                if (c == 's' || c == GL_RELATIVE_SMOOTH_CUBIC_CURVE_TO_NV) {  // relative
                    plist[2] += current;
                    plist[3] += current;
                }
                if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
                    const float2 start_tangent = startTangent<4,float2>(plist);
                    handlePendingSpline(start_tangent);
                }
                cubicCurveTo(plist);
                if (next_cmd_is_spline) {
                    const float2 end_tangent = endTangent<4,float2>(plist);
                    spline_start_tangent = end_tangent;
                }
                last_cmd_type = LCT_CUBIC;
                current = plist[3];
                prior_curve_point = plist[2];
            }
            break;
        case GL_ARC_TO_NV:
        case GL_RELATIVE_ARC_TO_NV:
        case 'A':
        case 'a':
            { // SVG-style arcs
                EndPointArc arc;

                // 0: rx -- ellipse X radius
                // 1: ry -- ellipse Y radius
                // 2: x-axis-rotation
                // 3: large-arc-flag
                // 4: sweep-flag
                // 5: x -- X of to-point
                // 6: y -- Y of to-point
                arc.p[0] = current;
                arc.radii = float2(coord[0], coord[1]);
                arc.x_axis_rotation = coord[2];
                arc.large_arc_flag = coord[3] != 0;
                arc.sweep_flag = coord[4] != 0;
                arc.p[1] = float2(coord[5], coord[6]);
                coord += 7;
                if (c == 'a' || c == GL_RELATIVE_ARC_TO_NV) {  // relative
                    arc.p[1] += current;
                }
                const CenterPointArc center_point_arc(arc);
                if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
                    const float2 start_tangent = float2(center_point_arc.tangent(center_point_arc.Theta1()));
                    handlePendingSpline(start_tangent);
                }
                arcTo(center_point_arc);
                if (next_cmd_is_spline) {
                    const float2 end_tangent = float2(center_point_arc.tangent(center_point_arc.Theta1())+center_point_arc.DeltaTheta());
                    spline_start_tangent = end_tangent;
                }
                last_cmd_type = LCT_OTHER;
                current = arc.p[1];
            }
            break;
        case GL_CONIC_CURVE_TO_NV:
        case GL_RELATIVE_CONIC_CURVE_TO_NV:
        case 'W':
        case 'w':
            {
                float2 plist[3] = {
                    current,
                    float2(coord[0], coord[1]),
                    float2(coord[2], coord[3])
                };
                float w = coord[4];
                coord += 2*2+1;  // P1, P2, and w
                if (c == 'w' || c == GL_RELATIVE_CONIC_CURVE_TO_NV) {  // relative
                    plist[1] += current;
                    plist[2] += current;
                }
                if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
                    if (w > 0) {
                        const float2 start_tangent = startTangent<3,float2>(plist);
                        handlePendingSpline(start_tangent);
                    } else {
                        const double2 diff = double2(plist[2] - plist[0]);
                        const double len = length(diff);
                        const float2 start_tangent = (len > 0) ? float2(diff/len) : float2(0,0);
                        handlePendingSpline(start_tangent);
                    }
                }
                conicCurveTo(plist, w);
                if (next_cmd_is_spline) {
                    if (w > 0) {
                        const float2 end_tangent = endTangent<3,float2>(plist);
                        spline_start_tangent = end_tangent;
                    } else {
                        const double2 diff = double2(plist[2] - plist[0]);
                        const double len = length(diff);
                        const float2 end_tangent = (len > 0) ? float2(diff/len) : float2(0,0);
                        spline_start_tangent = end_tangent;
                    }
                }
                // Behave same as a quadratic segment for smoothness
                // because Table 5.pathEquations (Path Equations) in
                // spec says that the updated prior end point (npep)
                // uses the (coord[0],coord[1]) position, same as
                // GL_QUADRATIC_CURVE_TO_NV.
                last_cmd_type = LCT_QUADRATIC;
                current = plist[2];
                prior_curve_point = plist[1];
            }
            break;
        case GL_CLOSE_PATH_NV:
        case 'Z':
        case 'z':
            closeHelper(next_cmd_is_spline);
            if (last_cmd_type != LCT_NONE) {
                // Issue 167: Should CLOSE_PATH_NV count as specifying
                // the start position (sp) for the purposes of determining
                // if the PostScript path commands CIRCULAR_CCW_ARC_TO_NV
                // or CIRCULAR_CW_ARC_TO_NV should change sp to ncp?
                //
                // RESOLVED:  No.  The PostScript semantic appears to be
                // that a CLOSE_PATH_NV does not set the "current point"
                // to valid.  This is based on inspection of Ghostscript
                // behavior.
                //
                // For this reason, the "other than CLOSE_PATH_NV"
                // phrase is placed in the paragraph describing how when
                // these PostScript arc commands change sp.
                //
                // Specifically: "The CIRCULAR_CCW_ARC_TO_NV and
                // CIRCULAR_CW_ARC_TO_NV commands conditionally change
                // sp to the command's ncp but only the sp has not been
                // specified by any prior command other than CLOSE_PATH_NV
                // in the path's command sequence since the beginning of
                // the path's command sequence or last RESTART_PATH_NV.
                // When these circular arc commands change the sp to the
                // command's ncp, it implies the initial implicit line these
                // commands generate from sp to ncp will be zero length.
                // (This behavior is to match the semantics of PostScript.)"
                last_cmd_type = LCT_OTHER;
            }
            break;

        default:
            assert(!"bad command");
            break;
        }
    }
    endPath(path);
}

// Performs explicit/implicit contour close.
void PathSegmentProcessor::closeHelper(bool next_cmd_is_spline)
{
    float2 plist[2] = {
        current,
        initial_contour_point
    };

    if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
        const double2 diff = double2(plist[1] - plist[0]);
        const double len = length(diff);
        const float2 start_tangent = (len > 0) ? float2(diff/len) : float2(0,0);
        handlePendingSpline(start_tangent);
    }
    // http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
    // "The "closepath" (Z or z) ends the current subpath and causes
    // an automatic straight line to be drawn from the current point
    // to the initial point of the current subpath."
    //
    // PDF 32000 specifies the close operator ("h") to do the same in
    // different words:  "Close the current subpath by appending a
    // straight line segment from the current point to the starting
    // point of the subpath. If the current subpath is already closed,
    // h shall donothing."
    close(plist);

    if (next_cmd_is_spline) {
        const double2 diff = double2(plist[1] - plist[0]);
        const double len = length(diff);
        const float2 end_tangent = (len > 0) ? float2(diff/len) : float2(0,0);
        spline_start_tangent = end_tangent;
    }

    // SVG: "If a "closepath" is followed immediately by any other command,
    // then the next subpath starts at the same initial point as the
    // current subpath."
    //
    // PDF 32000 specifies the close operator ("h") to do the same in
    // different words:  "This operator terminates the current subpath.
    // Appending another segment to the current path shall begin a new
    // subpath, even if the new segment begins at the endpoint reached
    // by the h operation."
    current = initial_contour_point;

    // Based on testing ghostscript, the "closepath" command is not meant
    // to begin a current path.  For this reason, last_cmd_type is NOT
    // set or modified.
    //
    // This is not relevant to SVG because the SVG path grammar requires
    // a path to begin with a moveto command.  Neither is this relevant
    // to PDF because the PDF specification says "The path construction
    // operators may be invoked in any sequence, but the first one invoked
    // must be m or re to begin a new subpath."
}

// Performs explicit/implicit contour close or handles final spline
// segment if one is pending.
inline void PathSegmentProcessor::implicitCloseHelper(bool next_cmd_is_spline)
{
    if (implicit_close) {
        closeHelper(next_cmd_is_spline);
    } else {
        if (last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT) {
            const float2 start_tangent = float2(0,0);
            handlePendingSpline(start_tangent);
        }
    }
}

void PathSegmentProcessor::beginPath(PathPtr& p)
{
    // Do nothing.
}

void PathSegmentProcessor::restart()
{
    // Do nothing.
}

void PathSegmentProcessor::splineCubicCurveTo(const float2 p[4], bool more)
{
    // Boolean more is true if the path command is expected to generate additional
    // cubic segments.  Example: GL_END_CLOSED_SPLINE_TO_NV generates two cubic
    // segments.

    // By default, ignore more flag and the fact that this cubic is generated
    // from a Catmull-Rom spline and simply treat as a cubic curve segment.
    cubicCurveTo(p);
}

void PathSegmentProcessor::endPath(PathPtr& p)
{
    // Do nothing.
}

// Default virtual function simply expands the lineToArc to into a lineTo
// and arcTo operations.
void PathSegmentProcessor::lineToArcTo(const float2 p[2],
                                       const CenterPointArc &arc)
{
    lineTo(p);
    arcTo(arc);
}

// Default virtual function simply expands four lineTo operations for
// the edges of the rect.
void PathSegmentProcessor::rect(float x, float y,
                                float width, float height)
{
    float2 plist[6] = {
        current,
        float2(x,y),
        float2(x+width,y),
        float2(x+width,y+height),
        float2(x,y+height),
        float2(x,y)
    };
    initial_contour_point = float2(x,y);

    // "x y m" (move)
    // "(x+width) y l" (horizontal line)
    // "(x+width) (y+height) l" (vertical line)
    // "x (y+height) l" (horizontal line)
    // "h" (close)
    moveTo(plist);
    lineTo(&plist[1]);
    lineTo(&plist[2]);
    lineTo(&plist[3]);
    close(&plist[4]);

    // Returning from this function, we expect current to be updated.
}

// Default virtual function simply expands rounded rectangle into four
// lineTo operations and four rounded corners.
void PathSegmentProcessor::rrect(float x, float y,
                                 float w, float h,
                                 const float radius[8])

{
    // Figure 5.roundedRectangle:  Geometric interpretation of Equation
    // 5.roundedRectangleContour parameters.
    //
    //               _ r_ulx                       _ r_urx
    //              / \                           / \                  .
    //             /   \C_ul                A_ur /   \                 .
    //       B_ul .-----.-----------------------.-----. B_ur     \     .
    //           /|                                   |\         |
    //    r_uly < |                                   | > r_ury  |
    //           \|                                   |/         |
    //       A_ul .                                   . C_ur     |
    //            |                                   |          \     .
    //            |                                   |           > h
    //            |                                   |          /
    //            |                                   |          |
    //       A_ll .                                   . C_rl     |
    //           /|                                   |\         |
    //    r_lly < |                                   | > r_rly  |
    //           \|                                   |/         |
    //       B_ll .-----.-----------------------.-----. B_rl     /
    //     & (x,y) \   /C_ll                A_rl \   /
    //              \_/                           \_/
    //                r_llx                         r_rlx
    //             \______________  __________________/
    //                            \/
    //                             w
    const float &r_llx = radius[0],
                &r_lly = radius[1],
                &r_lrx = radius[2],
                &r_lry = radius[3], 
                &r_urx = radius[4],
                &r_ury = radius[5], 
                &r_ulx = radius[6],
                &r_uly = radius[7];

    const float2 plist[12] = {
        current,

        float2(x+r_llx,   y),    // C_ll
        float2(x+w-r_lrx, y),    // A_rl

        float2(x+w, y+r_lry),    // C_rl
        float2(x+w, y+h-r_ury),  // C_ur

        float2(x+w-r_urx, y+h),  // A_ur
        float2(x+r_ulx,   y+h),  // C_ul

        float2(x, y+h-r_uly),    // A_ul
        float2(x, y+r_lly),      // A_ll

        float2(x+r_llx,   y),    // C_ll
        float2(x+r_llx,   y),    // C_ll
        float2(x,y)              // (x,y)
    };
    // Bottom horizontal side
    moveTo(plist);
    lineTo(&plist[1]);
    // Lower-right corner
    CenterPointArc cp_arc;
    cp_arc.center = float2(x+w-r_lrx,y+r_lry);
    cp_arc.radii = float2(r_lrx, r_lry);
    if (cp_arc.Radii().x == 0 || cp_arc.Radii().y == 0) {
        if (cp_arc.Radii().x == 0 && cp_arc.Radii().y == 0) {
            cp_arc.form = CenterPointArc::DEGENERATE_POINT;
        } else {
            cp_arc.form = CenterPointArc::DEGENERATE_LINE;
        }
    } else {
        cp_arc.form = CenterPointArc::BEHAVED;
    }
    cp_arc.p[0] = plist[2];
    cp_arc.p[1] = plist[3];
    cp_arc.theta1 = float((3*M_PI)/2);
    cp_arc.delta_theta = float(M_PI/2);
    cp_arc.psi = 0;
    arcTo(cp_arc);
    // Right vertical side
    lineTo(&plist[3]);
    // Upper-right corner
    cp_arc.center = float2(x+w-r_urx,y+h-r_ury);
    cp_arc.radii = float2(r_urx, r_ury);
    if (cp_arc.radii.x == 0 || cp_arc.radii.y == 0) {
        if (cp_arc.radii.x == 0 && cp_arc.radii.y == 0) {
            cp_arc.form = CenterPointArc::DEGENERATE_POINT;
        } else {
            cp_arc.form = CenterPointArc::DEGENERATE_LINE;
        }
    } else {
        cp_arc.form = CenterPointArc::BEHAVED;
    }
    cp_arc.p[0] = plist[4];
    cp_arc.p[1] = plist[5];
    cp_arc.theta1 = 0;
    arcTo(cp_arc);
    // Horizontal top side
    lineTo(&plist[5]);
    // Upper-left corner
    cp_arc.center = float2(x+r_ulx,y+h-r_uly);
    cp_arc.radii = float2(r_ulx, r_uly);
    if (cp_arc.radii.x == 0 || cp_arc.radii.y == 0) {
        if (cp_arc.radii.x == 0 && cp_arc.radii.y == 0) {
            cp_arc.form = CenterPointArc::DEGENERATE_POINT;
        } else {
            cp_arc.form = CenterPointArc::DEGENERATE_LINE;
        }
    } else {
        cp_arc.form = CenterPointArc::BEHAVED;
    }
    cp_arc.p[0] = plist[6];
    cp_arc.p[1] = plist[7];
    cp_arc.theta1 = float(M_PI/2);
    arcTo(cp_arc);
    // Vertical left side
    lineTo(&plist[7]);
    // Lower-right corner
    cp_arc.center = float2(x+r_llx,y+r_lly);
    cp_arc.radii = float2(r_llx, r_lly);
    if (cp_arc.radii.x == 0 || cp_arc.radii.y == 0) {
        if (cp_arc.radii.x == 0 && cp_arc.radii.y == 0) {
            cp_arc.form = CenterPointArc::DEGENERATE_POINT;
        } else {
            cp_arc.form = CenterPointArc::DEGENERATE_LINE;
        }
    } else {
        cp_arc.form = CenterPointArc::BEHAVED;
    }
    cp_arc.p[0] = plist[8];
    cp_arc.p[1] = plist[1];
    cp_arc.theta1 = float((2*M_PI)/2);
    arcTo(cp_arc);
    // Back at the beginning so close the path.
    close(&plist[9]);
    // Move to the (x,y) location (not a point on the rounded rectangle!).
    moveTo(&plist[10]);
    // Returning from this function, we expect current to be updated.
}

void PathSegmentProcessor::degenerate()
{
    // Do nothing.
    //
    // Certain processors (for example GetArcLengthPathSegmentProcessor)
    // need to know about empty commands for accounting purposes.
}

inline void PathSegmentProcessor::restartPathState()
{
    last_cmd_type = LCT_NONE;
    current = float2(0,0);
    initial_contour_point = float2(0,0);
    // Reset spline state
    start_usage = USE_AS_CONTROL_POINT;
    within_closed_spline = false;
    needs_last_end_tangent = false;
}

bool PathSegmentProcessor::commandProvidesSplineControlPoint(const PathPtr path, size_t ndx)
{
    assert(ndx < size_t(path->getCmdSize()));

    const unsigned char cmd = path->getCmds()[ndx];
    switch (cmd) {
    // Virtual prime spline-to commands...
    case GL_PRIME_SPLINE_TO_NV:
    case GL_RELATIVE_PRIME_SPLINE_TO_NV:
    // Unshared versions of spline-to commands...
    case GL_SPLINE_TO_NV:
    case GL_RELATIVE_SPLINE_TO_NV:
    // Shared versions of spline-to commands...
    case GL_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
    case GL_RELATIVE_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
        return true;

    // All other commands are NOT (including CLOSED_SPLINE_TO commands).
    default:
        return false;
    }
}

// Is this command of path one that will set primed_point?
bool PathSegmentProcessor::commandSetsPrimedPoint(const PathPtr path, size_t ndx)
{
    assert(ndx < size_t(path->getCmdSize()));

    const unsigned char cmd = path->getCmds()[ndx];
    switch (cmd) {
    // Virtual prime spline-to commands...
    case GL_PRIME_SPLINE_TO_NV:
    case GL_RELATIVE_PRIME_SPLINE_TO_NV:
    // Unshared versions of spline-to commands...
    case GL_SPLINE_TO_NV:
    case GL_RELATIVE_SPLINE_TO_NV:
    case GL_BEGIN_CLOSED_SPLINE_TO_NV:
    case GL_RELATIVE_BEGIN_CLOSED_SPLINE_TO_NV:
    // Shared versions of spline-to commands...
    case GL_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
    case GL_RELATIVE_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
    case GL_BEGIN_CLOSED_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
    case GL_RELATIVE_BEGIN_CLOSED_SPLINE_TO_NV ^ GL_SHARED_EDGE_NV:
        return true;

    // All other commands are NOT (including END_CLOSED_SPLINE_TO commands).
    default:
        return false;
    }
}

// True if next command is a GL_*PRIME_SPLINE_TO_NV command; false otherwise.
bool PathSegmentProcessor::isPrimeSplineToCommand(const PathPtr path, size_t ndx)
{
    assert(ndx < size_t(path->getCmdSize()));

    switch (path->getCmds()[ndx]) {
    case GL_PRIME_SPLINE_TO_NV:
    case GL_RELATIVE_PRIME_SPLINE_TO_NV:
        return true;
    default:
        return false;
    }
}

// Given 4 centripetal Catmull-Rom control points, convert to cubic Bezier
// control points and call cubicCurveTo.
//
// start_usage and end_usage indicates if start (first) and end (last) control
// point in catmullrom_point should be treated as either a control point
// (USE_CONTROL_POINT) or a tangent (USE_TANGENT).
//
// Boolean more is true if the path command is expected to generate additional
// cubic segments.  Example: GL_END_CLOSED_SPLINE_TO_NV generates two cubic
// segments.
//
// Based on math developed in the paper "On the Parameterization of Catmull-Rom Curves"
// by Cem Yuksel, Scott Schaefer, and John Keyser, presented at
// 2009 SIAM/ACM Joint Conference on Geometric and Physical Modeling.
// http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
// http://www.cemyuksel.com/research/catmullrom_param/catmullrom_slides.pdf
//
void PathSegmentProcessor::centripetalCatmullRomSpline(const float2 catmullrom_point[4],
                                                       SplinePointUsage start_usage,
                                                       SplinePointUsage end_usage,
                                                       bool more)
{
    // "Let P0,P1,P2,P3 be four consecutive control points of the Catmull-Rom curve...
    const float2 &P0 = catmullrom_point[0],
                 &start_tangent = catmullrom_point[0],  // when start_usage is USE_TANGENT
                 &P1 = catmullrom_point[1],
                 &P2 = catmullrom_point[2],
                 &P3 = catmullrom_point[3],
                 &end_tangent = catmullrom_point[3];  // when end_usage is USE_TANGENT
    // ...with parameter values 0, d1^a, d2^a+d1^a, d3^a+d2^a+d1^a, where di = |P[i]-P[i-1]|"
    // (see Figure 4 in paper)...
    const double d2 = distance(P1, P2);
    if (d2 <= 0) {
        // Degenerate segment; skip generating geometry.
      degenerate();
    } else {
        const double d1 = distance(P0, P1),
                     d3 = distance(P2, P3);
        // "a = 1/2 corresponds to centripetal parameterization."
        // d1_2a, etc. is really d1^(2*a) where a=0.5; in other words, d1^1 or simply d1
        const double d1_2a = d1,
                     d2_2a = d2,
                     d3_2a = d3;
        // d1_a, etc. is really d1^a where a=0.5; in other words, sqrt(d1)
        const double d1_a = sqrt(d1),
                     d2_a = sqrt(d2),
                     d3_a = sqrt(d3);
        // Implement Equation 2 in "On the Parameterization of Catmull-Rom Curves".
        // ADDITIONALLY: substitute P1 for B1 if d1_a is zero and P2 for B2 if d3_a is zero.
        const float one_third = 1/3.0f;
        const bool use_start_tangent = start_usage==USE_AS_TANGENT;
        const bool use_end_tangent = end_usage==USE_AS_TANGENT;
        const float2 B0 = P1,
                     B1 = use_start_tangent ? (P1 + start_tangent * one_third*dot(start_tangent, P2-P1))
                                            : d1_a>0 ? float2((d1_2a*P2 - d2_2a*P0+(2*d1_2a+3*d1_a*d2_a+d2_2a)*P1)
                                                              / (3*d1_a*(d1_a+d2_a))) 
                                                     : P1,
                     B2 = use_end_tangent ? (P2 + end_tangent * one_third*dot(end_tangent, P1-P2))
                                          : d3_a>0 ? float2((d3_2a*P1 - d2_2a*P3+(2*d3_2a+3*d3_a*d2_a+d2_2a)*P2)
                                                            / (3*d3_a*(d3_a+d2_a)))
                                                   : P2,
                     B3 = P2;
        const float2 bezier_point[4] = { B0, B1, B2, B3 };
        splineCubicCurveTo(bezier_point, more);
    }
}

void PathSegmentProcessor::handlePendingSpline(const float2 &start_tangent)
{
    assert(last_cmd_type == LCT_SPLINE_NEEDING_END_TANGENT);
    if (needs_last_end_tangent) {
        assert(within_closed_spline);
        // Collect a start_tangent for the first-non-spline command after GL_BEGIN_CLOSED_SPLINE_TO_NV.
        second_closed_spline_point = start_tangent;
        last_end_usage = USE_AS_TANGENT;
        needs_last_end_tangent = false;  // Just collect one!
    } else {
        pending_spline[3] = start_tangent;
        const SplinePointUsage end_usage = USE_AS_TANGENT;
        centripetalCatmullRomSpline(pending_spline, start_usage, end_usage, false);
    }
}
