/******************************Module*Header**********************************\
 *
 *                           *******************
 *                           * GDI SAMPLE CODE *
 *                           *******************
 *
 * Module Name: fillpath.c
 *
 * Copyright (c) 1994-1998 3Dlabs Inc. Ltd. All rights reserved.
 * Copyright (c) 1995-1999 Microsoft Corporation.  All rights reserved. 
\*****************************************************************************/
// LATER identify convex polygons and special-case?
// LATER identify vertical edges and special-case?
// LATER move pointed-to variables into automatics in search loops
// LATER punt to the engine with segmented framebuffer callbacks
// LATER handle complex clipping
// LATER coalesce rectangles

#include "precomp.h"
#include "log.h"
#include "gdi.h"
#include "clip.h"
#define ALLOC_TAG ALLOC_TAG_IF2P
//-----------------------------Public*Routine----------------------------------
//
// DrvFillPath
//
// This function fills the specified path with the specified brush and ROP.
// This function detects single convex polygons, and will call to separate
// faster convex polygon code for those cases.  This routine also detects
// polygons that are really rectangles, and handles those separately as well.
//
// Parameters
//  pso---------Points to a SURFOBJ structure that defines the surface on which
//              to draw. 
//  ppo---------Points to a PATHOBJ structure that defines the path to be filled
//              The PATHOBJ_Xxx service routines are provided to enumerate the
//              lines, Bezier curves, and other data that make up the path. 
//  pco---------Points to a CLIPOBJ structure. The CLIPOBJ_Xxx service routines
//              are provided to enumerate the clip region as a set of rectangles. 
//  pbo---------Points to a BRUSHOBJ structure that defines the pattern and
//              colors to fill with. 
//  pptlBrushOrg-Points to a POINTL structure that defines the brush origin,
//              which is used to align the brush pattern on the device. 
//  mix---------Defines the foreground and background raster operations to use
//              for the brush. 
//  flOptions---Specifies either FP_WINDINGMODE, indicating that a winding mode
//              fill should be performed, or FP_ALTERNATEMODE, indicating that
//              an alternating mode fill should be performed. All other flags
//              should be ignored. 
//
// Return Value
//  The return value is TRUE if the driver is able to fill the path. If the
//  path or clipping is too complex to be handled by the driver and should be
//  handled by GDI, the return value is FALSE, and an error code is not logged.
//  If the driver encounters an unexpected error, such as not being able to
//  realize the brush, the return value is DDI_ERROR, and an error code is
//  logged.
//
// Comments
//  GDI can call DrvFillPath to fill a path on a device-managed surface. When
//  deciding whether to call this function, GDI compares the fill requirements
//  with the following flags in the flGraphicsCaps member of the DEVINFO
//  structure: GCAPS_BEZIERS, GCAPS_ALTERNATEFILL, and GCAPS_WINDINGFILL.
//
//  The mix mode defines how the incoming pattern should be mixed with the data
//  already on the device surface. The MIX data type consists of two ROP2 values
//  packed into a single ULONG. The low-order byte defines the foreground raster
//  operation; the next byte defines the background raster operation.
//
//  Multiple polygons in a path cannot be treated as being disjoint; The fill
//  must consider all the points in the path.  That is, if the path contains
//  multiple polygons, you cannot simply draw one polygon after the other
//  (unless they don't overlap).
//
//  This function is an optional entry point for the driver. but is recommended
//  for good performance. To get GDI to call this function, not only do you
//  have to HOOK_FILLPATH, you have to set GCAPS_ALTERNATEFILL and/or
//  GCAPS_WINDINGFILL.
//
//-----------------------------------------------------------------------------
BOOL
DrvFillPath(SURFOBJ*    pso,
            PATHOBJ*    ppo,
            CLIPOBJ*    pco,
            BRUSHOBJ*   pbo,
            POINTL*     pptlBrush,
            MIX         mix,
            FLONG       flOptions)
{
    GFNPB   pb;
    BYTE    jClipping;      // Clipping type
    EDGE*   pCurrentEdge;
    EDGE    AETHead;        // Dummy head/tail node & sentinel for Active Edge
                            // Table
    EDGE*   pAETHead;       // Pointer to AETHead
    EDGE    GETHead;        // Dummy head/tail node & sentinel for Global Edge
                            // Table
    EDGE*   pGETHead;       // Pointer to GETHead
    EDGE*   pFreeEdges;     // Pointer to memory free for use to store edges
    ULONG   ulNumRects;     // # of rectangles to draw currently in rectangle
                            // list
    RECTL*  prclRects;      // Pointer to start of rectangle draw list
    INT     iCurrentY;      // Scan line for which we're currently scanning out
                            // the fill

    BOOL        bMore;
    PATHDATA    pd;
    RECTL       ClipRect;
    PDev*       ppdev;

    BOOL        bRetVal=FALSE;     // FALSE until proven TRUE
    BOOL        bMemAlloced=FALSE; // FALSE until proven TRUE

    FLONG       flFirstRecord;
    POINTFIX*   pPtFxTmp;
    ULONG       ulPtFxTmp;
    POINTFIX    aptfxBuf[NUM_BUFFER_POINTS];
    ULONG       ulRop4;

    DBG_GDI((6, "DrvFillPath called"));

    pb.psurfDst = (Surf*)pso->dhsurf;

    pb.pco = pco;
    ppdev = pb.psurfDst->ppdev;
    pb.ppdev = ppdev;
    pb.ulRop4 = gaMix[mix & 0xFF] | (gaMix[mix >> 8] << 8);
    ulRop4 = pb.ulRop4;


    vCheckGdiContext(ppdev);

    //
    // There's nothing to do if there are only one or two points
    //
    if ( ppo->cCurves <= 2 )
    {
        goto ReturnTrue;
    }

    //
    // Pass the surface off to GDI if it's a device bitmap that we've uploaded
    // to the system memory.
    //    
    if ( pb.psurfDst->flags == SF_SM )
    {
        DBG_GDI((1, "dest surface is in system memory. Punt it back"));

        
        return ( EngFillPath(pso, ppo, pco, pbo, pptlBrush, mix, flOptions));
    }

    //
    // Set up the clipping
    //
    if ( pco == (CLIPOBJ*)NULL )
    {
        //
        // No CLIPOBJ provided, so we don't have to worry about clipping
        //
        jClipping = DC_TRIVIAL;
    }
    else
    {
        //
        // Use the CLIPOBJ-provided clipping
        //
        jClipping = pco->iDComplexity;
    }
    
    //
    // Now we are sure the surface we are going to draw is in the video memory
    //
    // Set default fill as solid fill
    //
    pb.pgfn = vSolidFillWithRop;
    pb.solidColor = 0;    //Assume we don't need a pattern
    pb.prbrush = NULL;

    //
    // It is too difficult to determine interaction between
    // multiple paths, if there is more than one, skip this
    //
    PATHOBJ_vEnumStart(ppo);
    bMore = PATHOBJ_bEnum(ppo, &pd);

    //
    // First we need to check if we need a pattern or not
    //
    if ( (((ulRop4 & 0xff00) >> 8) != (ulRop4 & 0x00ff))
      || ((((ulRop4 >> 4) ^ (ulRop4)) & 0xf0f) != 0) )
    {
        pb.solidColor = pbo->iSolidColor;

        //
        // Check to see if it is a non-solid brush (-1)
        //
        if ( pbo->iSolidColor == -1 )
        {
            //
            // Get the driver's realized brush
            //
            pb.prbrush = (RBrush*)pbo->pvRbrush;

            //
            // If it hasn't been realized, do it
            // Note: GDI will call DrvRealizeBrsuh to fullfill this task. So the
            // driver should have this function ready
            //
            if ( pb.prbrush == NULL )
            {
                DBG_GDI((7, "Realizing brush"));

                pb.prbrush = (RBrush*)BRUSHOBJ_pvGetRbrush(pbo);
                if ( pb.prbrush == NULL )
                {
                    //
                    // If we can't realize it, nothing we can do
                    //


                    return(FALSE);
                }
                DBG_GDI((7, "Brsuh realizing done"));
            }// Realize brush

            pb.pptlBrush = pptlBrush;

            //
            // Check if brush pattern is 1 BPP or not
            // Note: This is set in DrvRealizeBrush
            //
            if ( pb.prbrush->fl & RBRUSH_2COLOR )
            {
                //
                // 1 BPP pattern. Do a Mono fill
                //
                pb.pgfn = vMonoPatFill;
            }
            else
            {
                //
                // Pattern is more than 1 BPP. Do color pattern fill
                //
                pb.pgfn = vPatFill;
                DBG_GDI((7, "Skip Fast Fill Color Pattern"));

                //
                // P2 can not handle fast filled patterns
                //
                goto SkipFastFill;
            }
        }// Handle non-solid brush
    }// Blackness check

    //
    // For solid brush, we can use FastFill
    //
    if ( bMore )
    {
        //
        // FastFill only knows how to take a single contiguous buffer
        // of points.  Unfortunately, GDI sometimes hands us paths
        // that are split over multiple path data records.  Convex
        // figures such as Ellipses, Pies and RoundRects are almost
        // always given in multiple records.  Since probably 90% of
        // multiple record paths could still be done by FastFill, for
        // those cases we simply copy the points into a contiguous
        // buffer...
        //
        // First make sure that the entire path would fit in the
        // temporary buffer, and make sure the path isn't comprised
        // of more than one subpath:
        //
        if ( (ppo->cCurves >= NUM_BUFFER_POINTS)
           ||(pd.flags & PD_ENDSUBPATH) )
        {
            goto SkipFastFill;
        }

        pPtFxTmp = &aptfxBuf[0];

        //
        // Copy one vertex over to pPtFxTmp from pd(path data)
        //
        RtlCopyMemory(pPtFxTmp, pd.pptfx, sizeof(POINTFIX) * pd.count);

        //
        // Move the memory pointer over to next structure
        //
        pPtFxTmp     += pd.count;
        ulPtFxTmp     = pd.count;
        flFirstRecord = pd.flags;       // Remember PD_BEGINSUBPATH flag

        //
        // Loop to get all the vertex info. After the loop, all the vertex info
        // will be in array aptfxBuf[]
        //
        do
        {
            bMore = PATHOBJ_bEnum(ppo, &pd);

            RtlCopyMemory(pPtFxTmp, pd.pptfx, sizeof(POINTFIX) * pd.count);
            ulPtFxTmp += pd.count;
            pPtFxTmp += pd.count;
        } while ( !(pd.flags & PD_ENDSUBPATH) );

        //
        // Fake up the path data record
        //
        pd.pptfx  = &aptfxBuf[0];
        pd.count  = ulPtFxTmp;
        pd.flags |= flFirstRecord;

        //
        // If there's more than one subpath, we can't call FastFill
        //
        DBG_GDI((7, "More than one subpath!"));
        if ( bMore )
        {
            goto SkipFastFill;
        }
    }// if ( bMore )

    //
    // Fast polygon fill
    //
    if ( bFillPolygon(ppdev, (Surf*)pso->dhsurf, pd.count,
                      pd.pptfx, pb.solidColor,
                      ulRop4,
                      pco, pb.prbrush, pptlBrush) )
    {
        DBG_GDI((7, "Fast Fill Succeeded"));
        InputBufferFlush(ppdev);


        return (TRUE);
    }

SkipFastFill:
    DBG_GDI((7, "Fast Fill Skipped"));
    if ( jClipping != DC_TRIVIAL )
    {
        if ( jClipping != DC_RECT )
        {
            DBG_GDI((7, "Complex Clipping"));

            //
            // There is complex clipping; let GDI fill the path
            //
            goto ReturnFalse;
        }

        //
        // Clip to the clip rectangle
        //
        ClipRect = pco->rclBounds;
    }
    else
    {
        //
        // So the y-clipping code doesn't do any clipping
        // We don't blow the values out when we scale up to GIQ
        //
        ClipRect.top = (LONG_MIN + 1) / 16; // +1 to avoid compiler problem
        ClipRect.bottom = LONG_MAX / 16;
    }

    //
    // Set up working storage in the temporary buffer, storage for list of
    // rectangles to draw
    // Note: ppdev->pvTmpBuffer is allocated in DrvEnableSurface() in enable.c
    // The purpose of using ppdev->pvTmpBuffer is to save us from having to
    // allocate and free the temp space inside high frequency calls. It was
    // allocated for TMP_BUFFER_SIZE bytes and will be freed in
    // DrvDeleteSurface()
    //
    prclRects = (RECTL*)ppdev->pvTmpBuffer;

    if ( !bMore )
    {
        RECTL*  pTmpRect;
        INT     cPoints = pd.count;

        //
        // The count can't be less than three, because we got all the edges
        // in this subpath, and above we checked that there were at least
        // three edges
        //
        // If the count is four, check to see if the polygon is really a
        // rectangle since we can really speed that up. We'll also check for
        // five with the first and last points the same.
        //
        // ??? we have already done the memcpy for the pd data. shall we use it
        //
        if ( ( cPoints == 4 )
           ||( ( cPoints == 5 )
             &&(pd.pptfx[0].x == pd.pptfx[4].x)
             &&(pd.pptfx[0].y == pd.pptfx[4].y) ) )
        {
            //
            // Get storage space for this temp rectangle
            //
            pTmpRect = prclRects;

            //
            // We have to start somewhere to assume that most
            // applications specify the top left point first
            // We want to check that the first two points are
            // either vertically or horizontally aligned.  If
            // they are then we check that the last point [3]
            // is either horizontally or  vertically  aligned,
            // and finally that the 3rd point [2] is  aligned
            // with both the first point and the  last  point
            //
            pTmpRect->top   = pd.pptfx[0].y - 1 & FIX_MASK;
            pTmpRect->left  = pd.pptfx[0].x - 1 & FIX_MASK;
            pTmpRect->right = pd.pptfx[1].x - 1 & FIX_MASK;

            //
            // Check if the first two points are vertically alligned
            //
            if ( pTmpRect->left ^ pTmpRect->right )
            {
                //
                // The first two points are not vertically alligned
                // Let's see if these two points are horizontal alligned
                //
                if ( pTmpRect->top  ^ (pd.pptfx[1].y - 1 & FIX_MASK) )
                {
                    //
                    // The first two points are not horizontally alligned
                    // So it is not a rectangle
                    //
                    goto not_rectangle;
                }

                //
                // Up to now, the first two points are horizontally alligned,
                // but not vertically alligned. We need to check if the first
                // point vertically alligned with the 4th point
                //
                if ( pTmpRect->left ^ (pd.pptfx[3].x - 1 & FIX_MASK) )
                {
                    //
                    // The first point is not vertically alligned with the 4th
                    // point either. So this is not a rectangle
                    //
                    goto not_rectangle;
                }

                //
                // Check if the 2nd point and the 3rd point are vertically aligned
                //
                if ( pTmpRect->right ^ (pd.pptfx[2].x - 1 & FIX_MASK) )
                {
                    //
                    // The 2nd point and the 3rd point are not vertically aligned
                    // So this is not a rectangle
                    //
                    goto not_rectangle;
                }

                //
                // Check to see if the 3rd and 4th points are horizontally
                // alligned. If not, then it is not a rectangle
                //
                pTmpRect->bottom = pd.pptfx[2].y - 1 & FIX_MASK;
                if ( pTmpRect->bottom ^ (pd.pptfx[3].y - 1 & FIX_MASK) )
                {
                    goto not_rectangle;
                }
            }// Check if the first two points are vertically alligned
            else
            {
                //
                // The first two points are vertically alligned. Now we need to
                // check if the 1st point and the 4th point are horizontally
                // aligned. If not, then this is not a rectangle
                //
                if ( pTmpRect->top ^ (pd.pptfx[3].y - 1 & FIX_MASK) )
                {
                    goto not_rectangle;
                }

                //
                // Check if the 2nd point and the 3rd point are horizontally
                // aligned. If not, then this is not a rectangle
                //
                pTmpRect->bottom = pd.pptfx[1].y - 1 & FIX_MASK;
                if ( pTmpRect->bottom ^ (pd.pptfx[2].y - 1 & FIX_MASK) )
                {
                    goto not_rectangle;
                }

                //
                // Check if the 3rd point and the 4th point are vertically
                // aligned. If not, then this is not a rectangle
                //
                pTmpRect->right = pd.pptfx[2].x - 1 & FIX_MASK;
                if ( pTmpRect->right ^ (pd.pptfx[3].x - 1 & FIX_MASK) )
                {
                    goto not_rectangle;
                }
            }

            //
            // We have a rectangle now. Do some adjustment here first
            // If the left is greater than the right then
            // swap them so the blt code won't have problem
            //
            if ( pTmpRect->left > pTmpRect->right )
            {
                FIX temp;

                temp = pTmpRect->left;
                pTmpRect->left = pTmpRect->right;
                pTmpRect->right = temp;
            }
            else
            {
                //
                // If left == right there's nothing to draw
                //
                if ( pTmpRect->left == pTmpRect->right )
                {
                    DBG_GDI((7, "Nothing to draw"));
                    goto ReturnTrue;
                }
            }// Adjust right and left edge

            //
            // Shift the values to get pixel coordinates
            //
            pTmpRect->left  = (pTmpRect->left  >> FIX_SHIFT) + 1;
            pTmpRect->right = (pTmpRect->right >> FIX_SHIFT) + 1;

            //
            // Adjust the top and bottom coordiantes if necessary
            //
            if ( pTmpRect->top > pTmpRect->bottom )
            {
                FIX temp;

                temp = pTmpRect->top;
                pTmpRect->top = pTmpRect->bottom;
                pTmpRect->bottom = temp;
            }
            else
            {
                if ( pTmpRect->top == pTmpRect->bottom )
                {
                    DBG_GDI((7, "Nothing to draw"));
                    goto ReturnTrue;
                }
            }

            //
            // Shift the values to get pixel coordinates
            //
            pTmpRect->top    = (pTmpRect->top    >> FIX_SHIFT) + 1;
            pTmpRect->bottom = (pTmpRect->bottom >> FIX_SHIFT) + 1;

            //
            // Finally, check for clipping
            //
            if ( jClipping == DC_RECT )
            {
                //
                // Clip to the clip rectangle
                //
                if ( !bIntersect(pTmpRect, &ClipRect, pTmpRect) )
                {
                    //
                    // Totally clipped, nothing to do
                    //
                    DBG_GDI((7, "Nothing to draw"));
                    goto ReturnTrue;
                }
            }

            //
            // If we get here then the polygon is a rectangle,
            // set count to 1 and goto bottom to draw it
            //
            ulNumRects = 1;
            goto draw_remaining_rectangles;
        }// Check to see if it is a rectangle

not_rectangle:
        ;
    }// if ( !bMore )

    //
    // Do we have enough memory for all the edges?
    // LATER does cCurves include closure????
    //
    if ( ppo->cCurves > MAX_EDGES )
    {
        //
        // Try to allocate enough memory
        //
        pFreeEdges = (EDGE*)ENGALLOCMEM(0, (ppo->cCurves * sizeof(EDGE)),
                                        ALLOC_TAG);
        if ( pFreeEdges == NULL )
        {
            DBG_GDI((1, "Can't allocate memory for %d edges", ppo->cCurves));

            //
            // Too many edges; let GDI fill the path
            //
            goto ReturnFalse;
        }
        else
        {
            //
            // Set a flag to indicate that we have allocate the memory so that
            // we can free it later
            //
            bMemAlloced = TRUE;
        }
    }// if ( ppo->cCurves > MAX_EDGES )
    else
    {
        //
        // If the total number of edges doesn't exceed the MAX_EDGES, then just
        // use our handy temporary buffer (it's big enough)
        //
        pFreeEdges = (EDGE*)((BYTE*)ppdev->pvTmpBuffer + RECT_BYTES);
    }

    //
    // Initialize an empty list of rectangles to fill
    //
    ulNumRects = 0;

    //
    // Enumerate the path edges and build a Global Edge Table (GET) from them
    // in YX-sorted order.
    //
    pGETHead = &GETHead;
    if ( !bConstructGET(pGETHead, pFreeEdges, ppo, &pd, bMore, &ClipRect) )
    {
        DBG_GDI((7, "Outside Range"));
        goto ReturnFalse;  // outside GDI's 2**27 range
    }

    //
    // Create an empty AET with the head node also a tail sentinel
    //
    pAETHead = &AETHead;
    AETHead.pNext = pAETHead;       // Mark that the AET is empty
    AETHead.X = 0x7FFFFFFF;         // This is greater than any valid X value, so
                                    // searches will always terminate

    //
    // Top scan of polygon is the top of the first edge we come to
    //
    iCurrentY = ((EDGE*)GETHead.pNext)->Y;

    //
    // Loop through all the scans in the polygon, adding edges from the GET to
    // the Active Edge Table (AET) as we come to their starts, and scanning out
    // the AET at each scan into a rectangle list. Each time it fills up, the
    // rectangle list is passed to the filling routine, and then once again at
    // the end if any rectangles remain undrawn. We continue so long as there
    // are edges to be scanned out
    //
    while ( 1 )
    {
        //
        // Advance the edges in the AET one scan, discarding any that have
        // reached the end (if there are any edges in the AET)
        //
        if ( AETHead.pNext != pAETHead )
        {
            vAdvanceAETEdges(pAETHead);
        }

        //
        // If the AET is empty, done if the GET is empty, else jump ahead to
        // the next edge in the GET; if the AET isn't empty, re-sort the AET
        //
        if ( AETHead.pNext == pAETHead )
        {
            if ( GETHead.pNext == pGETHead )
            {
                //
                // Done if there are no edges in either the AET or the GET
                //
                break;
            }

            //
            // There are no edges in the AET, so jump ahead to the next edge in
            // the GET
            //
            iCurrentY = ((EDGE*)GETHead.pNext)->Y;
        }
        else
        {
            //
            // Re-sort the edges in the AET by X coordinate, if there are at
            // least two edges in the AET (there could be one edge if the
            // balancing edge hasn't yet been added from the GET)
            //
            if ( ((EDGE*)AETHead.pNext)->pNext != pAETHead )
            {
                vXSortAETEdges(pAETHead);
            }
        }

        //
        // Move any new edges that start on this scan from the GET to the AET;
        // bother calling only if there's at least one edge to add
        //
        if ( ((EDGE*)GETHead.pNext)->Y == iCurrentY )
        {
            vMoveNewEdges(pGETHead, pAETHead, iCurrentY);
        }

        //
        // Scan the AET into rectangles to fill (there's always at least one
        // edge pair in the AET)
        //
        pCurrentEdge = (EDGE*)AETHead.pNext;   // point to the first edge
        do
        {
            INT iLeftEdge;

            //
            // The left edge of any given edge pair is easy to find; it's just
            // wherever we happen to be currently
            //
            iLeftEdge = pCurrentEdge->X;

            //
            // Find the matching right edge according to the current fill rule
            //
            if ( (flOptions & FP_WINDINGMODE) != 0 )
            {

                INT iWindingCount;

                //
                // Do winding fill; scan across until we've found equal numbers
                // of up and down edges
                //
                iWindingCount = pCurrentEdge->iWindingDirection;
                do
                {
                    pCurrentEdge = (EDGE*)pCurrentEdge->pNext;
                    iWindingCount += pCurrentEdge->iWindingDirection;
                } while ( iWindingCount != 0 );
            }
            else
            {
                //
                // Odd-even fill; the next edge is the matching right edge
                //
                pCurrentEdge = (EDGE*)pCurrentEdge->pNext;
            }

            //
            // See if the resulting span encompasses at least one pixel, and
            // add it to the list of rectangles to draw if so
            //
            if ( iLeftEdge < pCurrentEdge->X )
            {
                //
                // We've got an edge pair to add to the list to be filled; see
                // if there's room for one more rectangle
                //
                if ( ulNumRects >= MAX_PATH_RECTS )
                {
                    //
                    // No more room; draw the rectangles in the list and reset
                    // it to empty
                    //
                    pb.lNumRects = ulNumRects;
                    pb.pRects = prclRects;

                    pb.pgfn(&pb);

                    //
                    // Reset the list to empty
                    //
                    ulNumRects = 0;
                }

                //
                // Add the rectangle representing the current edge pair
                //
                if ( jClipping == DC_RECT )
                {
                    //
                    // Clipped
                    // Clip to left
                    //
                    prclRects[ulNumRects].left = max(iLeftEdge, ClipRect.left);

                    //
                    // Clip to right
                    //
                    prclRects[ulNumRects].right =
                                        min(pCurrentEdge->X, ClipRect.right);

                    //
                    // Draw only if not fully clipped
                    //
                    if ( prclRects[ulNumRects].left
                       < prclRects[ulNumRects].right )
                    {
                        prclRects[ulNumRects].top = iCurrentY;
                        prclRects[ulNumRects].bottom = iCurrentY + 1;
                        ulNumRects++;
                    }
                }
                else
                {
                    //
                    // Unclipped
                    //
                    prclRects[ulNumRects].top = iCurrentY;
                    prclRects[ulNumRects].bottom = iCurrentY + 1;
                    prclRects[ulNumRects].left = iLeftEdge;
                    prclRects[ulNumRects].right = pCurrentEdge->X;
                    ulNumRects++;
                }
            }
        } while ( (pCurrentEdge = (EDGE*)pCurrentEdge->pNext) != pAETHead );

        iCurrentY++;                        // next scan
    }// Loop through all the scans in the polygon

    //
    // Draw the remaining rectangles, if there are any
    //
draw_remaining_rectangles:

    if ( ulNumRects > 0 )
    {
        pb.lNumRects = ulNumRects;
        pb.pRects = prclRects;
        
        pb.pgfn(&pb);
    }

ReturnTrue:
    DBG_GDI((7, "Drawn"));
    bRetVal = TRUE;                         // done successfully

ReturnFalse:
    //
    // bRetVal is originally false.  If you jumped to ReturnFalse from somewhere,
    // then it will remain false, and be returned.
    //
    if ( bMemAlloced )
    {
        //
        // We did allocate memory, so release it
        //
        ENGFREEMEM(pFreeEdges);
    }

    DBG_GDI((6, "Returning %s", bRetVal ? "True" : "False"));

    InputBufferFlush(ppdev);


    return (bRetVal);
}// DrvFillPath()

