/*
 * Graphics Context support for generic rootless X server
 */
/*
 * Copyright (c) 2001 Greg Parker. All Rights Reserved.
 * Copyright (c) 2002-2003 Torrey T. Lyons. All Rights Reserved.
 * Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name(s) of the above copyright
 * holders shall not be used in advertising or otherwise to promote the sale,
 * use or other dealings in this Software without prior written authorization.
 */
/* $XFree86: xc/programs/Xserver/miext/rootless/rootlessGC.c,v 1.1 2003/04/15 01:05:44 torrey Exp $ */

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif

#include "mi.h"
#include "scrnintstr.h"
#include "gcstruct.h"
#include "pixmapstr.h"
#include "windowstr.h"
#include "dixfontstr.h"
#include "mivalidate.h"
#include "fb.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "rootlessCommon.h"


// GC functions
static void RootlessValidateGC(GCPtr pGC, unsigned long changes,
                               DrawablePtr pDrawable);
static void RootlessChangeGC(GCPtr pGC, unsigned long mask);
static void RootlessCopyGC(GCPtr pGCSrc, unsigned long mask, GCPtr pGCDst);
static void RootlessDestroyGC(GCPtr pGC);
static void RootlessChangeClip(GCPtr pGC, int type, pointer pvalue,
                               int nrects);
static void RootlessDestroyClip(GCPtr pGC);
static void RootlessCopyClip(GCPtr pgcDst, GCPtr pgcSrc);

GCFuncs rootlessGCFuncs = {
    RootlessValidateGC,
    RootlessChangeGC,
    RootlessCopyGC,
    RootlessDestroyGC,
    RootlessChangeClip,
    RootlessDestroyClip,
    RootlessCopyClip,
};

// GC operations
static void RootlessFillSpans();
static void RootlessSetSpans();
static void RootlessPutImage();
static RegionPtr RootlessCopyArea();
static RegionPtr RootlessCopyPlane();
static void RootlessPolyPoint();
static void RootlessPolylines();
static void RootlessPolySegment();
static void RootlessPolyRectangle();
static void RootlessPolyArc();
static void RootlessFillPolygon();
static void RootlessPolyFillRect();
static void RootlessPolyFillArc();
static int RootlessPolyText8();
static int RootlessPolyText16();
static void RootlessImageText8();
static void RootlessImageText16();
static void RootlessImageGlyphBlt();
static void RootlessPolyGlyphBlt();
static void RootlessPushPixels();

static GCOps rootlessGCOps = {
    RootlessFillSpans,
    RootlessSetSpans,
    RootlessPutImage,
    RootlessCopyArea,
    RootlessCopyPlane,
    RootlessPolyPoint,
    RootlessPolylines,
    RootlessPolySegment,
    RootlessPolyRectangle,
    RootlessPolyArc,
    RootlessFillPolygon,
    RootlessPolyFillRect,
    RootlessPolyFillArc,
    RootlessPolyText8,
    RootlessPolyText16,
    RootlessImageText8,
    RootlessImageText16,
    RootlessImageGlyphBlt,
    RootlessPolyGlyphBlt,
    RootlessPushPixels
#ifdef NEED_LINEHELPER
    , NULL
#endif
};

/*
   There are two issues we must contend with when drawing. These are
   controlled with ROOTLESS_PROTECT_ALPHA and ROOTLESS_ACCEL.

   If ROOTLESS_PROTECT_ALPHA is set, we have to make sure that the alpha
   channel of the on screen windows is always opaque. fb makes this harder
   than it would otherwise be by noticing that a planemask of 0x00ffffff
   includes all bits when depth==24, and so it "optimizes" the planemask to
   0xffffffff. We work around this by temporarily setting depth=bpp while
   changing the GC.

   So the normal situation (in 32 bit mode) is that the planemask is
   0x00ffffff and thus fb leaves the alpha channel alone. The rootless
   implementation is responsible for setting the alpha channel opaque
   initially.

   Unfortunately drawing with a planemask that doesn't have all bits set
   normally causes fb to fall off its fastest paths when blitting and
   filling.  So we try to recognize when we can relax the planemask back to
   0xffffffff, and do that for the duration of the drawing operation,
   setting the alpha channel in fg/bg pixels to opaque at the same time. We
   can do this when drawing op is GXcopy. We can also do this when copying
   from another window since its alpha channel must also be opaque.

   The other issue to consider is that the rootless implementation may
   provide accelerated drawing functions if ROOTLESS_ACCEL is set. For some
   drawing primitives we swap in rootless acceleration functions, which use
   the accelerated drawing functions where possible.

   Where both alpha protection and acceleration is used, it is even a bigger
   win to relax the planemask to all ones because most accelerated drawing
   functions can only be used in this case. However, even if we can't set
   the planemask to all ones, we can still use the accelerated
   CompositePixels function for GXcopy if it is a forward copy. This is
   mainly intended for copying from pixmaps to windows. The CompositePixels
   operation used sets alpha to 0xFF during the copy.

   The three macros below are used to implement this, potentially accelerated
   drawing ops look something like this:

   OP {
       GC_SAVE(gc);
       GCOP_UNWRAP(gc);

       ...

       if (canAccelxxx(..) && otherwise-suitable)
            GC_UNSET_PM(gc, dst);

       gc->funcs->OP(gc, ...);

       GC_RESTORE(gc, dst);
       GCOP_WRAP(gc);
   }

 */

#define GC_SAVE(pGC) 				\
    unsigned long _save_fg = (pGC)->fgPixel;	\
    unsigned long _save_bg = (pGC)->bgPixel;	\
    unsigned long _save_pm = (pGC)->planemask;	\
    Bool _changed = FALSE

