/*
 * Functions to use the XRender extension for text
 *
 * Copyright 2003 TransGaming Technologies
 * david@transgaming.com
 *
 * Copyright 2001 Huw D M Davies for CodeWeavers
 */
#include "config.h"

#include "winnt.h"
#include "x11drv.h"
#include "bitmap.h"
#include "region.h"

#include "wine/winuser16.h"

#include "wine/unicode.h"
#include "wine/port.h"
#include "wine/debug.h"

#include <string.h>
#include <stdlib.h>
#include <assert.h>

BOOL X11DRV_XRender_Installed = FALSE;
BOOL X11DRV_XRender_Cursor = FALSE;
WINE_DEFAULT_DEBUG_CHANNEL(xrender);

#ifdef HAVE_LIBXRENDER

#include "ts_xlib.h"
#include "ts_xutil.h"
#include <X11/extensions/Xrender.h>

#if (RENDER_MAJOR > 0) || (RENDER_MINOR >= 5)
#define HAVE_LIBXRENDER_CURSOR
#endif

#define MAKE_FUNCPTR(f) static typeof(f) * p##f

static void *xRender_so_handle;
MAKE_FUNCPTR(XRenderQueryExtension);
MAKE_FUNCPTR(XRenderQueryVersion);
MAKE_FUNCPTR(XRenderFindVisualFormat);
MAKE_FUNCPTR(XRenderFindFormat);
MAKE_FUNCPTR(XRenderFreeGlyphSet);
MAKE_FUNCPTR(XRenderCreateGlyphSet);
MAKE_FUNCPTR(XRenderFreePicture);
MAKE_FUNCPTR(XRenderAddGlyphs);
MAKE_FUNCPTR(XRenderCreatePicture);
MAKE_FUNCPTR(XRenderSetPictureClipRectangles);
MAKE_FUNCPTR(XRenderFillRectangle);
MAKE_FUNCPTR(XRenderCompositeString16);
#ifdef HAVE_LIBXRENDER_CURSOR
MAKE_FUNCPTR(XRenderCreateCursor);
#endif


#undef MAKE_FUNCPTR

#ifndef SONAME_LIBXRENDER
# define SONAME_LIBXRENDER "libXrender.so.1"
#endif


static CRITICAL_SECTION xrender_cs;

static XRenderPictFormat *screen_format; /* format of screen */
static XRenderPictFormat *mono_format; /* format of mono bitmap */
#ifdef HAVE_LIBXRENDER_CURSOR
static XRenderPictFormat *cursor_format; /* format of ARGB cursor */
#endif

typedef struct
{
  LOGFONTW lf;
  XFORM  xform; /* this is dum as we don't care about offsets */
  DWORD hash;
} LFANDSIZE;

#define INITIAL_REALIZED_BUF_SIZE 128


typedef struct
{
  LFANDSIZE lfsz;
  GlyphSet glyphset;
  XRenderPictFormat *font_format;
  int nrealized;
  BOOL *realized;
  UINT count;
  INT next;
} gsCacheEntry;

struct tagXRENDERINFO
{
    int                cacheIndex;
    Picture            pict;
    Picture            tile_pict;
    Pixmap             tile_xpm;
    COLORREF           lastTextColor;
};
#define CACHE_ENTRY(index) (glyphsetCache + (index))

static gsCacheEntry *glyphsetCache = NULL;
static DWORD glyphsetCacheSize = 0;
static INT lastfree = -1;
static INT mru = -1;

#define INIT_CACHE_SIZE 10

const static int antialias = 1;

/***********************************************************************
 *   X11DRV_XRender_Init
 *
 * Let's see if our XServer has the extension available
 *
 */
