Blob Blame Raw
/*
 * Graphics Context support for Mac OS X rootless X server
 */
/*
 * Copyright (c) 2001 Greg Parker. All Rights Reserved.
 * Copyright (c) 2002 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/hw/darwin/quartz/rootlessGC.c,v 1.3 2002/07/24 05:58:33 torrey Exp $ */

#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 "rootless-common.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
};


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 func wrapping
// ValidateGC wraps gcOps iff dest is viewable. All others just unwrap&call.

// GCFUN_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; \
}

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

#define GC_SKIP_ROOT(pDst)	\
    do {			\
	if (GC_IS_ROOT (pDst))	\
	    return;		\
    } while (0)
	
/* Our main problem when drawing is that we have to make sure that
   the alpha channel of the windows we're drawing in 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
   "optimizes" the pm to 0xffffffff. We work around that by temporarily
   setting depth=bpp while changing the GC.

   Anyway, so the normal situation (in 32 bit mode) is that the
   planemask is 0x00ffffff and thus fb leaves the alpha channel alone
   (and it's opaque initially, so things work out).

   But there's a problem with drawing with a planemask that doesn't
   have all bits set - it normally causes fb to fall off its fastest
   paths when blitting and filling.

   So my solution to that is to try to recognize when we can relax the
   planemask back to ~0, 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 it when copying from another window (since its alpha channel must
   also be opaque).

   Note that even when we can't set planemask to all ones, fbBlt may
   still choose altivec'd code if it's GXcopy and a forwards copy. This
   is mainly intended for copying from pixmaps to windows. The copy
   operation used sets alpha to opaque.

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

   OP {
       GC_SAVE (gc);
       GCFUNC_UNWRAP (gc);

       ...

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

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

       GC_RESTORE (gc, dst);
       GCFUNC_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)

static void
validate_gc (GCPtr pGC, unsigned long changes, DrawablePtr pDrawable)
{
    RootlessGCRec *gcrec = (RootlessGCRec *)
        (pGC)->devPrivates[rootlessGCPrivateIndex].ptr;

    pGC->funcs->ValidateGC(pGC, changes, pDrawable);

    if (((WindowPtr) pDrawable)->viewable)
    {
	gcrec->originalOps = pGC->ops;
    }
}

static RootlessWindowRec *
can_accel_blit (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 *
can_accel_fill (DrawablePtr pDraw, GCPtr pGC)
{
    if (pGC->fillStyle != FillSolid)
	return NULL;

    return can_accel_blit (pDraw, pGC);
}

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

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

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

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

    gcrec->originalOps = NULL;

    if (pDrawable->type == DRAWABLE_WINDOW)
    {
	/* Prevent fb relaxing planemask by telling it we use all
	   bits temporarily. */

	unsigned int depth = pDrawable->depth;
	pDrawable->depth = pDrawable->bitsPerPixel;
	pGC->planemask &= ~RootlessAlphaMask (pDrawable->bitsPerPixel);
	validate_gc (pGC, changes | GCPlaneMask, pDrawable);
	pDrawable->depth = depth;
    }
    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.
// So 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;