#define GC_RESTORE(pGC, pDraw)					\
    do {							\
        if (_changed) {						\
            unsigned int depth = (pDraw)->depth;		\
            (pGC)->fgPixel = _save_fg;				\
            (pGC)->bgPixel = _save_bg;				\
            (pGC)->planemask = _save_pm;			\
            (pDraw)->depth = (pDraw)->bitsPerPixel;		\
            VALIDATE_GC(pGC, GCForeground | GCBackground |	\
                        GCPlaneMask, pDraw);			\
            (pDraw)->depth = depth;				\
        }							\
    } while (0)

#define GC_UNSET_PM(pGC, pDraw)						\
    do {								\
        unsigned int mask = RootlessAlphaMask ((pDraw)->bitsPerPixel);	\
        if (((pGC)->planemask & mask) != mask) {			\
            unsigned int depth = (pDraw)->depth;			\
            (pGC)->fgPixel |= mask;					\
            (pGC)->bgPixel |= mask;					\
            (pGC)->planemask |= mask;					\
            (pDraw)->depth = (pDraw)->bitsPerPixel;			\
            VALIDATE_GC(pGC, GCForeground |				\
                        GCBackground | GCPlaneMask, pDraw);		\
            (pDraw)->depth = depth;					\
            _changed = TRUE;						\
        }								\
    } while (0)

#define VALIDATE_GC(pGC, changes, pDrawable)				\
    do {								\
        pGC->funcs->ValidateGC(pGC, changes, pDrawable);		\
        if (((WindowPtr) pDrawable)->viewable) {			\
            gcrec->originalOps = pGC->ops;				\
        }								\
    } while(0)

static RootlessWindowRec *
canAccelBlit (DrawablePtr pDraw, GCPtr pGC)
{
    WindowPtr pTop;
    RootlessWindowRec *winRec;
    unsigned int pm;

    if (pGC->alu != GXcopy)
        return NULL;

    if (pDraw->type != DRAWABLE_WINDOW)
        return NULL;

    pm = ~RootlessAlphaMask(pDraw->bitsPerPixel);
    if ((pGC->planemask & pm) != pm)
        return NULL;

    pTop = TopLevelParent((WindowPtr) pDraw);
    if (pTop == NULL)
        return NULL;

    winRec = WINREC(pTop);
    if (winRec == NULL)
        return NULL;

    return winRec;
}

static inline RootlessWindowRec *
canAccelFill(DrawablePtr pDraw, GCPtr pGC)
{
    if (pGC->fillStyle != FillSolid)
        return NULL;

    return canAccelBlit(pDraw, pGC);
}

static unsigned int
boxBytes(DrawablePtr pDraw, BoxRec *box)
{
    unsigned int pixels;

    pixels = (box->x2 - box->x1) * (box->y2 - box->y1);

    return pixels * (pDraw->bitsPerPixel >> 3);
}


/*
 * Screen function to create a graphics context
 */
Bool
RootlessCreateGC(GCPtr pGC)
{
    RootlessGCRec *gcrec;
    RootlessScreenRec *s;
    Bool result;

    SCREEN_UNWRAP(pGC->pScreen, CreateGC);
    s = (RootlessScreenRec *) pGC->pScreen->
            devPrivates[rootlessScreenPrivateIndex].ptr;
    result = s->CreateGC(pGC);

    gcrec = (RootlessGCRec *) pGC->devPrivates[rootlessGCPrivateIndex].ptr;
    gcrec->originalOps = NULL; // don't wrap ops yet
    gcrec->originalFuncs = pGC->funcs;
    pGC->funcs = &rootlessGCFuncs;

    SCREEN_WRAP(pGC->pScreen, CreateGC);
    return result;
}


/*
 * GC funcs
 *
 * These wrap lower level GC funcs.
 * ValidateGC wraps the GC ops iff dest is viewable.
 * All the others just unwrap and call.
 */

// GCFUNC_UNRAP assumes funcs have been wrapped and 
// does not assume ops have been wrapped
#define GCFUNC_UNWRAP(pGC) \
    RootlessGCRec *gcrec = (RootlessGCRec *) \
        (pGC)->devPrivates[rootlessGCPrivateIndex].ptr; \
    (pGC)->funcs = gcrec->originalFuncs; \
    if (gcrec->originalOps) { \
        (pGC)->ops = gcrec->originalOps; \
}

#define GCFUNC_WRAP(pGC) \
    gcrec->originalFuncs = (pGC)->funcs; \
    (pGC)->funcs = &rootlessGCFuncs; \
    if (gcrec->originalOps) { \
        gcrec->originalOps = (pGC)->ops; \
        (pGC)->ops = &rootlessGCOps; \
}


static void
RootlessValidateGC(GCPtr pGC, unsigned long changes, DrawablePtr pDrawable)
{
    GCFUNC_UNWRAP(pGC);

    gcrec->originalOps = NULL;

    if (pDrawable->type == DRAWABLE_WINDOW)
    {
#ifdef ROOTLESS_PROTECT_ALPHA
        unsigned int depth = pDrawable->depth;

        // We force a planemask so fb doesn't overwrite the alpha channel.
        // Left to its own devices, fb will optimize away the planemask.
        pDrawable->depth = pDrawable->bitsPerPixel;
        pGC->planemask &= ~RootlessAlphaMask(pDrawable->bitsPerPixel);
        VALIDATE_GC(pGC, changes | GCPlaneMask, pDrawable);
        pDrawable->depth = depth;
#else
        VALIDATE_GC(pGC, changes, pDrawable);
#endif
    } else {
        pGC->funcs->ValidateGC(pGC, changes, pDrawable);
    }

    GCFUNC_WRAP(pGC);
}

