/******************************Module*Header*******************************\
* Module Name: pathwide.hxx
*
* Path widening defines.
*
* CLASS HIERARCHY
*
* In constructing the wide-line, we keep track of 4 paths:
*
*   1) The original path.
*   2) The path holding the vertices of the polygonized pen.
*   3) The 'left' side of the wide-line outline.
*   4) The 'right' side of the wide-line outline.
*
* To keep track of the latter 3 paths, we use:
*
*   PATHMEMOBJ      // Regular path
*       |
*       v
*   WIDEPATHOBJ     // Path object to keep track of one side of the
*       |           // wide-line.  Adds methods for easily adding
*       |           // one point at a time.
*       v
*   WIDEPENOBJ      // Path object to keep the pen shape.  A pen is
*                   // composed of an arbitrary number of points,
*                   // like a path, so we make it a path.  Plus we
*                   // add some pen functionality.
*
* It was useful to compartmentalize the task of the widener into separate
* base classes:
*
*   READER          // Reads the original path a point at a time
*     |
*     v
*   LINER           // Reads the path a line at a time (flattening
*     |             // Beziers and generating close-figure lines as
*     |             // it goes).
*     v
*   STYLER          // Handles styling by hacking the lines into
*     |             // itty bitty pieces.
*     v
*   WIDENER         // Widens the path by generating the points at
*                   // every line-join and end-cap.
*
* Created: 9-Oct-1991
* Author: J. Andrew Goossen [andrewgo]
*
* Copyright (c) 1991-1999 Microsoft Corporation
\**************************************************************************/

 #if DBG
// #define DEBUG_WIDE
#endif

class WIDENER;

#define SQUARE(x) ((x) * (x))
#define LPLUSHALFTOFX(x) (LTOFX(x) + (LTOFX(1) >> 1))

/***********************************Struct*********************************\
* struct HOBBY
*
* Structure used for containing half a Hobby pen.
*
* History:
*  9-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

typedef struct _HOBBY
{
    PPOINTFIX pptfx;
    COUNT     cptfx;
} HOBBY, *PHOBBY;     /* hob, phob */

/*********************************class************************************\
* class EVECTORFLEXT
*
* Can use VECTORFX's.
*
* History:
*  22-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

class EVECTORFLEXT : public EVECTORFL      /* evecfl */
{
public:
    EVECTORFLEXT(VECTORFX& vec)
    {
        x.vFxToEf(vec.x);
        y.vFxToEf(vec.y);
    }
    BOOL bToVECTORFX(VECTORFX& vec)
    {
        return(x.bEfToFx(vec.x) && y.bEfToFx(vec.y));
    }
};

/*********************************class************************************\
* class LINEDATA
*
* Keeps track of useful stuff about the current line in the original
* path that we're processing.
*
* History:
*  22-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

// LINEDATA flags:

#define LDF_INVERT             0x0001L
#define LDF_VECSQUARECOMPUTED  0x0002L
#define LDF_VECPERPCOMPUTED    0x0004L
#define LDF_VECDRAWCOMPUTED    0x0008L
#define LDF_NORMALIZEDCOMPUTED 0x0010L

class LINEDATA      /* ld */
{
public:
    FLONG       fl;             // LINEDATA flags, as above

    PPATHREC    ppr;            // Pathrecord containing point
    PPOINTFIX   pptfx;          // *pptfx == -ptfx if bInvert is TRUE
    LONGLONG    fxCrossStart;
    LONGLONG    fxCrossEnd;

    EVECTORFX   vecLine;        // Actual line vector
    EVECTORFX   vecTangent;     // True tangent to current point.  Direction
                                // only -- to be used for calculating perps

// Cached values:

    EVECTORFX   vecSquare;      // Vector to offset to square cap start
    EVECTORFX   vecPerp;        // Perpendicular vector to line
    EVECTORFX   vecDraw;        // Drawing vector for line
    POINTFL     ptflNormalized; // Normalized version of line vector

    BOOL        bInvert()       { return(fl & LDF_INVERT); }
    VOID        vSetInvert()    { fl |= LDF_INVERT; }
    VOID        vClearInvert()  { fl &= ~LDF_INVERT; }

    VOID        vSetVecSquareComputed()  { fl |= LDF_VECSQUARECOMPUTED; }
    VOID        vSetVecPerpComputed()    { fl |= LDF_VECPERPCOMPUTED; }
    VOID        vSetVecDrawComputed()    { fl |= LDF_VECDRAWCOMPUTED; }
    VOID        vSetNormalizedComputed() { fl |= LDF_NORMALIZEDCOMPUTED; }