static void
RootlessFillSpans(DrawablePtr dst, GCPtr pGC, int nInit,
                  DDXPointPtr pptInit, int *pwidthInit, int sorted)
{
    GC_SAVE (pGC);

    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("fill spans start\n");

    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++;
            pwidthInit++;
            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 (can_accel_fill (dst, pGC)
	    && box_bytes (dst, &box) >= xp_fill_bytes_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);

	RootlessFinishedDrawing ((WindowPtr) dst);
    }

    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)
{
    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("set spans start\n");

    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);

	RootlessFinishedDrawing ((WindowPtr) dst);
    }
    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;

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

    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);

    RootlessFinishedDrawing ((WindowPtr) dst);

    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);
    Bool src_drawing = FALSE;

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

    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("copy area start (src 0x%x, dst 0x%x)\n", 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 >= xp_copy_bytes_threshold && can_accel_blit (pSrc, pGC))
	{
	    GC_UNSET_PM (pGC, dst);
	}

        RootlessStartDrawing((WindowPtr) pSrc);
	src_drawing = TRUE;
    }
    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);

    RootlessFinishedDrawing ((WindowPtr) dst);
    if (src_drawing)
	RootlessFinishedDrawing ((WindowPtr) pSrc);

    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;
    Bool src_drawing = FALSE;

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

    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("copy plane start\n");

    if (pSrc->type == DRAWABLE_WINDOW && IsFramedWindow((WindowPtr)pSrc)) {
        RootlessStartDrawing((WindowPtr) pSrc);
	src_drawing = TRUE;
    }
    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);

    RootlessFinishedDrawing ((WindowPtr) dst);
    if (src_drawing)
	RootlessFinishedDrawing ((WindowPtr) pSrc);

    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)
{
    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("polypoint start\n");

    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 */
    }

    RootlessFinishedDrawing ((WindowPtr) dst);

    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)
{
    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("poly lines start\n");

    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);
    }

    RootlessFinishedDrawing ((WindowPtr) dst);

    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)
{
    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("poly segment start (win 0x%x)\n", 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);
    }

    RootlessFinishedDrawing ((WindowPtr) dst);

    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)
{
    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("poly rectangle start\n");

    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++;
        }
    }

    RootlessFinishedDrawing ((WindowPtr) dst);

    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)
{
    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("poly arc start\n");

    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);
    }

    RootlessFinishedDrawing ((WindowPtr) dst);

    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);

    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("fill poly start (win 0x%x, fillStyle 0x%x)\n", 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 (can_accel_fill (dst, pGC)
	    && box_bytes (dst, &box) >= xp_fill_bytes_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);

	RootlessFinishedDrawing ((WindowPtr) dst);
    }

    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);

    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("fill rect start (win 0x%x, fillStyle 0x%x)\n", 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;
        }

        /* cfb messes with the pRectsInit so we have to do our
	   calculations first */

	RootlessStartDrawing((WindowPtr) dst);

	if (can_accel_fill (dst, pGC)
	    && box_bytes (dst, &box) >= xp_fill_bytes_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);

	RootlessFinishedDrawing ((WindowPtr) dst);
    }

    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 narcs, xArc *parcs)
{
    GC_SAVE (pGC);

    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("fill arc start\n");

    if (narcs > 0) {
        BoxRec box;
	int i;

        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 ? */

	for (i = 0; i < narcs; i++)
	{
            if(box.x1 > parcs[i].x)
                box.x1 = parcs[i].x;
            if(box.x2 < (parcs[i].x + parcs[i].width))
                box.x2 = parcs[i].x + parcs[i].width;
            if(box.y1 > parcs[i].y)
                box.y1 = parcs[i].y;
            if(box.y2 < (parcs[i].y + parcs[i].height))
                box.y2 = parcs[i].y + parcs[i].height;
        }

	RootlessStartDrawing((WindowPtr) dst);

	if (can_accel_fill (dst, pGC)
	    && box_bytes (dst, &box) >= xp_fill_bytes_threshold)
	{
	    GC_UNSET_PM (pGC, dst);
	}

	pGC->ops->PolyFillArc(dst, pGC, narcs, parcs);

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

	RootlessFinishedDrawing ((WindowPtr) dst);
    }
    else
	pGC->ops->PolyFillArc(dst, pGC, narcs, parcs);

    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);

    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("imagetext8 start\n");

    RootlessStartDrawing((WindowPtr) dst);

    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;

	if (can_accel_fill (dst, pGC)
	    && box_bytes (dst, &box) >= xp_fill_bytes_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);

    RootlessFinishedDrawing ((WindowPtr) dst);

    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

    if (GC_IS_ROOT (dst))
	return 0;			/* FIXME: ok? */

    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("polytext8 start\n");

    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);
    }

    RootlessFinishedDrawing ((WindowPtr) dst);

    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);

    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("imagetext16 start\n");

    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 (can_accel_fill (dst, pGC)
	    && box_bytes (dst, &box) >= xp_fill_bytes_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);

	RootlessFinishedDrawing ((WindowPtr) dst);
    }
    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

    if (GC_IS_ROOT (dst))
	return 0;			/* FIXME: ok? */

    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("polytext16 start\n");

    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);
    }

    RootlessFinishedDrawing ((WindowPtr) dst);

    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 nglyph,
                                  CharInfoPtr *ppci, pointer unused)
{
    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("imageglyph start\n");

    RootlessStartDrawing((WindowPtr) dst);
    pGC->ops->ImageGlyphBlt(dst, pGC, x, y, nglyph, ppci, unused);

    if (nglyph > 0) {
        int top, bot, width = 0;
        BoxRec box;

        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;

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

    RootlessFinishedDrawing ((WindowPtr) 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)
{
    GC_SKIP_ROOT (dst);
    GCOP_UNWRAP(pGC);
    RL_DEBUG_MSG("polyglyph start\n");

    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);
    }

    RootlessFinishedDrawing ((WindowPtr) dst);

    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;

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

    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);

    RootlessFinishedDrawing ((WindowPtr) dst);

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