static void RootlessChangeGC(GCPtr pGC, unsigned long mask)
{
    GCFUNC_UNWRAP(pGC);
    pGC->funcs->ChangeGC(pGC, mask);
    GCFUNC_WRAP(pGC);
}

static void RootlessCopyGC(GCPtr pGCSrc, unsigned long mask, GCPtr pGCDst)
{
    GCFUNC_UNWRAP(pGCDst);
    pGCDst->funcs->CopyGC(pGCSrc, mask, pGCDst);
    GCFUNC_WRAP(pGCDst);
}

static void RootlessDestroyGC(GCPtr pGC)
{
    GCFUNC_UNWRAP(pGC);
    pGC->funcs->DestroyGC(pGC);
    GCFUNC_WRAP(pGC);
}

static void RootlessChangeClip(GCPtr pGC, int type, pointer pvalue, int nrects)
{
    GCFUNC_UNWRAP(pGC);
    pGC->funcs->ChangeClip(pGC, type, pvalue, nrects);
    GCFUNC_WRAP(pGC);
}

static void RootlessDestroyClip(GCPtr pGC)
{
    GCFUNC_UNWRAP(pGC);
    pGC->funcs->DestroyClip(pGC);
    GCFUNC_WRAP(pGC);
}

static void RootlessCopyClip(GCPtr pgcDst, GCPtr pgcSrc)
{
    GCFUNC_UNWRAP(pgcDst);
    pgcDst->funcs->CopyClip(pgcDst, pgcSrc);
    GCFUNC_WRAP(pgcDst);
}


/*
 * GC ops
 *
 * We can't use shadowfb because shadowfb assumes one pixmap
 * and our root window is a special case.
 * However, much of this code is copied from shadowfb.
 */

// assumes both funcs and ops are wrapped
#define GCOP_UNWRAP(pGC) \
    RootlessGCRec *gcrec = (RootlessGCRec *) \
        (pGC)->devPrivates[rootlessGCPrivateIndex].ptr; \
    GCFuncs *saveFuncs = pGC->funcs; \
    (pGC)->funcs = gcrec->originalFuncs; \
    (pGC)->ops = gcrec->originalOps;

#define GCOP_WRAP(pGC) \
    gcrec->originalOps = (pGC)->ops; \
    (pGC)->funcs = saveFuncs; \
    (pGC)->ops = &rootlessGCOps;

/* Turn drawing on the root into a no-op */
#define GC_IS_ROOT(pDst) ((pDst)->type == DRAWABLE_WINDOW \
                            && IsRoot ((WindowPtr) (pDst)))

#define GC_SKIP_ROOT(pDst)			\
    do {					\
        if (GC_IS_ROOT (pDst))			\
            return;				\
    } while (0)


static void
RootlessFillSpans(DrawablePtr dst, GCPtr pGC, int nInit,
                  DDXPointPtr pptInit, int *pwidthInit, int sorted)
{
    GC_SAVE(pGC);
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("fill spans start ");

    if (nInit <= 0) {
        pGC->ops->FillSpans(dst, pGC, nInit, pptInit, pwidthInit, sorted);
    } else {
        DDXPointPtr ppt = pptInit;
        int *pwidth = pwidthInit;
        int i = nInit;
        BoxRec box;

        box.x1 = ppt->x;
        box.x2 = box.x1 + *pwidth;
        box.y2 = box.y1 = ppt->y;

        while (--i) {
            ppt++;
            pwidth++;
            if (box.x1 > ppt->x)
                box.x1 = ppt->x;
            if (box.x2 < (ppt->x + *pwidth))
                box.x2 = ppt->x + *pwidth;
            if (box.y1 > ppt->y)
                box.y1 = ppt->y;
            else if (box.y2 < ppt->y)
                box.y2 = ppt->y;
        }

        box.y2++;

        RootlessStartDrawing((WindowPtr) dst);

        if (canAccelFill(dst, pGC) &&
            boxBytes(dst, &box) >= rootless_FillBytes_threshold)
        {
            GC_UNSET_PM(pGC, dst);
        }

        pGC->ops->FillSpans(dst, pGC, nInit, pptInit, pwidthInit, sorted);

        TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    }

    GC_RESTORE(pGC, dst);
    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("fill spans end\n");
}

static void
RootlessSetSpans(DrawablePtr dst, GCPtr pGC, char *pSrc,
                 DDXPointPtr pptInit, int *pwidthInit,
                 int nspans, int sorted)
{
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("set spans start ");

    if (nspans <= 0) {
        pGC->ops->SetSpans(dst, pGC, pSrc, pptInit, pwidthInit,
                           nspans, sorted);
    } else {
        DDXPointPtr ppt = pptInit;
        int *pwidth = pwidthInit;
        int i = nspans;
        BoxRec box;

        box.x1 = ppt->x;
        box.x2 = box.x1 + *pwidth;
        box.y2 = box.y1 = ppt->y;

        while (--i) {
            ppt++;
            pwidth++;
            if (box.x1 > ppt->x)
                box.x1 = ppt->x;
            if (box.x2 < (ppt->x + *pwidth))
                box.x2 = ppt->x + *pwidth;
            if (box.y1 > ppt->y)
                box.y1 = ppt->y;
            else if (box.y2 < ppt->y)
                box.y2 = ppt->y;
        }

        box.y2++;

        RootlessStartDrawing((WindowPtr) dst);
        pGC->ops->SetSpans(dst, pGC, pSrc, pptInit, pwidthInit,
                           nspans, sorted);

        TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    }
    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("set spans end\n");
}