    BOOL        bVecSquareComputed()    { return(fl & LDF_VECSQUARECOMPUTED); }
    BOOL        bVecPerpComputed()      { return(fl & LDF_VECPERPCOMPUTED); }
    BOOL        bVecDrawComputed()      { return(fl & LDF_VECDRAWCOMPUTED); }
    BOOL        bNormalizedComputed()   { return(fl & LDF_NORMALIZEDCOMPUTED); }

    VOID        vInit()     { fl = 0; }

    VOID        vInit(POINTFIX& ptfxEnd, POINTFIX& ptfxStart)
                            { fl = 0;
                              vecLine  = ptfxEnd;
                              vecLine -= ptfxStart;
                              vecTangent = vecLine; }

// bToLeftSide() returns true if the perpendicular for the line
// falls to the left of the draw vector:

    BOOL        bToLeftSide() { return(fxCrossStart > fxCrossEnd); }

// bSamePenSection() returns true if the two lines have perpendicular
// vectors in the same area:

    BOOL        bSamePenSection(LINEDATA& ld)
    {
        return((pptfx == ld.pptfx) && (bToLeftSide() == ld.bToLeftSide()));
    }
};

typedef LINEDATA *PLINEDATA;

/*********************************Class************************************\
* class WIDEPATHOBJ
*
* History:
*  12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

class WIDEPATHOBJ : public PATHMEMOBJ   /* wpath */
{
private:
    #ifdef DEBUG_WIDE
    BOOL bOpenPath;
    #endif

protected:
    BOOL bOutOfMemory;

    PPOINTFIX pptfxPathRecCurrent;
    PPOINTFIX pptfxPathRecEnd;
    PPATHREC  pprFigureStart;

    BOOL bGrowPath();
    VOID vGrowPathAndAddPoint(
            PPOINTFIX, PEVECTORFX = (PEVECTORFX) NULL, BOOL = FALSE);

public:
    WIDEPATHOBJ()
    {
        bOutOfMemory = FALSE;

        #ifdef DEBUG_WIDE
        bOpenPath = FALSE;
        #endif
    }

   ~WIDEPATHOBJ() {}

    VOID vAddPoint(PPOINTFIX pptfx, BOOL bAddRecordIfNeeded = TRUE)
    {
        #ifdef DEBUG_WIDE
        ASSERTGDI(bOpenPath, "vAddPoint path not open!");
        #endif

        if (pptfxPathRecCurrent >= pptfxPathRecEnd)
        {
            #ifdef DEBUG_WIDE
            ASSERTGDI(pptfxPathRecCurrent == pptfxPathRecEnd,
                      "vAddPoint out of bounds!");
            #endif

            if (bAddRecordIfNeeded)
                vGrowPathAndAddPoint(pptfx);
        }
        else
        {
            #ifdef DEBUG_WIDE
            ASSERTGDI((PBYTE) pptfxPathRecCurrent - (PBYTE) ppath->ppachain
            < (LONG) ppath->ppachain->siztPathAlloc, "vAddPoint out of bounds.");
            #endif

            *pptfxPathRecCurrent++ = *pptfx;
        }
    }

    VOID vAddPoint(PPOINTFIX pptfx, PEVECTORFX pvec, BOOL bInvert)
    {
        #ifdef DEBUG_WIDE
        ASSERTGDI(bOpenPath, "vAddPoint2 path not open!");
        #endif

        if (pptfxPathRecCurrent >= pptfxPathRecEnd)
        {
            #ifdef DEBUG_WIDE
            ASSERTGDI(pptfxPathRecCurrent == pptfxPathRecEnd,
                      "vAddPoint2 out of bounds!");
            #endif

            vGrowPathAndAddPoint(pptfx, pvec, bInvert);
        }
        else
        {
            #ifdef DEBUG_WIDE
            ASSERTGDI((PBYTE) pptfxPathRecCurrent - (PBYTE) ppath->ppachain
            < (LONG) ppath->ppachain->siztPathAlloc, "vAddPoint2 out of bounds.");
            #endif

        // 'bInvert' will usually be constant, and so one of these code
        // paths will optimize away:

            if (bInvert)
            {
                pptfxPathRecCurrent->x = pptfx->x - pvec->x;
                pptfxPathRecCurrent->y = pptfx->y - pvec->y;
            }
            else
            {
                pptfxPathRecCurrent->x = pptfx->x + pvec->x;
                pptfxPathRecCurrent->y = pptfx->y + pvec->y;
            }

            pptfxPathRecCurrent++;
        }
    }