//-----------------------------------------------------------------------------
//
// void vAdvanceAETEdges(EDGE* pAETHead)
//
// Advance the edges in the AET to the next scan, dropping any for which we've
// done all scans. Assumes there is at least one edge in the AET.
//
//-----------------------------------------------------------------------------
VOID
vAdvanceAETEdges(EDGE* pAETHead)
{
    EDGE*   pLastEdge;
    EDGE*   pCurrentEdge;

    pLastEdge = pAETHead;
    pCurrentEdge = (EDGE*)pLastEdge->pNext;
    do
    {
        //
        // Count down this edge's remaining scans
        //
        if ( --pCurrentEdge->iScansLeft == 0 )
        {
            //
            // We've done all scans for this edge; drop this edge from the AET
            //
            pLastEdge->pNext = pCurrentEdge->pNext;
        }
        else
        {
            //
            // Advance the edge's X coordinate for a 1-scan Y advance
            // Advance by the minimum amount
            //
            pCurrentEdge->X += pCurrentEdge->iXWhole;

            //
            // Advance the error term and see if we got one extra pixel this
            // time
            //
            pCurrentEdge->iErrorTerm += pCurrentEdge->iErrorAdjustUp;

            if ( pCurrentEdge->iErrorTerm >= 0 )
            {
                //
                // The error term turned over, so adjust the error term and
                // advance the extra pixel
                //
                pCurrentEdge->iErrorTerm -= pCurrentEdge->iErrorAdjustDown;
                pCurrentEdge->X += pCurrentEdge->iXDirection;
            }

            pLastEdge = pCurrentEdge;
        }
    } while ((pCurrentEdge = (EDGE *)pLastEdge->pNext) != pAETHead);
}// vAdvanceAETEdges()