static void
RootlessPutImage(DrawablePtr dst, GCPtr pGC,
                 int depth, int x, int y, int w, int h,
                 int leftPad, int format, char *pBits)
{
    BoxRec box;

    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("put image start ");

    RootlessStartDrawing((WindowPtr) dst);
    pGC->ops->PutImage(dst, pGC, depth, x,y,w,h, leftPad, format, pBits);

    box.x1 = x + dst->x;
    box.x2 = box.x1 + w;
    box.y1 = y + dst->y;
    box.y2 = box.y1 + h;

    TRIM_BOX(box, pGC);
    if (BOX_NOT_EMPTY(box))
        RootlessDamageBox ((WindowPtr) dst, &box);

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("put image end\n");
}

/* changed area is *dest* rect */
static RegionPtr
RootlessCopyArea(DrawablePtr pSrc, DrawablePtr dst, GCPtr pGC,
                 int srcx, int srcy, int w, int h,
                 int dstx, int dsty)
{
    RegionPtr result;
    BoxRec box;

    GC_SAVE(pGC);
    GCOP_UNWRAP(pGC);

    if (GC_IS_ROOT(dst) || GC_IS_ROOT(pSrc))
        return NULL;			/* nothing exposed */

    RL_DEBUG_MSG("copy area start (src 0x%x, dst 0x%x)", pSrc, dst);

    if (pSrc->type == DRAWABLE_WINDOW && IsFramedWindow((WindowPtr)pSrc)) {
        unsigned int bytes;

        /* If both source and dest are windows, and we're doing
           a simple copy operation, we can remove the alpha-protecting
           planemask (since source has opaque alpha as well) */

        bytes = w * h * (pSrc->depth >> 3);

        if (bytes >= rootless_CopyBytes_threshold && canAccelBlit(pSrc, pGC))
        {
            GC_UNSET_PM(pGC, dst);
        }

        RootlessStartDrawing((WindowPtr) pSrc);
    }
    RootlessStartDrawing((WindowPtr) dst);
    result = pGC->ops->CopyArea(pSrc, dst, pGC, srcx, srcy, w, h, dstx, dsty);

    box.x1 = dstx + dst->x;
    box.x2 = box.x1 + w;
    box.y1 = dsty + dst->y;
    box.y2 = box.y1 + h;

    TRIM_BOX(box, pGC);
    if (BOX_NOT_EMPTY(box))
        RootlessDamageBox ((WindowPtr) dst, &box);

    GC_RESTORE(pGC, dst);
    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("copy area end\n");
    return result;
}

/* changed area is *dest* rect */
static RegionPtr RootlessCopyPlane(DrawablePtr pSrc, DrawablePtr dst,
                                   GCPtr pGC, int srcx, int srcy,
                                   int w, int h, int dstx, int dsty,
                                   unsigned long plane)
{
    RegionPtr result;
    BoxRec box;

    GCOP_UNWRAP(pGC);

    if (GC_IS_ROOT(dst) || GC_IS_ROOT(pSrc))
        return NULL;			/* nothing exposed */

    RL_DEBUG_MSG("copy plane start ");

    if (pSrc->type == DRAWABLE_WINDOW && IsFramedWindow((WindowPtr)pSrc)) {
        RootlessStartDrawing((WindowPtr) pSrc);
    }
    RootlessStartDrawing((WindowPtr) dst);
    result = pGC->ops->CopyPlane(pSrc, dst, pGC, srcx, srcy, w, h,
                                 dstx, dsty, plane);

    box.x1 = dstx + dst->x;
    box.x2 = box.x1 + w;
    box.y1 = dsty + dst->y;
    box.y2 = box.y1 + h;

    TRIM_BOX(box, pGC);
    if (BOX_NOT_EMPTY(box))
        RootlessDamageBox ((WindowPtr) dst, &box);

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("copy plane end\n");
    return result;
}

// Options for size of changed area:
//  0 = box per point
//  1 = big box around all points
//  2 = accumulate point in 20 pixel radius
#define ROOTLESS_CHANGED_AREA 1
#define abs(a) ((a) > 0 ? (a) : -(a))

/* changed area is box around all points */
static void RootlessPolyPoint(DrawablePtr dst, GCPtr pGC,
                              int mode, int npt, DDXPointPtr pptInit)
{
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("polypoint start ");

    RootlessStartDrawing((WindowPtr) dst);
    pGC->ops->PolyPoint(dst, pGC, mode, npt, pptInit);

    if (npt > 0) {
#if ROOTLESS_CHANGED_AREA==0
        // box per point
        BoxRec box;

        while (npt) {
            box.x1 = pptInit->x;
            box.y1 = pptInit->y;
            box.x2 = box.x1 + 1;
            box.y2 = box.y1 + 1;

            TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
            if (BOX_NOT_EMPTY(box))
                RootlessDamageBox ((WindowPtr) dst, &box);

            npt--;
            pptInit++;
        }

#elif ROOTLESS_CHANGED_AREA==1
        // one big box
        BoxRec box;

        box.x2 = box.x1 = pptInit->x;
        box.y2 = box.y1 = pptInit->y;
        while (--npt) {
            pptInit++;
            if (box.x1 > pptInit->x)
                box.x1 = pptInit->x;
            else if (box.x2 < pptInit->x)
                box.x2 = pptInit->x;
            if (box.y1 > pptInit->y)
                box.y1 = pptInit->y;
            else if (box.y2 < pptInit->y)
                box.y2 = pptInit->y;
        }

        box.x2++;
        box.y2++;

        TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);

#elif ROOTLESS_CHANGED_AREA==2
        // clever(?) method: accumulate point in 20-pixel radius
        BoxRec box;
        int firstx, firsty;

        box.x2 = box.x1 = firstx = pptInit->x;
        box.y2 = box.y1 = firsty = pptInit->y;
        while (--npt) {
            pptInit++;
            if (abs(pptInit->x - firstx) > 20 ||
                abs(pptInit->y - firsty) > 20) {
                box.x2++;
                box.y2++;
                TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
                if (BOX_NOT_EMPTY(box))
                    RootlessDamageBox ((WindowPtr) dst, &box);
                box.x2 = box.x1 = firstx = pptInit->x;
                box.y2 = box.y1 = firsty = pptInit->y;
            } else {
                if (box.x1 > pptInit->x) box.x1 = pptInit->x;
                else if (box.x2 < pptInit->x) box.x2 = pptInit->x;
                if (box.y1 > pptInit->y) box.y1 = pptInit->y;
                else if (box.y2 < pptInit->y) box.y2 = pptInit->y;
            }
        }
        box.x2++;
        box.y2++;
        TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox((WindowPtr) dst, &box);
#endif  /* ROOTLESS_CHANGED_AREA */
    }

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("polypoint end\n");
}