BOOL X11DRV_XRender_Init(void)
{
    int error_base, event_base, major, minor, i;
    XRenderPictFormat pf;

    TRACE("\n");

    InitializeCriticalSection( &xrender_cs );
    CRITICAL_SECTION_NAME( &xrender_cs, "xrender_cs" );

    xRender_so_handle = wine_dlopen(SONAME_LIBXRENDER, RTLD_NOW, NULL, 0);
    if (!xRender_so_handle)
    {
        goto noxrender;
    }

#define DYNLOAD_XRENDER(f) \
    do { p##f = wine_dlsym(xRender_so_handle, #f, NULL, 0); \
    if (!p##f) \
    { \
        ERR("failed to load XRender symbol '%s'\n", #f); \
        goto xrender_initerror; \
    }} while (0)

    DYNLOAD_XRENDER(XRenderQueryExtension);
    DYNLOAD_XRENDER(XRenderQueryVersion);
    DYNLOAD_XRENDER(XRenderFindVisualFormat);
    DYNLOAD_XRENDER(XRenderFindFormat);
    DYNLOAD_XRENDER(XRenderFreeGlyphSet);
    DYNLOAD_XRENDER(XRenderFindFormat);
    DYNLOAD_XRENDER(XRenderCreateGlyphSet);
    DYNLOAD_XRENDER(XRenderFreePicture);
    DYNLOAD_XRENDER(XRenderAddGlyphs);
    DYNLOAD_XRENDER(XRenderCreatePicture);
    DYNLOAD_XRENDER(XRenderSetPictureClipRectangles);
    DYNLOAD_XRENDER(XRenderFillRectangle);
    DYNLOAD_XRENDER(XRenderCompositeString16);

#undef DYNLOAD_XRENDER

#define DYNLOAD_XRENDER(f) \
    p##f = wine_dlsym(xRender_so_handle, #f, NULL, 0);

#ifdef HAVE_LIBXRENDER_CURSOR
    DYNLOAD_XRENDER(XRenderCreateCursor);
#endif

#undef DYNLOAD_XRENDER

    wine_tsx11_lock();
    if(pXRenderQueryExtension(gdi_display, &event_base, &error_base) &&
       pXRenderQueryVersion(gdi_display, &major, &minor)) {
        X11DRV_XRender_Installed = TRUE;
        TRACE("Xrender is up and running error_base = %d\n", error_base);
        screen_format = pXRenderFindVisualFormat(gdi_display, visual);
        pf.type = PictTypeDirect;
        pf.depth = 1;
        pf.direct.alpha = 0;
        pf.direct.alphaMask = 1;
        mono_format = pXRenderFindFormat(gdi_display, PictFormatType |
                                          PictFormatDepth | PictFormatAlpha |
                                          PictFormatAlphaMask, &pf, 0);

        glyphsetCache = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                                  sizeof(*glyphsetCache) * INIT_CACHE_SIZE);

        glyphsetCacheSize = INIT_CACHE_SIZE;
        lastfree = 0;
        for(i = 0; i < INIT_CACHE_SIZE; i++) {
          glyphsetCache[i].next = i + 1;
          glyphsetCache[i].count = -1;
        }
        glyphsetCache[i-1].next = -1;

#ifdef HAVE_LIBXRENDER_CURSOR
        if ((major > 0) || (minor >= 5)) {
          /* FIXME: the Xcursor library lets you disable this by setting
           * the XCURSOR_CORE environment variable to true/yes/on/1.
           * Perhaps we should have a way too. */
          TRACE("Xrender ARGB cursor support available\n");
          X11DRV_XRender_Cursor = TRUE;
          pf.type = PictTypeDirect;
          pf.depth = 32;
          pf.direct.red = 16;
          pf.direct.redMask = 0xff;
          pf.direct.green = 8;
          pf.direct.greenMask = 0xff;
          pf.direct.blue = 0;
          pf.direct.blueMask = 0xff;
          pf.direct.alpha = 24;
          pf.direct.alphaMask = 0xff;
          cursor_format = pXRenderFindFormat(gdi_display, PictFormatType |
                                              PictFormatDepth | PictFormatRed |
                                              PictFormatRedMask | PictFormatGreen |
                                              PictFormatGreenMask | PictFormatBlue |
                                              PictFormatBlueMask | PictFormatAlpha |
                                              PictFormatAlphaMask, &pf, 0);
        }
#endif
    } else {
        wine_tsx11_unlock();
        goto xrender_initerror;
    }
    wine_tsx11_unlock();
    return TRUE;

xrender_initerror:
    wine_dlclose(xRender_so_handle, NULL, 0);
noxrender:
    MESSAGE("XRender could not be loaded\n"
            "\tThis is not necessarily a fatal error, however some\n"
            "\tapplications require XRender to be installed and\n"
            "\tmay not function or display text correctly otherwise.\n"
            "\tPlease consult the Cedega Font FAQ for more details\n"
            "\tabout this problem");
    return FALSE;
}

/***********************************************************************
 *   X11DRV_XRender_Finalize
 */
void X11DRV_XRender_Finalize(void)
{
    /* FIXME: free structs */
    FIXME("Free cached glyphsets\n");
    wine_dlclose(xRender_so_handle, NULL, 0);
    DeleteCriticalSection( &xrender_cs );
}

static BOOL fontcmp(const LFANDSIZE *p1, const LFANDSIZE *p2)
{
  if(p1->hash != p2->hash) return TRUE;
  if(memcmp(&p1->xform, &p2->xform, sizeof(p1->xform))) return TRUE;
  if(memcmp(&p1->lf, &p2->lf, offsetof(LOGFONTW, lfFaceName))) return TRUE;
  return strcmpW(p1->lf.lfFaceName, p2->lf.lfFaceName);
}

#if 0
static void walk_cache(void)
{
  int i;

  for(i=mru; i >= 0; i = glyphsetCache[i].next)
    TRACE("item %d\n", i);
}
#endif

static int LookupEntry(const LFANDSIZE *plfsz)
{
  int i, prev_i = -1;

  for(i = mru; i >= 0; i = glyphsetCache[i].next) {
    TRACE("%d\n", i);
    if(glyphsetCache[i].count == -1) { /* reached free list so stop */
      i = -1;
      break;
    }

    if(!fontcmp(&glyphsetCache[i].lfsz, plfsz)) {
      glyphsetCache[i].count++;
      if(prev_i >= 0) {
        glyphsetCache[prev_i].next = glyphsetCache[i].next;
        glyphsetCache[i].next = mru;
        mru = i;
      }
      TRACE("found font in cache %d\n", i);
      return i;
    }
    prev_i = i;
  }
  TRACE("font not in cache\n");
  return -1;
}

/* Returns index of entry */
static int AllocEntry(void)
{
  int best = -1, prev_best = -1, i, prev_i = -1;

  if(lastfree >= 0) {
    assert(glyphsetCache[lastfree].count == -1);
    glyphsetCache[lastfree].count = 1;
    best = lastfree;
    lastfree = glyphsetCache[lastfree].next;
    assert(best != mru);
    glyphsetCache[best].next = mru;
    mru = best;

    TRACE("empty space at %d, next lastfree = %d\n", mru, lastfree);
    return mru;
  }

  for(i = mru; i >= 0; i = glyphsetCache[i].next) {
    if(glyphsetCache[i].count == 0) {
      best = i;
      prev_best = prev_i;
    }
    prev_i = i;
  }

  if(best >= 0) {
    TRACE("freeing unused glyphset at cache %d\n", best);
    wine_tsx11_lock();
    pXRenderFreeGlyphSet(gdi_display, glyphsetCache[best].glyphset);
    wine_tsx11_unlock();
    glyphsetCache[best].glyphset = 0;
    if(glyphsetCache[best].nrealized) { /* do we really want to do this? */
      HeapFree(GetProcessHeap(), 0, glyphsetCache[best].realized);
      glyphsetCache[best].realized = NULL;
      glyphsetCache[best].nrealized = 0;
    }
    glyphsetCache[best].count = 1;
    if(prev_best >= 0) {
      glyphsetCache[prev_best].next = glyphsetCache[best].next;
      glyphsetCache[best].next = mru;
      mru = best;
    } else {
      assert(mru == best);
    }
    return mru;
  }

  TRACE("Growing cache\n");
  glyphsetCache = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                              glyphsetCache,
                              (glyphsetCacheSize + INIT_CACHE_SIZE)
                              * sizeof(*glyphsetCache));

  for(best = i = glyphsetCacheSize; i < glyphsetCacheSize + INIT_CACHE_SIZE;
      i++) {
    glyphsetCache[i].next = i + 1;
    glyphsetCache[i].count = -1;
  }
  glyphsetCache[i-1].next = -1;
  glyphsetCacheSize += INIT_CACHE_SIZE;

  lastfree = glyphsetCache[best].next;
  glyphsetCache[best].count = 1;
  glyphsetCache[best].next = mru;
  mru = best;
  TRACE("new free cache slot at %d\n", mru);
  return mru;
}