//-----------------------------------------------------------------------------
//
// VOID vXSortAETEdges(EDGE* pAETHead)
//
// X-sort the AET, because the edges may have moved around relative to
// one another when we advanced them. We'll use a multipass bubble
// sort, which is actually okay for this application because edges
// rarely move relative to one another, so we usually do just one pass.
// Also, this makes it easy to keep just a singly-linked list. Assumes there
// are at least two edges in the AET.
//
//-----------------------------------------------------------------------------
VOID
vXSortAETEdges(EDGE *pAETHead)
{
    BOOL    bEdgesSwapped;
    EDGE*   pLastEdge;
    EDGE*   pCurrentEdge;
    EDGE*   pNextEdge;

    do
    {
        bEdgesSwapped = FALSE;
        pLastEdge = pAETHead;
        pCurrentEdge = (EDGE *)pLastEdge->pNext;
        pNextEdge = (EDGE *)pCurrentEdge->pNext;

        do
        {
            if ( pNextEdge->X < pCurrentEdge->X )
            {
                //
                // Next edge is to the left of the current edge; swap them
                //
                pLastEdge->pNext = pNextEdge;
                pCurrentEdge->pNext = pNextEdge->pNext;
                pNextEdge->pNext = pCurrentEdge;
                bEdgesSwapped = TRUE;

                //
                // Continue sorting before the edge we just swapped; it might
                // move farther yet
                //
                pCurrentEdge = pNextEdge;
            }
            
            pLastEdge = pCurrentEdge;
            pCurrentEdge = (EDGE *)pLastEdge->pNext;
        } while ( (pNextEdge = (EDGE*)pCurrentEdge->pNext) != pAETHead );
    } while ( bEdgesSwapped );
}// vXSortAETEdges()