#undef ROOTLESS_CHANGED_AREA

/* changed area is box around each line */
static void RootlessPolylines(DrawablePtr dst, GCPtr pGC,
                              int mode, int npt, DDXPointPtr pptInit)
{
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("poly lines start ");

    RootlessStartDrawing((WindowPtr) dst);
    pGC->ops->Polylines(dst, pGC, mode, npt, pptInit);

    if (npt > 0) {
        BoxRec box;
        int extra = pGC->lineWidth >> 1;

        box.x2 = box.x1 = pptInit->x;
        box.y2 = box.y1 = pptInit->y;

        if (npt > 1) {
            if (pGC->joinStyle == JoinMiter)
                extra = 6 * pGC->lineWidth;
            else if (pGC->capStyle == CapProjecting)
                extra = pGC->lineWidth;
        }

        if (mode == CoordModePrevious) {
            int x = box.x1;
            int y = box.y1;

            while (--npt) {
                pptInit++;
                x += pptInit->x;
                y += pptInit->y;
                if (box.x1 > x)
                    box.x1 = x;
                else if (box.x2 < x)
                    box.x2 = x;
                if (box.y1 > y)
                    box.y1 = y;
                else if (box.y2 < y)
                    box.y2 = y;
            }
        } else {
            while (--npt) {
                pptInit++;
                if (box.x1 > pptInit->x)
                    box.x1 = pptInit->x;
                else if (box.x2 < pptInit->x)
                    box.x2 = pptInit->x;
                if (box.y1 > pptInit->y)
                    box.y1 = pptInit->y;
                else if (box.y2 < pptInit->y)
                    box.y2 = pptInit->y;
            }
        }

        box.x2++;
        box.y2++;

        if (extra) {
            box.x1 -= extra;
            box.x2 += extra;
            box.y1 -= extra;
            box.y2 += extra;
        }

        TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    }

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("poly lines end\n");
}

/* changed area is box around each line segment */
static void RootlessPolySegment(DrawablePtr dst, GCPtr pGC,
                                int nseg, xSegment *pSeg)
{
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("poly segment start (win 0x%x)", dst);

    RootlessStartDrawing((WindowPtr) dst);
    pGC->ops->PolySegment(dst, pGC, nseg, pSeg);

    if (nseg > 0) {
        BoxRec box;
        int extra = pGC->lineWidth;

        if (pGC->capStyle != CapProjecting)
        extra >>= 1;

        if (pSeg->x2 > pSeg->x1) {
            box.x1 = pSeg->x1;
            box.x2 = pSeg->x2;
        } else {
            box.x2 = pSeg->x1;
            box.x1 = pSeg->x2;
        }

        if (pSeg->y2 > pSeg->y1) {
            box.y1 = pSeg->y1;
            box.y2 = pSeg->y2;
        } else {
            box.y2 = pSeg->y1;
            box.y1 = pSeg->y2;
        }

        while (--nseg) {
            pSeg++;
            if (pSeg->x2 > pSeg->x1) {
                if (pSeg->x1 < box.x1) box.x1 = pSeg->x1;
                if (pSeg->x2 > box.x2) box.x2 = pSeg->x2;
            } else {
                if (pSeg->x2 < box.x1) box.x1 = pSeg->x2;
                if (pSeg->x1 > box.x2) box.x2 = pSeg->x1;
            }
            if (pSeg->y2 > pSeg->y1) {
                if (pSeg->y1 < box.y1) box.y1 = pSeg->y1;
                if (pSeg->y2 > box.y2) box.y2 = pSeg->y2;
            } else {
                if (pSeg->y2 < box.y1) box.y1 = pSeg->y2;
                if (pSeg->y1 > box.y2) box.y2 = pSeg->y1;
            }
        }

        box.x2++;
        box.y2++;

        if (extra) {
            box.x1 -= extra;
            box.x2 += extra;
            box.y1 -= extra;
            box.y2 += extra;
        }

        TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    }

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("poly segment end\n");
}

/* changed area is box around each line (not entire rects) */
static void RootlessPolyRectangle(DrawablePtr dst, GCPtr pGC,
                                  int nRects, xRectangle *pRects)
{
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("poly rectangle start ");

    RootlessStartDrawing((WindowPtr) dst);
    pGC->ops->PolyRectangle(dst, pGC, nRects, pRects);

    if (nRects > 0) {
        BoxRec box;
        int offset1, offset2, offset3;

        offset2 = pGC->lineWidth;
        if (!offset2) offset2 = 1;
        offset1 = offset2 >> 1;
        offset3 = offset2 - offset1;

        while (nRects--) {
            box.x1 = pRects->x - offset1;
            box.y1 = pRects->y - offset1;
            box.x2 = box.x1 + pRects->width + offset2;
            box.y2 = box.y1 + offset2;
            TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
            if (BOX_NOT_EMPTY(box))
                RootlessDamageBox ((WindowPtr) dst, &box);

            box.x1 = pRects->x - offset1;
            box.y1 = pRects->y + offset3;
            box.x2 = box.x1 + offset2;
            box.y2 = box.y1 + pRects->height - offset2;
            TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
            if (BOX_NOT_EMPTY(box))
                RootlessDamageBox ((WindowPtr) dst, &box);

            box.x1 = pRects->x + pRects->width - offset1;
            box.y1 = pRects->y + offset3;
            box.x2 = box.x1 + offset2;
            box.y2 = box.y1 + pRects->height - offset2;
            TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
            if (BOX_NOT_EMPTY(box))
                RootlessDamageBox ((WindowPtr) dst, &box);

            box.x1 = pRects->x - offset1;
            box.y1 = pRects->y + pRects->height - offset1;
            box.x2 = box.x1 + pRects->width + offset2;
            box.y2 = box.y1 + offset2;
            TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
            if (BOX_NOT_EMPTY(box))
                RootlessDamageBox ((WindowPtr) dst, &box);

            pRects++;
        }
    }

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("poly rectangle end\n");
}