static int GetCacheEntry(const LFANDSIZE *plfsz)
{
  XRenderPictFormat pf;
  gsCacheEntry *entry;
  int index;

  if((index = LookupEntry(plfsz)) != -1) return index;

  index = AllocEntry();
  entry = CACHE_ENTRY(index);
  entry->lfsz = *plfsz;
  assert(entry->nrealized == 0);


  if(plfsz->lf.lfQuality == ANTIALIASED_QUALITY ||
          (antialias && abs(plfsz->lf.lfHeight) > 5 &&
          plfsz->lf.lfQuality != NONANTIALIASED_QUALITY)) {
    pf.depth = 8;
    pf.direct.alphaMask = 0xff;
  } else {
    pf.depth = 1;
    pf.direct.alphaMask = 1;
  }
  pf.type = PictTypeDirect;
  pf.direct.alpha = 0;

  wine_tsx11_lock();
  entry->font_format = pXRenderFindFormat(gdi_display,
                                           PictFormatType |
                                           PictFormatDepth |
                                           PictFormatAlpha |
                                           PictFormatAlphaMask,
                                           &pf, 0);

  entry->glyphset = pXRenderCreateGlyphSet(gdi_display, entry->font_format);
  wine_tsx11_unlock();

  return index;
}

static void dec_ref_cache(int index)
{
  gsCacheEntry* entry = CACHE_ENTRY( index );

  TRACE("dec'ing entry at index %d to %d\n", index, entry->count - 1);

  if( entry->count <= 0 )
  {
    ERR( "Invalid decrement (%p & %p) %d!\n", entry, glyphsetCache, index );
    assert(entry->count > 0);
  }

  entry->count--;
}

static void lfsz_calc_hash(LFANDSIZE *plfsz)
{
  DWORD hash = 0, *ptr;
  int i;

  for(ptr = (DWORD*)&plfsz->xform; ptr < (DWORD*)(&plfsz->xform + 1); ptr++)
    hash ^= *ptr;
  for(i = 0, ptr = (DWORD*)&plfsz->lf; i < 7; i++, ptr++)
    hash ^= *ptr;
  for(i = 0, ptr = (DWORD*)&plfsz->lf.lfFaceName; i < LF_FACESIZE/2; i++, ptr++) {
    WCHAR *pwc = (WCHAR *)ptr;
    if(!*pwc) break;
    hash ^= *ptr;
    pwc++;
    if(!*pwc) break;
  }
  plfsz->hash = hash;
  return;
}

/***********************************************************************
 *   X11DRV_XRender_SelectFont
 */
BOOL X11DRV_XRender_SelectFont(DC *dc, HFONT hfont)
{
    X11DRV_PDEVICE *physDev = (X11DRV_PDEVICE *)dc->physDev;
    LFANDSIZE lfsz;

    GetObjectW(hfont, sizeof(lfsz.lf), &lfsz.lf);
    TRACE("h=%ld w=%ld weight=%ld it=%d charset=%d name=%s\n",
          lfsz.lf.lfHeight, lfsz.lf.lfWidth, lfsz.lf.lfWeight,
          lfsz.lf.lfItalic, lfsz.lf.lfCharSet, debugstr_w(lfsz.lf.lfFaceName));
    lfsz.xform = dc->xformWorld2Vport;
    lfsz_calc_hash(&lfsz);

    EnterCriticalSection(&xrender_cs); 

    if(!physDev->xrender)
        physDev->xrender = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                                     sizeof(*physDev->xrender));
    else if(physDev->xrender->cacheIndex != -1)
        dec_ref_cache(physDev->xrender->cacheIndex);
    physDev->xrender->cacheIndex = GetCacheEntry(&lfsz);

    LeaveCriticalSection(&xrender_cs); 

    return TRUE;
}