//-----------------------------------------------------------------------------
//
// VOID vMoveNewEdges(EDGE* pGETHead, EDGE* pAETHead, INT iCurrentY)
//
// Moves all edges that start on the current scan from the GET to the AET in
// X-sorted order. Parameters are pointer to head of GET and pointer to dummy
// edge at head of AET, plus current scan line. Assumes there's at least one
// edge to be moved.
//
//-----------------------------------------------------------------------------
VOID
vMoveNewEdges(EDGE* pGETHead,
              EDGE* pAETHead,
              INT   iCurrentY)
{
    EDGE*   pCurrentEdge = pAETHead;
    EDGE*   pGETNext = (EDGE*)pGETHead->pNext;

    do
    {
        //
        // Scan through the AET until the X-sorted insertion point for this
        // edge is found. We can continue from where the last search left
        // off because the edges in the GET are in X sorted order, as is
        // the AET. The search always terminates because the AET sentinel
        // is greater than any valid X
        //
        while ( pGETNext->X > ((EDGE *)pCurrentEdge->pNext)->X )
        {
            pCurrentEdge = (EDGE*)pCurrentEdge->pNext;
        }

        //
        // We've found the insertion point; add the GET edge to the AET, and
        // remove it from the GET
        //
        pGETHead->pNext = pGETNext->pNext;
        pGETNext->pNext = pCurrentEdge->pNext;
        pCurrentEdge->pNext = pGETNext;
        pCurrentEdge = pGETNext;    // continue insertion search for the next
                                    //  GET edge after the edge we just added
        pGETNext = (EDGE*)pGETHead->pNext;
    } while (pGETNext->Y == iCurrentY);
}// vMoveNewEdges()