/* changed area is box around each arc (assumes all arcs are 360 degrees) */
static void RootlessPolyArc(DrawablePtr dst, GCPtr pGC, int narcs, xArc *parcs)
{
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("poly arc start ");

    RootlessStartDrawing((WindowPtr) dst);
    pGC->ops->PolyArc(dst, pGC, narcs, parcs);

    if (narcs > 0) {
        int extra = pGC->lineWidth >> 1;
        BoxRec box;

        box.x1 = parcs->x;
        box.x2 = box.x1 + parcs->width;
        box.y1 = parcs->y;
        box.y2 = box.y1 + parcs->height;

        /* should I break these up instead ? */

        while (--narcs) {
            parcs++;
            if (box.x1 > parcs->x)
                box.x1 = parcs->x;
            if (box.x2 < (parcs->x + parcs->width))
                box.x2 = parcs->x + parcs->width;
            if (box.y1 > parcs->y)
                box.y1 = parcs->y;
            if (box.y2 < (parcs->y + parcs->height))
                box.y2 = parcs->y + parcs->height;
        }

        if (extra) {
            box.x1 -= extra;
            box.x2 += extra;
            box.y1 -= extra;
            box.y2 += extra;
        }

        box.x2++;
        box.y2++;

        TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    }

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("poly arc end\n");
}


/* changed area is box around each poly */
static void RootlessFillPolygon(DrawablePtr dst, GCPtr pGC,
                                int shape, int mode, int count,
                                DDXPointPtr pptInit)
{
    GC_SAVE(pGC);
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("fill poly start (win 0x%x, fillStyle 0x%x)", dst,
                 pGC->fillStyle);

    if (count <= 2) {
        pGC->ops->FillPolygon(dst, pGC, shape, mode, count, pptInit);
    } else {
        DDXPointPtr ppt = pptInit;
        int i = count;
        BoxRec box;

        box.x2 = box.x1 = ppt->x;
        box.y2 = box.y1 = ppt->y;

        if (mode != CoordModeOrigin) {
            int x = box.x1;
            int y = box.y1;

            while (--i) {
                ppt++;
                x += ppt->x;
                y += ppt->y;
                if (box.x1 > x)
                    box.x1 = x;
                else if (box.x2 < x)
                    box.x2 = x;
                if (box.y1 > y)
                    box.y1 = y;
                else if (box.y2 < y)
                    box.y2 = y;
            }
        } else {
            while (--i) {
                ppt++;
                if (box.x1 > ppt->x)
                    box.x1 = ppt->x;
                else if (box.x2 < ppt->x)
                    box.x2 = ppt->x;
                if (box.y1 > ppt->y)
                    box.y1 = ppt->y;
                else if (box.y2 < ppt->y)
                    box.y2 = ppt->y;
            }
        }

        box.x2++;
        box.y2++;

        RootlessStartDrawing((WindowPtr) dst);

        if (canAccelFill(dst, pGC) &&
            boxBytes(dst, &box) >= rootless_FillBytes_threshold)
        {
            GC_UNSET_PM(pGC, dst);
        }

        pGC->ops->FillPolygon(dst, pGC, shape, mode, count, pptInit);

        TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    }

    GC_RESTORE(pGC, dst);
    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("fill poly end\n");
}

/* changed area is the rects */
static void RootlessPolyFillRect(DrawablePtr dst, GCPtr pGC,
                                 int nRectsInit, xRectangle *pRectsInit)
{
    GC_SAVE(pGC);
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("fill rect start (win 0x%x, fillStyle 0x%x)", dst,
                 pGC->fillStyle);

    if (nRectsInit <= 0) {
        pGC->ops->PolyFillRect(dst, pGC, nRectsInit, pRectsInit);
    } else {
        BoxRec box;
        xRectangle *pRects = pRectsInit;
        int nRects = nRectsInit;

        box.x1 = pRects->x;
        box.x2 = box.x1 + pRects->width;
        box.y1 = pRects->y;
        box.y2 = box.y1 + pRects->height;

        while (--nRects) {
            pRects++;
            if (box.x1 > pRects->x)
                box.x1 = pRects->x;
            if (box.x2 < (pRects->x + pRects->width))
                box.x2 = pRects->x + pRects->width;
            if (box.y1 > pRects->y)
                box.y1 = pRects->y;
            if (box.y2 < (pRects->y + pRects->height))
                box.y2 = pRects->y + pRects->height;
        }

        RootlessStartDrawing((WindowPtr) dst);
 
        if (canAccelFill(dst, pGC) &&
            boxBytes(dst, &box) >= rootless_FillBytes_threshold)
        {
            GC_UNSET_PM(pGC, dst);
        }

       pGC->ops->PolyFillRect(dst, pGC, nRectsInit, pRectsInit);

        TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    }

    GC_RESTORE(pGC, dst);
    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("fill rect end\n");
}