/***********************************************************************
 *   X11DRV_XRender_DeleteDC
 */
void X11DRV_XRender_DeleteDC(DC *dc)
{
    X11DRV_PDEVICE *physDev = (X11DRV_PDEVICE *)dc->physDev;

    EnterCriticalSection(&xrender_cs); 

    if(physDev->xrender->tile_pict)
    {
        wine_tsx11_lock();
        pXRenderFreePicture(gdi_display, physDev->xrender->tile_pict);
        wine_tsx11_unlock();
    }

    if(physDev->xrender->tile_xpm)
    {
        TSXFreePixmap(gdi_display, physDev->xrender->tile_xpm);
    }

    if(physDev->xrender->pict) {
        TRACE("freeing pict = %lx dc = %p\n", physDev->xrender->pict, dc);
        wine_tsx11_lock();
        pXRenderFreePicture(gdi_display, physDev->xrender->pict);
        wine_tsx11_unlock();
    }

    if(physDev->xrender->cacheIndex != -1)
        dec_ref_cache(physDev->xrender->cacheIndex);  
    HeapFree(GetProcessHeap(), 0, physDev->xrender);
    physDev->xrender = NULL;

    LeaveCriticalSection(&xrender_cs); 

    return;
}

/***********************************************************************
 *   X11DRV_XRender_UpdateDrawable
 *
 * This gets called from X11DRV_SetDrawable and deletes the pict when the
 * drawable changes.  However at the moment we delete the pict at the end of
 * every ExtTextOut so this is basically a NOP.
 */
void X11DRV_XRender_UpdateDrawable(DC *dc)
{
    X11DRV_PDEVICE *physDev = (X11DRV_PDEVICE *)dc->physDev;

    EnterCriticalSection(&xrender_cs); 

    if(physDev->xrender->pict) {
        TRACE("freeing pict %08lx from dc %p\n", physDev->xrender->pict, dc);
        wine_tsx11_lock();
        pXRenderFreePicture(gdi_display, physDev->xrender->pict);
        wine_tsx11_unlock();
    }
    physDev->xrender->pict = 0;

    LeaveCriticalSection(&xrender_cs); 

    return;
}

static BOOL UploadGlyph(DC *dc, WCHAR glyph)
{
    X11DRV_PDEVICE *physDev = (X11DRV_PDEVICE *)dc->physDev;
    int buflen;
    char *buf;
    Glyph gid;
    GLYPHMETRICS gm;
    XGlyphInfo gi;
    gsCacheEntry *entry = CACHE_ENTRY( physDev->xrender->cacheIndex );
    UINT ggo_format;
    BOOL aa;

    if(entry->nrealized <= glyph) {
        entry->nrealized = (glyph / 128 + 1) * 128;
        entry->realized = HeapReAlloc(GetProcessHeap(),
                                      HEAP_ZERO_MEMORY,
                                      entry->realized,
                                      entry->nrealized * sizeof(BOOL));
    }
    entry->realized[glyph] = TRUE;

    if(entry->font_format->depth == 8) {
        aa = TRUE;
        ggo_format = WINE_GGO_GRAY16_BITMAP;
    } else {
        aa = FALSE;
        ggo_format = GGO_BITMAP;
    }

    buflen = GetGlyphOutlineW(dc->hSelf, glyph, ggo_format, &gm, 0, NULL,
                              NULL);
    if(buflen == GDI_ERROR)
    {
        WARN("error in GetGlyphOutlineW\n");
        return FALSE;
    }

    buf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, buflen);
    if (GetGlyphOutlineW(dc->hSelf, glyph, ggo_format,
                    &gm, buflen, buf, NULL) == GDI_ERROR)
    {
        WARN("error in GetGlyphOutlineW (2)\n");
        HeapFree(GetProcessHeap(), 0, buf);
        return FALSE;
    }

    TRACE("buflen = %d. Got metrics: %dx%d adv=%d,%d origin=%ld,%ld\n",
          buflen,
          gm.gmBlackBoxX, gm.gmBlackBoxY, gm.gmCellIncX, gm.gmCellIncY,
          gm.gmptGlyphOrigin.x, gm.gmptGlyphOrigin.y);

    gi.width = gm.gmBlackBoxX;
    gi.height = gm.gmBlackBoxY;
    gi.x = -gm.gmptGlyphOrigin.x;
    gi.y = gm.gmptGlyphOrigin.y;
    gi.xOff = gm.gmCellIncX;
    gi.yOff = gm.gmCellIncY;

    if(TRACE_ON(xrender)) {
        int pitch, i, j;
        char output[300];
        unsigned char *line;

        if(!aa) {
            pitch = ((gi.width + 31) / 32) * 4;
            for(i = 0; i < gi.height; i++) {
                line = buf + i * pitch;
                output[0] = '\0';
                for(j = 0; j < pitch * 8; j++) {
                    strcat(output, (line[j / 8] & (1 << (7 - (j % 8)))) ? "#" : " ");
                }
                strcat(output, "\n");
                TRACE(output);
            }
        } else {
            char blks[] = " .:;!o*#";
            char str[2];

            str[1] = '\0';
            pitch = ((gi.width + 3) / 4) * 4;
            for(i = 0; i < gi.height; i++) {
                line = buf + i * pitch;
                output[0] = '\0';
                for(j = 0; j < pitch; j++) {
                    str[0] = blks[line[j] >> 5];
                    strcat(output, str);
                }
                strcat(output, "\n");
                TRACE(output);
            }
        }
    }

    if(!aa && BitmapBitOrder(gdi_display) != MSBFirst) {
        unsigned char *byte = buf, c;
        int i = buflen;

        while(i--) {
            c = *byte;

            /* magic to flip bit order */
            c = ((c << 1) & 0xaa) | ((c >> 1) & 0x55);
            c = ((c << 2) & 0xcc) | ((c >> 2) & 0x33);
            c = ((c << 4) & 0xf0) | ((c >> 4) & 0x0f);

            *byte++ = c;
        }
    }
    gid = glyph;
    wine_tsx11_lock();
    pXRenderAddGlyphs(gdi_display, entry->glyphset, &gid, &gi, 1,
                       buf, buflen);
    wine_tsx11_unlock();
    HeapFree(GetProcessHeap(), 0, buf);
    return TRUE;
}