//-----------------------------------------------------------------------------
//
// BOOL (EDGE* pGETHead, EDGE* pAETHead, INT iCurrentY)
//
// Build the Global Edge Table from the path. There must be enough memory in
// the free edge area to hold all edges. The GET is constructed in Y-X order,
// and has a head/tail/sentinel node at pGETHead.
//
//-----------------------------------------------------------------------------
BOOL
bConstructGET(EDGE*     pGETHead,
              EDGE*     pFreeEdges,
              PATHOBJ*  ppo,
              PATHDATA* pd,
              BOOL      bMore,
              RECTL*    pClipRect)
{
    POINTFIX pfxPathStart;    // point that started the current subpath
    POINTFIX pfxPathPrevious; // point before the current point in a subpath;
                              // starts the current edge

    //
    // Create an empty GET with the head node also a tail sentinel
    //
    pGETHead->pNext = pGETHead; // mark that the GET is empty
    pGETHead->Y = 0x7FFFFFFF;   // this is greater than any valid Y value, so
                                // Searches will always terminate

    //
    // Note: PATHOBJ_vEnumStart is implicitly  performed by engine
    // already and first path is enumerated by the caller 
    // so here we don't need to call it again.
    //
next_subpath:

    //
    // Make sure the PATHDATA is not empty (is this necessary)???
    //
    if ( pd->count != 0 )
    {
        //
        // If first point starts a subpath, remember it as such
        // and go on to the next point, so we can get an edge
        //
        if ( pd->flags & PD_BEGINSUBPATH )
        {
            //
            // The first point starts the subpath; Remember it
            //
            pfxPathStart    = *pd->pptfx; // the subpath starts here
            pfxPathPrevious = *pd->pptfx; // this point starts the next edge
            pd->pptfx++;                  // advance to the next point
            pd->count--;                  // count off this point
        }

        //
        // Add edges in PATHDATA to GET, in Y-X sorted order
        //
        while ( pd->count-- )
        {
            if ( (pFreeEdges =
                  pAddEdgeToGET(pGETHead, pFreeEdges, &pfxPathPrevious,
                                pd->pptfx, pClipRect)) == NULL )
            {
                goto ReturnFalse;
            }

            pfxPathPrevious = *pd->pptfx; // current point becomes previous
            pd->pptfx++;                  // advance to the next point
        }// Loop through all the points

        //
        // If last point ends the subpath, insert the edge that
        // connects to first point  (is this built in already?)
        //
        if ( pd->flags & PD_ENDSUBPATH )
        {
            if ( (pFreeEdges = pAddEdgeToGET(pGETHead, pFreeEdges, &pfxPathPrevious,
                                            &pfxPathStart, pClipRect)) == NULL )
            {
                goto ReturnFalse;
            }
        }
    }// if ( pd->count != 0 )

    //
    // The initial loop conditions preclude a do, while or for
    //
    if ( bMore )
    {
        bMore = PATHOBJ_bEnum(ppo, pd);
        goto next_subpath;
    }

    return(TRUE);   // done successfully

ReturnFalse:
    return(FALSE);  // failed
}// bConstructGET()