    VOID vReverseConcatenate(WIDEPATHOBJ&);
    VOID vPrependBeforeFigure();
    VOID vPrependBeforeSubpath();

    VOID vMarkFigureStart() { pprFigureStart = ppath->pprlast; }

    BOOL bBeginFigure();
    VOID vEndFigure();
    VOID vCloseFigure()     { ppath->pprlast->flags |= PD_CLOSEFIGURE; }

    BOOL bValid()           { return(PATHMEMOBJ::bValid() && !bOutOfMemory); }
    VOID vSetError()        { bOutOfMemory = TRUE; }
};

/******************************Public*Routine******************************\
* WIDEPATHOBJ::vAddPoint(pptfx, pvec, bInvert)
*
* Adds a single point to the path.  The added point is the given point
* plus the specified vector (minus the vector if bInvert is TRUE).
*
* History:
*  1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

/*********************************Class************************************\
* class WIDEPENOBJ
*
* History:
*  12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

class WIDEPENOBJ : public WIDEPATHOBJ   /* wpen */
{
private:

// Pre-computed look-up tables containing points of circular Hobby pens
// for widths 1 through 6:

    #define HOBBY_TABLE_SIZE 6L

    static POINTFIX aptfxHobby1[4];
    static POINTFIX aptfxHobby2[5];
    static POINTFIX aptfxHobby3[6];
    static POINTFIX aptfxHobby4[8];
    static POINTFIX aptfxHobby5[10];
    static POINTFIX aptfxHobby6[10];

    static HOBBY ahob[HOBBY_TABLE_SIZE];

    BOOL bHobby;                        // TRUE if this is a Hobby pen
    BOOL bThicken(PPOINTFIX pptfx);  // Thickens pen if need-be
    BOOL bHobbyize(EVECTORFX avec[]);// Makes a Hobby pen if need-be
    BOOL bPenFlatten(PPOINTFIX);     // Converts Beziers to lines
    VOID vAddPenPoint(PPOINTFIX);    // Adds a point to the end of our path

public:
    WIDEPENOBJ()          { bHobby = FALSE; }

    BOOL bIsHobby()       { return(bHobby); }
    BOOL bIsHobby(BOOL b) { return(bHobby = b); }

    BOOL bPolygonizePen(EXFORMOBJ&, LONG);
    VOID vDetermineDrawVertex(EVECTORFX&, LINEDATA&);
    COUNT cptAddRound(WIDENER&, LINEDATA&, LINEDATA&,
                      BOOL, BOOL, BOOL);
    VOID vAddRoundEndCap(WIDENER&, LINEDATA&, BOOL, BOOL);
};