/***********************************************************************
 *   X11DRV_XRender_ExtTextOut
 */
BOOL X11DRV_XRender_ExtTextOut( DC *dc, INT x, INT y, UINT flags,
                                const RECT *lprect, LPCWSTR wstr, UINT count,
                                const INT *lpDx )
{
    XRenderColor col;
    int idx;
    TEXTMETRICW tm;
    X11DRV_PDEVICE *physDev = (X11DRV_PDEVICE *)dc->physDev;
    RGNOBJ *obj;
    XRectangle *pXrect;
    SIZE sz;
    RECT rc;
    BOOL done_extents = FALSE;
    INT width, xwidth, ywidth;
    double cosEsc, sinEsc;
    XGCValues xgcval;
    LOGFONTW lf;
    int render_op = PictOpOver;

    TRACE("%04x, %d, %d, %08x, %p, %s, %d, %p)\n", dc->hSelf, x, y, flags,
          lprect, debugstr_wn(wstr, count), count, lpDx);
    if(lprect)
      TRACE("rect: %d,%d - %d,%d\n", lprect->left, lprect->top, lprect->right,
            lprect->bottom);
    TRACE("align = %x bkmode = %x mapmode = %x\n", dc->textAlign,
          dc->backgroundMode,
          dc->MapMode);

    if(dc->textAlign & TA_UPDATECP) {
        x = dc->CursPosX;
        y = dc->CursPosY;
    }

    GetObjectW(GetCurrentObject(dc->hSelf, OBJ_FONT), sizeof(lf), &lf);
    if(lf.lfEscapement != 0) {
        cosEsc = cos(lf.lfEscapement * M_PI / 1800);
        sinEsc = sin(lf.lfEscapement * M_PI / 1800);
    } else {
        cosEsc = 1;
        sinEsc = 0;
    }

    if(flags & (ETO_CLIPPED | ETO_OPAQUE)) {
        if(!lprect) {
            if(flags & ETO_CLIPPED) return FALSE;
                GetTextExtentPointW(dc->hSelf, wstr, count, &sz);
                done_extents = TRUE;
                rc.left = x;
                rc.top = y;
                rc.right = x + sz.cx;
                rc.bottom = y + sz.cy;
        } else {
            rc = *lprect;
        }

        rc.left = INTERNAL_XWPTODP(dc, rc.left, rc.top);
        rc.top = INTERNAL_YWPTODP(dc, rc.left, rc.top);
        rc.right = INTERNAL_XWPTODP(dc, rc.right, rc.bottom);
        rc.bottom = INTERNAL_YWPTODP(dc, rc.right, rc.bottom);

        if(rc.left > rc.right) {INT tmp = rc.left; rc.left = rc.right; rc.right = tmp;}
        if(rc.top > rc.bottom) {INT tmp = rc.top; rc.top = rc.bottom; rc.bottom = tmp;}
    }

    xgcval.function = GXcopy;
    xgcval.background = physDev->backgroundPixel;
    xgcval.fill_style = FillSolid;
    TSXChangeGC( gdi_display, physDev->gc,
                 GCFunction | GCBackground | GCFillStyle, &xgcval );

    X11DRV_LockDIBSection( dc, DIB_Status_GdiMod, FALSE, FALSE );

    if(flags & ETO_OPAQUE) {
        TSXSetForeground( gdi_display, physDev->gc, physDev->backgroundPixel );
        TSXFillRectangle( gdi_display, physDev->drawable, physDev->gc,
                          dc->DCOrgX + rc.left, dc->DCOrgY + rc.top,
                          rc.right - rc.left, rc.bottom - rc.top );
    }

    if(count == 0) {
        X11DRV_UnlockDIBSection( dc, TRUE );
        return TRUE;
    }

    x = INTERNAL_XWPTODP( dc, x, y );
    y = INTERNAL_YWPTODP( dc, x, y );

    TRACE("real x,y %d,%d\n", x, y);

    if(lpDx) {
        width = 0;
        for(idx = 0; idx < count; idx++)
            width += lpDx[idx];
    } else {
        if(!done_extents) {
            GetTextExtentPointW(dc->hSelf, wstr, count, &sz);
            done_extents = TRUE;
        }
        width = sz.cx;
    }
    width = INTERNAL_XWSTODS(dc, width);
    xwidth = width * cosEsc;
    ywidth = width * sinEsc;

    GetTextMetricsW(dc->hSelf, &tm);

    switch( dc->textAlign & (TA_LEFT | TA_RIGHT | TA_CENTER) ) {
    case TA_LEFT:
        if (dc->textAlign & TA_UPDATECP) {
            dc->CursPosX = INTERNAL_XDPTOWP( dc, x + xwidth, y - ywidth );
            dc->CursPosY = INTERNAL_YDPTOWP( dc, x + xwidth, y - ywidth );
        }
        break;

    case TA_CENTER:
        x -= xwidth / 2;
        y += ywidth / 2;
        break;

    case TA_RIGHT:
        x -= xwidth;
        y += ywidth;
        if (dc->textAlign & TA_UPDATECP) {
            dc->CursPosX = INTERNAL_XDPTOWP( dc, x + xwidth, y - ywidth );
            dc->CursPosY = INTERNAL_YDPTOWP( dc, x + xwidth, y - ywidth );
        }
        break;
    }

    switch( dc->textAlign & (TA_TOP | TA_BOTTOM | TA_BASELINE) ) {
    case TA_TOP:
        y += tm.tmAscent * cosEsc;
        x += tm.tmAscent * sinEsc;
        break;

    case TA_BOTTOM:
        y -= tm.tmDescent * cosEsc;
        x -= tm.tmDescent * sinEsc;
        break;

    case TA_BASELINE:
        break;
    }

    if (flags & ETO_CLIPPED)
    {
        SaveVisRgn16( dc->hSelf );
        CLIPPING_IntersectVisRect( dc, rc.left, rc.top, rc.right,
                                   rc.bottom, FALSE );
    }


    EnterCriticalSection(&xrender_cs); 

    if(!physDev->xrender->pict) {
        XRenderPictureAttributes pa;
        pa.subwindow_mode = IncludeInferiors;

        wine_tsx11_lock();
        physDev->xrender->pict =
          pXRenderCreatePicture(gdi_display,
                                physDev->drawable,
                                (dc->bitsPerPixel == 1) ?
                                  mono_format : screen_format,
                                CPSubwindowMode, &pa);
        wine_tsx11_unlock();

        TRACE("allocing pict = %lx dc = %p drawable = %08lx\n", physDev->xrender->pict, dc, physDev->drawable);
    } else {
        TRACE("using existing pict = %lx dc = %p\n", physDev->xrender->pict, dc);
    }

    obj = (RGNOBJ *) GDI_GetObjPtr(dc->hGCClipRgn, REGION_MAGIC);
    if (!obj)
    {
        ERR("Rgn is 0. Please report this.\n");
        LeaveCriticalSection(&xrender_cs); 
        return FALSE;
    }

    if (obj->rgn->numRects > 0)
    {
        XRectangle *pXr;
        RECT *pRect = obj->rgn->rects;
        RECT *pEndRect = obj->rgn->rects + obj->rgn->numRects;

        pXrect = HeapAlloc( GetProcessHeap(), 0,
                            sizeof(*pXrect) * obj->rgn->numRects );
        if(!pXrect)
        {
            WARN("Can't alloc buffer\n");
            GDI_ReleaseObj( dc->hGCClipRgn );
            LeaveCriticalSection(&xrender_cs); 
            return FALSE;
        }

        for(pXr = pXrect; pRect < pEndRect; pRect++, pXr++)
        {
            pXr->x = pRect->left;
            pXr->y = pRect->top;
            pXr->width = pRect->right - pRect->left;
            pXr->height = pRect->bottom - pRect->top;
            TRACE("Adding clip rect %d,%d - %d,%d\n", pRect->left, pRect->top,
                  pRect->right, pRect->bottom);
        }
    }
    else {
        TRACE("no clip rgn\n");
        pXrect = NULL;
    }

    wine_tsx11_lock();
    pXRenderSetPictureClipRectangles( gdi_display, physDev->xrender->pict,
                                       0, 0, pXrect, obj->rgn->numRects );
    wine_tsx11_unlock();

    if(pXrect)
        HeapFree( GetProcessHeap(), 0, pXrect );

    GDI_ReleaseObj( dc->hGCClipRgn );

    if(dc->backgroundMode != TRANSPARENT) {
        if(!((flags & ETO_CLIPPED) && (flags & ETO_OPAQUE))) {
            if(!(flags & ETO_OPAQUE) || x < rc.left || x + width >= rc.right ||
               y - tm.tmAscent < rc.top || y + tm.tmDescent >= rc.bottom) {
                TSXSetForeground( gdi_display, physDev->gc, physDev->backgroundPixel );
                TSXFillRectangle( gdi_display, physDev->drawable, physDev->gc,
                                  dc->DCOrgX + x, dc->DCOrgY + y - tm.tmAscent,
                                  width, tm.tmAscent + tm.tmDescent );
            }
        }
    }

    /* Create a 1x1 pixmap to tile over the font mask */
    if(!physDev->xrender->tile_xpm) {
        XRenderPictureAttributes pa;

              XRenderPictFormat *format = (dc->bitsPerPixel == 1) ? mono_format : screen_format;
        physDev->xrender->tile_xpm = TSXCreatePixmap(gdi_display,
                                                     physDev->drawable,
                                                     1, 1,
                                                     format->depth);
        pa.repeat = True;
        wine_tsx11_lock();
        physDev->xrender->tile_pict = pXRenderCreatePicture(gdi_display,
                                                             physDev->xrender->tile_xpm,
                                                             format,
                                                             CPRepeat, &pa);
        wine_tsx11_unlock();
        TRACE("Created pixmap of depth %d\n", format->depth);
        /* init lastTextColor to something different from dc->textColor */
        physDev->xrender->lastTextColor = ~dc->textColor;

    }

    if(dc->textColor != physDev->xrender->lastTextColor) {
        if(dc->bitsPerPixel != 1) {
            /* Map 0 -- 0xff onto 0 -- 0xffff */
            col.red = GetRValue(dc->textColor);
            col.red |= col.red << 8;
            col.green = GetGValue(dc->textColor);
            col.green |= col.green << 8;
            col.blue = GetBValue(dc->textColor);
            col.blue |= col.blue << 8;
            col.alpha = 0x0;
        } else { /* for a 1bpp bitmap we always need a 1 in the tile */
            col.red = col.green = col.blue = 0;
            col.alpha = 0xffff;
        }
        wine_tsx11_lock();
        pXRenderFillRectangle(gdi_display, PictOpSrc,
                               physDev->xrender->tile_pict,
                               &col, 0, 0, 1, 1);
        wine_tsx11_unlock();
        physDev->xrender->lastTextColor = dc->textColor;
    }

    /* FIXME the mapping of Text/BkColor onto 1 or 0 needs investigation.
     */
    if((dc->bitsPerPixel == 1) && ((dc->textColor & 0xffffff) == 0))
        render_op = PictOpOutReverse; /* This gives us 'black' text */

    for(idx = 0; idx < count; idx++) {
        if(wstr[idx] >= CACHE_ENTRY( physDev->xrender->cacheIndex )->nrealized ||
           CACHE_ENTRY( physDev->xrender->cacheIndex )->realized[wstr[idx]] == FALSE) {
            UploadGlyph(dc, wstr[idx]);
        }
    }


    TRACE("Writing %s at %d,%d\n", debugstr_wn(wstr,count), dc->DCOrgX + x,
          dc->DCOrgY + y);
    if(!lpDx)
    {
        wine_tsx11_lock();
        pXRenderCompositeString16(gdi_display, render_op,
                                   physDev->xrender->tile_pict,
                                   physDev->xrender->pict,
                                   CACHE_ENTRY( physDev->xrender->cacheIndex )->font_format,
                                   CACHE_ENTRY( physDev->xrender->cacheIndex )->glyphset,
                                   0, 0, dc->DCOrgX + x, dc->DCOrgY + y, wstr,
                                   count);
        wine_tsx11_unlock();
    }
    else
    {
        INT offset = 0, xoff = 0, yoff = 0;
        for(idx = 0; idx < count; idx++) {
            wine_tsx11_lock();
            pXRenderCompositeString16(gdi_display, render_op,
                                       physDev->xrender->tile_pict,
                                       physDev->xrender->pict,
                                       CACHE_ENTRY( physDev->xrender->cacheIndex )->font_format,
                                       CACHE_ENTRY( physDev->xrender->cacheIndex )->glyphset,
                                       0, 0, dc->DCOrgX + x + xoff,
                                       dc->DCOrgY + y + yoff,
                                       wstr + idx, 1);
            wine_tsx11_unlock();
            offset += INTERNAL_XWSTODS(dc, lpDx[idx]);
            xoff = offset * cosEsc;
            yoff = offset * sinEsc;
        }
    }

    if(physDev->xrender->pict) {
        wine_tsx11_lock();
        pXRenderFreePicture(gdi_display, physDev->xrender->pict);
        wine_tsx11_unlock();
    }
    physDev->xrender->pict = 0;

    LeaveCriticalSection(&xrender_cs); 

    if (flags & ETO_CLIPPED)
        RestoreVisRgn16( dc->hSelf );

    X11DRV_UnlockDIBSection( dc, TRUE );
    return TRUE;
}