//-----------------------------------------------------------------------------
//
// EDGE* pAddEdgeToGET(EDGE* pGETHead, EDGE* pFreeEdge, POINTFIX* ppfxEdgeStart,
//                     POINTFIX* ppfxEdgeEnd, RECTL* pClipRect)
//
// Adds the edge described by the two passed-in points to the Global Edge
// Table (GET), if the edge spans at least one pixel vertically.
//
//-----------------------------------------------------------------------------
EDGE*
pAddEdgeToGET(EDGE*     pGETHead,
              EDGE*     pFreeEdge,
              POINTFIX* ppfxEdgeStart,
              POINTFIX* ppfxEdgeEnd,
              RECTL*    pClipRect)
{
    int iYStart;
    int iYEnd;
    int iXStart;
    int iXEnd;
    int iYHeight;
    int iXWidth;
    int yJump;
    int yTop;

    //
    // Set the winding-rule direction of the edge, and put the endpoints in
    // top-to-bottom order
    //
    iYHeight = ppfxEdgeEnd->y - ppfxEdgeStart->y;

    if ( iYHeight == 0 )
    {
        //
        // Zero height; ignore this edge
        //
        return(pFreeEdge);
    }
    else if ( iYHeight > 0 )
    {
        //
        // Top-to-bottom
        //
        iXStart = ppfxEdgeStart->x;
        iYStart = ppfxEdgeStart->y;
        iXEnd = ppfxEdgeEnd->x;
        iYEnd = ppfxEdgeEnd->y;

        pFreeEdge->iWindingDirection = 1;
    }
    else
    {
        iYHeight = -iYHeight;
        iXEnd = ppfxEdgeStart->x;
        iYEnd = ppfxEdgeStart->y;
        iXStart = ppfxEdgeEnd->x;
        iYStart = ppfxEdgeEnd->y;
        
        pFreeEdge->iWindingDirection = -1;
    }

    if ( iYHeight & 0x80000000 )
    {
        //
        // Too large; outside 2**27 GDI range
        //
        return(NULL);
    }

    //
    // Set the error term and adjustment factors, all in GIQ coordinates for
    // now
    //
    iXWidth = iXEnd - iXStart;
    if ( iXWidth >= 0 )
    {
        //
        // Left to right, so we change X as soon as we move at all
        //
        pFreeEdge->iXDirection = 1;
        pFreeEdge->iErrorTerm = -1;
    }
    else
    {
        //
        // Right to left, so we don't change X until we've moved a full GIQ
        // coordinate
        //
        iXWidth = -iXWidth;
        pFreeEdge->iXDirection = -1;
        pFreeEdge->iErrorTerm = -iYHeight;
    }

    if ( iXWidth & 0x80000000 )
    {
        //
        // Too large; outside 2**27 GDI range
        //
        return(NULL);
    }

    if ( iXWidth >= iYHeight )
    {
        //
        // Calculate base run length (minimum distance advanced in X for a 1-
        // scan advance in Y)
        //
        pFreeEdge->iXWhole = iXWidth / iYHeight;

        //
        // Add sign back into base run length if going right to left
        //
        if ( pFreeEdge->iXDirection == -1 )
        {
            pFreeEdge->iXWhole = -pFreeEdge->iXWhole;
        }

        pFreeEdge->iErrorAdjustUp = iXWidth % iYHeight;
    }
    else
    {
        //
        // Base run length is 0, because line is closer to vertical than
        // horizontal
        //
        pFreeEdge->iXWhole = 0;
        pFreeEdge->iErrorAdjustUp = iXWidth;
    }

    pFreeEdge->iErrorAdjustDown = iYHeight;

    //
    // Calculate the number of pixels spanned by this edge, accounting for
    // clipping
    //
    // Top true pixel scan in GIQ coordinates
    // Shifting to divide and multiply by 16 is okay because the clip rect
    // always contains positive numbers
    //
    yTop = max(pClipRect->top << 4, (iYStart + 15) & ~0x0F);

    //
    // Initial scan line on which to fill edge
    //
    pFreeEdge->Y = yTop >> 4;

    //
    // Calculate # of scans to actually fill, accounting for clipping
    //
    if ( (pFreeEdge->iScansLeft = min(pClipRect->bottom, ((iYEnd + 15) >> 4))
         - pFreeEdge->Y) <= 0 )
    {
        //
        // No pixels at all are spanned, so we can ignore this edge
        //
        return(pFreeEdge);
    }

    //
    // If the edge doesn't start on a pixel scan (that is, it starts at a
    // fractional GIQ coordinate), advance it to the first pixel scan it
    // intersects. Ditto if there's top clipping. Also clip to the bottom if
    // needed
    //
    if ( iYStart != yTop )
    {
        //
        // Jump ahead by the Y distance in GIQ coordinates to the first pixel
        // to draw
        //
        yJump = yTop - iYStart;

        //
        // Advance x the minimum amount for the number of scans traversed
        //
        iXStart += pFreeEdge->iXWhole * yJump;

        vAdjustErrorTerm(&pFreeEdge->iErrorTerm, pFreeEdge->iErrorAdjustUp,
                        pFreeEdge->iErrorAdjustDown, yJump, &iXStart,
                        pFreeEdge->iXDirection);
    }

    //
    // Turn the calculations into pixel rather than GIQ calculations
    //
    // Move the X coordinate to the nearest pixel, and adjust the error term
    // accordingly
    // Dividing by 16 with a shift is okay because X is always positive
    pFreeEdge->X = (iXStart + 15) >> 4; // convert from GIQ to pixel coordinates

    //
    // LATER adjust only if needed (if prestepped above)?
    //
    if ( pFreeEdge->iXDirection == 1 )
    {
        //
        // Left to right
        //
        pFreeEdge->iErrorTerm -= pFreeEdge->iErrorAdjustDown
                               * (((iXStart + 15) & ~0x0F) - iXStart);
    }
    else
    {
        //
        // Right to left
        //
        pFreeEdge->iErrorTerm -= pFreeEdge->iErrorAdjustDown
                               * ((iXStart - 1) & 0x0F);
    }

    //
    // Scale the error term down 16 times to switch from GIQ to pixels.
    // Shifts work to do the multiplying because these values are always
    // non-negative
    //
    pFreeEdge->iErrorTerm >>= 4;

    //
    // Insert the edge into the GET in YX-sorted order. The search always ends
    // because the GET has a sentinel with a greater-than-possible Y value
    //
    while (  (pFreeEdge->Y > ((EDGE*)pGETHead->pNext)->Y)
           ||( (pFreeEdge->Y == ((EDGE*)pGETHead->pNext)->Y)
             &&(pFreeEdge->X > ((EDGE*)pGETHead->pNext)->X) ) )
    {
        pGETHead = (EDGE*)pGETHead->pNext;
    }

    pFreeEdge->pNext = pGETHead->pNext; // link the edge into the GET
    pGETHead->pNext = pFreeEdge;

    //
    // Point to the next edge storage location for next time
    //
    return(++pFreeEdge);
}// pAddEdgeToGET()