/*********************************class************************************\
* class READER
*
* Reads the path.
*
* History:
*  22-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

// Path widening flags:

#define FW_MORETOENUM       0x00000001L
#define FW_DOINGSTYLES      0x00000002L
#define FW_STYLENEXT        0x00000004L
#define FW_FIGURESTYLED     0x00000008L
#define FW_ALLROUND         0x00000010L

class READER
{
private:
    EPATHOBJ*   pepoSpine;
    PATHDATA    pd;

    VOID        vMoreToEnum(BOOL b)     { vSetFlag(b, FW_MORETOENUM); }
    BOOL        bMoreToEnum()           { return(fl & FW_MORETOENUM); }

    PPOINTFIX   pptfxRead;
    PPOINTFIX   pptfxEnd;

protected:
    FLONG       fl;                     // For widening flags, as above

    VOID vSetFlag(BOOL b, FLONG flBit)
    {
        if (b)
            fl |= flBit;
        else
            fl &= ~flBit;
    }

    READER(EPATHOBJ& epo)            { pepoSpine = &epo;
                                          pepoSpine->vEnumStart();
                                          vMoreToEnum(TRUE); }

    BOOL        bIsBezier()             { return(pd.flags & PD_BEZIERS); }

    BOOL        bNextFigure();
    BOOL        bNextPoint(POINTFIX& ptfx);

// Only valid when !bNextPoint():

    BOOL        bIsClosedFigure()       { return(pd.flags & PD_CLOSEFIGURE); }
};


/*********************************class************************************\
* class LINER
*
* Cracks Beziers and handles CloseFigures.
*
* History:
*  22-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

enum WIDENEVENT { WE_STARTFIGURE,       // Figure starts at 'ptfxThis'
                  WE_ENDFIGURE,         // Figure ends at 'ptfxThis'
                  WE_CLOSEFIGURE,       // Do a close figure join at 'ptfxThis'
                  WE_JOIN,              // Do a join at 'ptfxThis'
                  WE_BEZIERJOIN,        // Do a Bezier join at 'ptfxThis'
                  WE_STOPDASH,          // Stop a styling dash at 'ptfxThis'
                  WE_STARTDASH,         // Start a styling dash at 'ptfxThis'
                  WE_ZEROFIGURE,        // Figure has zero length at 'ptfxThis'
                  WE_FINISHFIGURE,      // Do start-cap of figure at 'ptfxThis'
                  WE_DONEPATH };

enum LINESTATE { LS_READPATH,           // Next point in figure comes from path
                 LS_STARTFIGURE,        // Next point in path is the start of
                                        // a figure
                 LS_FINISHFIGURE,       // Go back to first point in figure
                 LS_READBEZIER,         // Next point comes from current Bezier
                 LS_DONEPATH };

class LINER : public READER
{
private:
    BEZIER      bez;

    BOOL        bReadLine(LINEDATA& ld);

    POINTFIX    ptfxNext;        // Next point in path after 'ptfxThis' (see below)
    POINTFIX    ptfxStartFigure; // Keep around for later

    LINEDATA    ldStartFigure;   // Start point for current figure
    LINEDATA    ldBuf[2];        // Line vector data buffer for current lines

    LINESTATE   ls;              // Reading path state

    VOID vNextPoint();
    VOID vZeroFigure();          // Sets up for a zero-length figure

protected:
    LINEDATA    ldEndTangent;    // Line vector data for last line in figure
    LINEDATA    ldStartTangent;  // Line vector data for first line in figure

public:

    LINER(EPATHOBJ& epo) : READER(epo)
    {
        if (!bNextFigure())
            ls = LS_DONEPATH;
        else
        {
            bNextPoint(ptfxNext);
            ptfxStartFigure = ptfxNext;
            ls = LS_STARTFIGURE;
        }
    }

    VOID vNextEvent();

// Keeps track of all the info needed to add a join, start-cap or end-cap:

    WIDENEVENT  we;             // Flag to indicate start-cap, end-cap, etc.
    POINTFIX    ptfxThis;       // Point at which to add cap or join
    PLINEDATA   pldIn;          // Vector of line entering 'ptfxThis' (used for
                                // end-caps and joins)
    PLINEDATA   pldOut;         // Vector of line exiting 'ptfxThis' (used for
                                // start-caps and joins)
};


/*********************************class************************************\
* class STYLER
*
* Hacks up the path into itty bitty pieces to feed to the widener.
*
* History:
*  22-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

class STYLER : public LINER
{
private:
    VOID vStyleNext(BOOL b)   { vSetFlag(b, FW_STYLENEXT); }
    VOID vDoingStyles(BOOL b) { vSetFlag(b, FW_DOINGSTYLES); }

    BOOL bStyleNext()         { return(fl & FW_STYLENEXT); }
    BOOL bDoingStyles()       { return(fl & FW_DOINGSTYLES); }

    PFLOAT_LONG  pstyleStart;
    PFLOAT_LONG  pstyleCurrent;
    PFLOAT_LONG  pstyleEnd;

    EFLOAT   efRemainingLength;// Length remaining in current line
    EFLOAT   efStyleLength;    // Length of current dash or gap
    EFLOAT   efDoneLength;     // Length of current line done
    EFLOAT   efLineLength;     // efLineLength = efDoneLength + efRemainingLength
    POINTFIX ptfxLineStart;    // Start point of line

protected:
    EFLOAT  efWorldLength(EVECTORFX);
    EFLOAT  efNextStyleLength();
    VOID    vResetStyles()          { pstyleCurrent = pstyleStart; }

// These two actually get initialized by the derived class because it's
// a bit of work, and it will know if we'll need them:

    MATRIX       mxDeviceToWorld;
    EXFORMOBJ    exoDeviceToWorld;

public:
    STYLER(EPATHOBJ&, PLINEATTRS);

    VOID    vNextStyleEvent();
};

// Prototype used in WIDENER:

VOID vAddNice(WIDEPATHOBJ&, PPOINTFIX, PEVECTORFX, BOOL);

/*********************************class************************************\
* class WIDENER
*
* Widens the path.
*
* History:
*  22-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

class WIDENER : public STYLER
{
friend VOID WIDEPENOBJ::vAddRoundEndCap(WIDENER&, LINEDATA&, BOOL, BOOL);
friend COUNT WIDEPENOBJ::cptAddRound(WIDENER&, LINEDATA&, LINEDATA&,
                                      BOOL, BOOL, BOOL);

private:
    WIDEPENOBJ      wpen;

    WIDEPATHOBJ     wpathLeft;
    WIDEPATHOBJ     wpathRight;

    ULONG           iJoin;
    ULONG           iEndCap;

    VOID    vFigureStyled(BOOL b) { vSetFlag(b, FW_FIGURESTYLED); }
    VOID    vAllRound(BOOL b)     { vSetFlag(b, FW_ALLROUND); }

    BOOL    bFigureStyled()       { return(fl & FW_FIGURESTYLED); }
    BOOL    bAllRound()           { return(fl & FW_ALLROUND); }

    EFLOAT  efHalfWidthMiterLimitSquared;
    EFLOAT  efHalfWidth;

    VOID vVecPerpCompute(LINEDATA&);
    VOID vVecSquareCompute(LINEDATA&);
    VOID vVecDrawCompute(LINEDATA&);

protected:

    BOOL bMiterInLimit(EVECTORFX);

    EVECTORFX vecInDraw()       { if (!pldIn->bVecDrawComputed())
                                     vVecDrawCompute(*pldIn);

                                 return(pldIn->vecDraw); }

    EVECTORFX vecInPerp()       { if (!pldIn->bVecPerpComputed())
                                     vVecPerpCompute(*pldIn);

                                 return(pldIn->vecPerp); }

    EVECTORFX vecInSquare()     { if (!pldIn->bVecSquareComputed())
                                     vVecSquareCompute(*pldIn);

                                 return(pldIn->vecSquare); }

    EVECTORFX vecOutDraw()      { if (!pldOut->bVecDrawComputed())
                                     vVecDrawCompute(*pldOut);

                                 return(pldOut->vecDraw); }

    EVECTORFX vecOutPerp()      { if (!pldOut->bVecPerpComputed())
                                     vVecPerpCompute(*pldOut);

                                 return(pldOut->vecPerp); }

    EVECTORFX vecOutSquare()    { if (!pldOut->bVecSquareComputed())
                                     vVecSquareCompute(*pldOut);

                                 return(pldOut->vecSquare); }

    VOID vAddLeft(EVECTORFX& vec, BOOL bInvert = FALSE)
                        { wpathLeft.vAddPoint(&ptfxThis, &vec, !bInvert); }

    VOID vAddLeft(PEVECTORFX pvec, BOOL bInvert)
                        { wpathLeft.vAddPoint(&ptfxThis, pvec, !bInvert); }

    VOID vAddLeft()     { wpathLeft.vAddPoint(&ptfxThis); }

    VOID vAddLeftNice(PEVECTORFX pvec, BOOL bInvert)
                        { vAddNice(wpathLeft, &ptfxThis, pvec, !bInvert); }


    VOID vAddRight(EVECTORFX& vec, BOOL bInvert = FALSE)
                        { wpathRight.vAddPoint(&ptfxThis, &vec, bInvert); }

    VOID vAddRight(PEVECTORFX pvec, BOOL bInvert)
                        { wpathRight.vAddPoint(&ptfxThis, pvec, bInvert); }

    VOID vAddRight()    { wpathRight.vAddPoint(&ptfxThis); }

    VOID vAddRightNice(PEVECTORFX pvec, BOOL bInvert)
                        { vAddNice(wpathRight, &ptfxThis, pvec, bInvert); }

    VOID vAddJoin(BOOL);
    VOID vAddRoundJoin(BOOL);
    VOID vAddEndCap();
    VOID vAddStartCap();
    BOOL bWiden();
    VOID vSetError()  { wpathRight.vSetError(); }

public:
    WIDENER(EPATHOBJ&, EXFORMOBJ&, PLINEATTRS);
    BOOL bValid()       { return(wpathRight.bValid() &&
                                 wpathLeft.bValid() &&
                                 wpen.bValid()); }
    VOID vMakeItWide(EPATHOBJ&);
};