Cursor X11DRV_XRender_CreateCursor( Display *display, CURSORICONINFO *ptr )
{
#ifdef HAVE_LIBXRENDER_CURSOR
  int mask_size;
  char *mask;
  char *image;
  RGBQUAD *pal;
  char *cimage;
  XImage *ximage;
  Pixmap pixmap = None;
  Picture picture = None;
  Cursor cursor = None;
  int hotx, hoty;
  GC gc;

  TRACE("creating %dx%d %dbpp cursor\n", ptr->nWidth, ptr->nHeight, ptr->bBitsPerPixel);
  TRACE("with hotspot %d,%d\n", ptr->ptHotSpot.x, ptr->ptHotSpot.y);
  if (ptr->bBitsPerPixel != 32 && ptr->bBitsPerPixel != 24 &&
      ptr->bBitsPerPixel != 16 && ptr->bBitsPerPixel != 15 &&
      ptr->bBitsPerPixel != 8) {
    FIXME("%dbpp cursors not supported\n", ptr->bBitsPerPixel);
    return None;
  }

  mask_size = (ptr->nWidth/8 * ptr->nHeight);
  mask = (char *)(ptr+1);
  image = mask + mask_size;
  pal = (RGBQUAD*)(image + ptr->nWidthBytes * ptr->nHeight);
  cimage = image;

  if (ptr->bBitsPerPixel != 32) {
    /* convert to 32bpp, using mask for alpha */
    char *src = image;
    char *msrc = mask;
    int x, y;
    DWORD color, sc;
    DWORD *dst;
    BYTE mbit;

    cimage = HeapAlloc(GetProcessHeap(), 0, ptr->nWidth * ptr->nHeight * 4);
    dst = (DWORD *)cimage;
    mbit = 0x80;
    for (y=0; y<ptr->nHeight; y++) {
      for (x=0; x<ptr->nWidth; x++) {
        color = (*msrc & mbit) ? 0x00000000 : 0xff000000;
        switch (ptr->bBitsPerPixel) {
        case 8:
          sc = *(BYTE*)src;
          color |= ((DWORD)pal[sc].rgbBlue);
          color |= ((DWORD)pal[sc].rgbGreen) << 8;
          color |= ((DWORD)pal[sc].rgbRed) << 16;
          src += 1;
          break;
        case 15:
          sc = *(WORD*)src;
          color |= ((sc&0x001f)<<3) | ((sc&0x001c)>>2);
          color |= ((sc&0x03e0)<<6) | ((sc&0x0380)<<1);
          color |= ((sc&0x7c00)<<9) | ((sc&0x7000)<<4);
          src += 2;
          break;
        case 16:
          sc = *(WORD*)src;
          color |= ((sc&0x001f)<<3) | ((sc&0x001c)>>2);
          color |= ((sc&0x07e0)<<5) | ((sc&0x0600)>>1);
          color |= ((sc&0xf800)<<8) | ((sc&0xe000)<<3);
          src += 2;
          break;
        case 24:
          color |= (src[2] << 16) | (src[1] << 8) | src[0];
          src += 3;
          break;
        }
        *dst++ = color;
        mbit >>= 1;
        if (!mbit) {
          msrc++;
          mbit = 0x80;
        }
      }
      /* if each line of the mask should be aligned, do it here.
       * However, it doesn't seem anywhere else takes care of that
       * anyway, so I haven't bothered to do it here either. */
    }
  }

  ximage = TSXCreateImage(display, visual, 32, ZPixmap,
                          0, NULL, ptr->nWidth, ptr->nHeight,
                          32, 0);
#if 0
  /* force 32bpp */
  ximage->depth = 32;
  ximage->bits_per_pixel = 32;
  ximage->bytes_per_line = ptr->nWidth * 4;
  ximage->red_mask = 0xff0000;
  ximage->green_mask = 0x00ff00;
  ximage->blue_mask = 0x0000ff;
#endif
  TRACE("D: %d, BPP: %d, M: %06lx/%06lx/%06lx\n", ximage->depth, ximage->bits_per_pixel,
        ximage->red_mask, ximage->green_mask, ximage->blue_mask);
  ximage->data = cimage;

  wine_tsx11_lock();
  pixmap = XCreatePixmap(display, root_window, ptr->nWidth, ptr->nHeight, 32);
  if (pixmap) {
    gc = XCreateGC(display, pixmap, 0, 0);
    XPutImage(display, pixmap, gc, ximage, 0, 0, 0, 0, ptr->nWidth, ptr->nHeight);
    XFreeGC(display, gc);
    picture = pXRenderCreatePicture(display, pixmap, cursor_format, 0, 0);
    XFreePixmap(display, pixmap);
  } else {
    ERR("cursor pixmap creation failed\n");
  }
  if (picture) {
    hotx = ptr->ptHotSpot.x;
    if (hotx < 0) hotx = 0;
    if (hotx > ptr->nWidth) hotx = ptr->nWidth;
    hoty = ptr->ptHotSpot.y;
    if (hoty < 0) hoty = 0;
    if (hoty > ptr->nHeight) hoty = ptr->nHeight;
    cursor = pXRenderCreateCursor(display, picture, hotx, hoty);
    pXRenderFreePicture(display, picture);
  } else {
    ERR("cursor picture creation failed\n");
  }
  if (!cursor) {
    ERR("cursor creation failed\n");
  }
  wine_tsx11_unlock();

  ximage->data = NULL;
  TSXDestroyImage(ximage);

  if (cimage != image)
    HeapFree(GetProcessHeap(), 0, cimage);

  return cursor;

#else
  assert(0);
  return None;
#endif
}

#else /* #ifdef HAVE_LIBXRENDER */

BOOL X11DRV_XRender_Init(void)
{
    WARN("XRender support not compiled in.\n");
    return FALSE;
}

void X11DRV_XRender_Finalize(void)
{
  assert(0);
  return;
}

BOOL X11DRV_XRender_SelectFont(DC *dc, HFONT hfont)
{
  assert(0);
  return FALSE;
}

void X11DRV_XRender_DeleteDC(DC *dc)
{
  assert(0);
  return;
}

BOOL X11DRV_XRender_ExtTextOut( DC *dc, INT x, INT y, UINT flags,
                                const RECT *lprect, LPCWSTR wstr, UINT count,
                                const INT *lpDx )
{
  assert(0);
  return FALSE;
}

void X11DRV_XRender_UpdateDrawable(DC *dc)
{
  assert(0);
  return;
}

Cursor X11DRV_XRender_CreateCursor( Display *display, CURSORICONINFO *ptr )
{
  assert(0);
  return None;
}

#endif