//-----------------------------------------------------------------------------
//
// void vAdjustErrorTerm(int *pErrorTerm, int iErrorAdjustUp,
//                       int iErrorAdjustDown, int yJump, int *pXStart,
//                       int iXDirection)
// Adjust the error term for a skip ahead in y. This is in ASM because there's
// a multiply/divide that may involve a larger than 32-bit value.
//
//-----------------------------------------------------------------------------
void
vAdjustErrorTerm(int*   pErrorTerm,
                 int    iErrorAdjustUp,
                 int    iErrorAdjustDown,
                 int    yJump,
                 int*   pXStart,
                 int    iXDirection)

{
#if defined(_X86_) || defined(i386)
    //
    // Adjust the error term up by the number of y coordinates we'll skip
    // *pErrorTerm += iErrorAdjustUp * yJump;
    //
    _asm    mov ebx,pErrorTerm
    _asm    mov eax,iErrorAdjustUp
    _asm    mul yJump
    _asm    add eax,[ebx]
    _asm    adc edx,-1      // the error term starts out negative

    //
    // See if the error term turned over even once while skipping
    //
    _asm    js  short NoErrorTurnover

    //
    // # of times we'll turn over the error term and step an extra x
    // coordinate while skipping
    // NumAdjustDowns = (*pErrorTerm / iErrorAdjustDown) + 1;
    //
    _asm    div iErrorAdjustDown
    _asm    inc eax

    //
    // Note that EDX is the remainder; (EDX - iErrorAdjustDown) is where
    // the error term ends up ultimately
    //
    // Advance x appropriately for the # of times the error term
    // turned over
    // if (iXDirection == 1)
    // {
    //     *pXStart += NumAdjustDowns;
    // }
    // else
    // {
    //     *pXStart -= NumAdjustDowns;
    // }
    //
    _asm    mov ecx,pXStart
    _asm    cmp iXDirection,1
    _asm    jz  short GoingRight
    _asm    neg eax
GoingRight:
    _asm    add [ecx],eax

    // Adjust the error term down to its proper post-skip value
    // *pErrorTerm -= iErrorAdjustDown * NumAdjustDowns;
    _asm    sub edx,iErrorAdjustDown
    _asm    mov eax,edx     // put into EAX for storing to pErrorTerm next
NoErrorTurnover:
    _asm    mov [ebx],eax

#else
    //
    // LONGLONGS are 64 bit integers (We hope!) as the multiply could
    // overflow 32 bit integers. If 64 bit ints are unsupported, the
    // LONGLONG will end up as a double. Hopefully there will be no
    // noticable difference in accuracy.
    LONGLONG NumAdjustDowns;
    LONGLONG tmpError = *pErrorTerm;

    //
    // Adjust the error term up by the number of y coordinates we'll skip
    //
    tmpError += (LONGLONG)iErrorAdjustUp * (LONGLONG)yJump;

    //
    // See if the error term turned over even once while skipping
    //
    if ( tmpError >= 0 )
    {
        //
        // # of times we'll turn over the error term and step an extra x
        // coordinate while skipping
        //
        NumAdjustDowns = (tmpError / (LONGLONG)iErrorAdjustDown) + 1;

        //
        // Advance x appropriately for the # of times the error term
        // turned over
        //
        if ( iXDirection == 1 )
        {
            *pXStart += (LONG)NumAdjustDowns;
        }
        else
        {
            *pXStart -= (LONG) NumAdjustDowns;
        }

        //
        // Adjust the error term down to its proper post-skip value
        //
        tmpError -= (LONGLONG)iErrorAdjustDown * NumAdjustDowns;
    }
    *pErrorTerm = (LONG)tmpError;

#endif  // X86
}// vAdjustErrorTerm()