/* changed area is box around each arc (assuming arcs are all 360 degrees) */
static void RootlessPolyFillArc(DrawablePtr dst, GCPtr pGC,
                                int narcsInit, xArc *parcsInit)
{
    GC_SAVE(pGC);
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("fill arc start ");

    if (narcsInit > 0) {
        BoxRec box;
        int narcs = narcsInit;
        xArc *parcs = parcsInit;

        box.x1 = parcs->x;
        box.x2 = box.x1 + parcs->width;
        box.y1 = parcs->y;
        box.y2 = box.y1 + parcs->height;

        /* should I break these up instead ? */

        while (--narcs) {
            parcs++;
            if (box.x1 > parcs->x)
                box.x1 = parcs->x;
            if (box.x2 < (parcs->x + parcs->width))
                box.x2 = parcs->x + parcs->width;
            if (box.y1 > parcs->y)
                box.y1 = parcs->y;
            if (box.y2 < (parcs->y + parcs->height))
                box.y2 = parcs->y + parcs->height;
        }

        RootlessStartDrawing((WindowPtr) dst);

        if (canAccelFill(dst, pGC) &&
            boxBytes(dst, &box) >= rootless_FillBytes_threshold)
        {
            GC_UNSET_PM(pGC, dst);
        }

        pGC->ops->PolyFillArc(dst, pGC, narcsInit, parcsInit);

        TRIM_AND_TRANSLATE_BOX(box, dst, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    } else {
        pGC->ops->PolyFillArc(dst, pGC, narcsInit, parcsInit);
    }

    GC_RESTORE(pGC, dst);
    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("fill arc end\n");
}


static void RootlessImageText8(DrawablePtr dst, GCPtr pGC,
                               int x, int y, int count, char *chars)
{
    GC_SAVE(pGC);
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("imagetext8 start ");

    if (count > 0) {
        int top, bot, Min, Max;
        BoxRec box;

        top = max(FONTMAXBOUNDS(pGC->font, ascent), FONTASCENT(pGC->font));
        bot = max(FONTMAXBOUNDS(pGC->font, descent), FONTDESCENT(pGC->font));

        Min = count * FONTMINBOUNDS(pGC->font, characterWidth);
        if (Min > 0) Min = 0;
        Max = count * FONTMAXBOUNDS(pGC->font, characterWidth);
        if (Max < 0) Max = 0;

        /* ugh */
        box.x1 = dst->x + x + Min +
        FONTMINBOUNDS(pGC->font, leftSideBearing);
        box.x2 = dst->x + x + Max +
        FONTMAXBOUNDS(pGC->font, rightSideBearing);

        box.y1 = dst->y + y - top;
        box.y2 = dst->y + y + bot;

        RootlessStartDrawing((WindowPtr) dst);

        if (canAccelFill(dst, pGC) &&
            boxBytes(dst, &box) >= rootless_FillBytes_threshold)
        {
            GC_UNSET_PM(pGC, dst);
        }

        pGC->ops->ImageText8(dst, pGC, x, y, count, chars);

        TRIM_BOX(box, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    } else {
        pGC->ops->ImageText8(dst, pGC, x, y, count, chars);
    }

    GC_RESTORE(pGC, dst);
    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("imagetext8 end\n");
}

static int RootlessPolyText8(DrawablePtr dst, GCPtr pGC,
                             int x, int y, int count, char *chars)
{
    int width; // the result, sorta

    GCOP_UNWRAP(pGC);

    if (GC_IS_ROOT(dst))
        return 0;

    RL_DEBUG_MSG("polytext8 start ");

    RootlessStartDrawing((WindowPtr) dst);
    width = pGC->ops->PolyText8(dst, pGC, x, y, count, chars);
    width -= x;

    if (width > 0) {
        BoxRec box;

        /* ugh */
        box.x1 = dst->x + x + FONTMINBOUNDS(pGC->font, leftSideBearing);
        box.x2 = dst->x + x + FONTMAXBOUNDS(pGC->font, rightSideBearing);

        if (count > 1) {
            if (width > 0) box.x2 += width;
            else box.x1 += width;
        }

        box.y1 = dst->y + y - FONTMAXBOUNDS(pGC->font, ascent);
        box.y2 = dst->y + y + FONTMAXBOUNDS(pGC->font, descent);

        TRIM_BOX(box, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    }

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("polytext8 end\n");
    return (width + x);
}

static void RootlessImageText16(DrawablePtr dst, GCPtr pGC,
                                int x, int y, int count, unsigned short *chars)
{
    GC_SAVE(pGC);
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("imagetext16 start ");

    if (count > 0) {
        int top, bot, Min, Max;
        BoxRec box;

        top = max(FONTMAXBOUNDS(pGC->font, ascent), FONTASCENT(pGC->font));
        bot = max(FONTMAXBOUNDS(pGC->font, descent), FONTDESCENT(pGC->font));

        Min = count * FONTMINBOUNDS(pGC->font, characterWidth);
        if (Min > 0) Min = 0;
        Max = count * FONTMAXBOUNDS(pGC->font, characterWidth);
        if (Max < 0) Max = 0;

        /* ugh */
        box.x1 = dst->x + x + Min +
            FONTMINBOUNDS(pGC->font, leftSideBearing);
        box.x2 = dst->x + x + Max +
            FONTMAXBOUNDS(pGC->font, rightSideBearing);

        box.y1 = dst->y + y - top;
        box.y2 = dst->y + y + bot;

        RootlessStartDrawing((WindowPtr) dst);

        if (canAccelFill(dst, pGC) &&
            boxBytes(dst, &box) >= rootless_FillBytes_threshold)
        {
            GC_UNSET_PM(pGC, dst);
        }

        pGC->ops->ImageText16(dst, pGC, x, y, count, chars);

        TRIM_BOX(box, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    } else {
        pGC->ops->ImageText16(dst, pGC, x, y, count, chars);
    }

    GC_RESTORE(pGC, dst);
    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("imagetext16 end\n");
}

static int RootlessPolyText16(DrawablePtr dst, GCPtr pGC,
                            int x, int y, int count, unsigned short *chars)
{
    int width; // the result, sorta

    GCOP_UNWRAP(pGC);

    if (GC_IS_ROOT(dst))
        return 0;

    RL_DEBUG_MSG("polytext16 start ");

    RootlessStartDrawing((WindowPtr) dst);
    width = pGC->ops->PolyText16(dst, pGC, x, y, count, chars);
    width -= x;

    if (width > 0) {
        BoxRec box;

        /* ugh */
        box.x1 = dst->x + x + FONTMINBOUNDS(pGC->font, leftSideBearing);
        box.x2 = dst->x + x + FONTMAXBOUNDS(pGC->font, rightSideBearing);

        if (count > 1) {
            if (width > 0) box.x2 += width;
            else box.x1 += width;
        }

        box.y1 = dst->y + y - FONTMAXBOUNDS(pGC->font, ascent);
        box.y2 = dst->y + y + FONTMAXBOUNDS(pGC->font, descent);

        TRIM_BOX(box, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    }

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("polytext16 end\n");
    return width + x;
}

static void RootlessImageGlyphBlt(DrawablePtr dst, GCPtr pGC,
                                  int x, int y, unsigned int nglyphInit,
                                  CharInfoPtr *ppciInit, pointer unused)
{
    GC_SAVE(pGC);
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("imageglyph start ");

    if (nglyphInit > 0) {
        int top, bot, width = 0;
        BoxRec box;
        unsigned int nglyph = nglyphInit;
        CharInfoPtr *ppci = ppciInit;

        top = max(FONTMAXBOUNDS(pGC->font, ascent), FONTASCENT(pGC->font));
        bot = max(FONTMAXBOUNDS(pGC->font, descent), FONTDESCENT(pGC->font));

        box.x1 = ppci[0]->metrics.leftSideBearing;
        if (box.x1 > 0) box.x1 = 0;
        box.x2 = ppci[nglyph - 1]->metrics.rightSideBearing -
            ppci[nglyph - 1]->metrics.characterWidth;
        if (box.x2 < 0) box.x2 = 0;

        box.x2 += dst->x + x;
        box.x1 += dst->x + x;

        while (nglyph--) {
            width += (*ppci)->metrics.characterWidth;
            ppci++;
        }

        if (width > 0)
            box.x2 += width;
        else
            box.x1 += width;

        box.y1 = dst->y + y - top;
        box.y2 = dst->y + y + bot;

        RootlessStartDrawing((WindowPtr) dst);

        if (canAccelFill(dst, pGC) &&
            boxBytes(dst, &box) >= rootless_FillBytes_threshold)
        {
            GC_UNSET_PM(pGC, dst);
        }

        pGC->ops->ImageGlyphBlt(dst, pGC, x, y, nglyphInit, ppciInit, unused);

        TRIM_BOX(box, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    } else {
        pGC->ops->ImageGlyphBlt(dst, pGC, x, y, nglyphInit, ppciInit, unused);
    }

    GC_RESTORE(pGC, dst);
    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("imageglyph end\n");
}

static void RootlessPolyGlyphBlt(DrawablePtr dst, GCPtr pGC,
                                 int x, int y, unsigned int nglyph,
                                 CharInfoPtr *ppci, pointer pglyphBase)
{
    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("polyglyph start ");

    RootlessStartDrawing((WindowPtr) dst);
    pGC->ops->PolyGlyphBlt(dst, pGC, x, y, nglyph, ppci, pglyphBase);

    if (nglyph > 0) {
        BoxRec box;

        /* ugh */
        box.x1 = dst->x + x + ppci[0]->metrics.leftSideBearing;
        box.x2 = dst->x + x + ppci[nglyph - 1]->metrics.rightSideBearing;

        if (nglyph > 1) {
            int width = 0;

            while (--nglyph) {
                width += (*ppci)->metrics.characterWidth;
                ppci++;
            }

            if (width > 0) box.x2 += width;
            else box.x1 += width;
        }

        box.y1 = dst->y + y - FONTMAXBOUNDS(pGC->font, ascent);
        box.y2 = dst->y + y + FONTMAXBOUNDS(pGC->font, descent);

        TRIM_BOX(box, pGC);
        if (BOX_NOT_EMPTY(box))
            RootlessDamageBox ((WindowPtr) dst, &box);
    }

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("polyglyph end\n");
}


/* changed area is in dest */
static void
RootlessPushPixels(GCPtr pGC, PixmapPtr pBitMap, DrawablePtr dst,
                   int dx, int dy, int xOrg, int yOrg)
{
    BoxRec box;

    GCOP_UNWRAP(pGC);
    GC_SKIP_ROOT(dst);
    RL_DEBUG_MSG("push pixels start ");

    RootlessStartDrawing((WindowPtr) dst);
    pGC->ops->PushPixels(pGC, pBitMap, dst, dx, dy, xOrg, yOrg);

    box.x1 = xOrg + dst->x;
    box.x2 = box.x1 + dx;
    box.y1 = yOrg + dst->y;
    box.y2 = box.y1 + dy;

    TRIM_BOX(box, pGC);
    if (BOX_NOT_EMPTY(box))
        RootlessDamageBox ((WindowPtr) dst, &box);

    GCOP_WRAP(pGC);
    RL_DEBUG_MSG("push pixels end\n");
}
