/* Cockos SWELL (Simple/Small Win32 Emulation Layer for Linux/OSX)
   Copyright (C) 2006 and later, Cockos, Inc.

    This software is provided 'as-is', without any express or implied
    warranty.  In no event will the authors be held liable for any damages
    arising from the use of this software.

    Permission is granted to anyone to use this software for any purpose,
    including commercial applications, and to alter it and redistribute it
    freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
       claim that you wrote the original software. If you use this software
       in a product, an acknowledgment in the product documentation would be
       appreciated but is not required.
    2. Altered source versions must be plainly marked as such, and must not be
       misrepresented as being the original software.
    3. This notice may not be removed or altered from any source distribution.
  

    This file provides basic win32 GDI-->lice translation. 

*/

#ifdef SWELL_LICE_GDI
#ifndef SWELL_PROVIDED_BY_APP

#include "swell.h"
#include "swell-internal.h"

#include "../mutex.h"
#include "../ptrlist.h"
#include "../wdlcstring.h"
#include "../wdlutf8.h"

#include "swell-gdi-internalpool.h"


#ifdef SWELL_FREETYPE
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H

#ifdef SWELL_FONTCONFIG
#include <fontconfig/fontconfig.h>
#else
#include "../dirscan.h"
#endif

static bool s_freetype_failed;
static FT_Library s_freetype; // todo: TLS for multithread support? -- none of the text drawing is thread safe!

extern const char *swell_last_font_filename;

#ifndef SWELL_FREETYPE_CACHE_SIZE
#define SWELL_FREETYPE_CACHE_SIZE 80
#endif

class fontConfigCacheEnt
{
  public:
    fontConfigCacheEnt(const char *name, int flags, int w, int h, FT_Face face, const char *fndesc)
    {
      m_name = strdup(name);
      m_flags = flags;
      m_face = face;
      m_w=w;
      m_h=h;
      m_fndesc = strdup(fndesc);
      FT_Reference_Face(m_face);
    }
    ~fontConfigCacheEnt() 
    { 
      free(m_name); 
      free(m_fndesc);
      FT_Done_Face(m_face); 
    }

    char *m_name;
    char *m_fndesc;
    int m_flags, m_w, m_h;
    FT_Face m_face;
};


#ifdef SWELL_FONTCONFIG

static FcConfig *s_fontconfig;
const char *swell_enumFontFiles(int x)
{
  if (!s_fontconfig) return NULL;

  static FcPattern *pat;
  static FcObjectSet *prop;
  static FcFontSet *fonts;
  if (x<0)
  {
    if (fonts) FcFontSetDestroy(fonts);
    if (prop) FcObjectSetDestroy(prop);
    if (pat) FcPatternDestroy(pat);
    pat=NULL;
    prop=NULL;
    fonts=NULL;
    return NULL;
  }
  if (!pat)
  {
    pat = FcPatternCreate();
    prop = FcObjectSetBuild(FC_FAMILY, NULL);
    fonts = FcFontList(s_fontconfig,pat,prop);
  }
  if (fonts && x < fonts->nfont)
  {
    FcPattern *f = fonts->fonts[x];
    FcChar8 *fn = NULL;
    if (FcPatternGetString(f,FC_FAMILY,0,&fn) == FcResultMatch && fn && *fn) 
    return (const char *)fn;
  }
  
  return NULL;
}

#else

static WDL_PtrList<char> s_freetype_fontlist;
static WDL_PtrList<char> s_freetype_regfonts;

const char *swell_enumFontFiles(int x)
{
  const int n1 = s_freetype_regfonts.GetSize();
  if (x < n1) return s_freetype_regfonts.Get(x);
  return s_freetype_fontlist.Get(x-n1);
}


static void ScanFontDirectory(const char *path, int maxrec=3)
{
  WDL_DirScan ds;
  WDL_FastString fs;
  if (!ds.First(path)) do
  { 
    if (ds.GetCurrentFN()[0] != '.')
    {
      if (ds.GetCurrentIsDirectory())
      {
        if (maxrec>0 && 
            stricmp(ds.GetCurrentFN(),"type1") &&
            stricmp(ds.GetCurrentFN(),"cmap") &&
            stricmp(ds.GetCurrentFN(),"ghostscript") &&
            strcmp(ds.GetCurrentFN(),"X11")
           )
        {
          ds.GetCurrentFullFN(&fs);
          ScanFontDirectory(fs.Get(),maxrec-1);
        }
      }
      else
      {
        const char *ext = WDL_get_fileext(ds.GetCurrentFN());
        if (!stricmp(ext,".ttf") || !stricmp(ext,".otf"))
        {
          ds.GetCurrentFullFN(&fs);
          s_freetype_fontlist.Add(strdup(fs.Get()));
        }
      }
    }
  } while (!ds.Next());
}

static int sortByFilePart(const char **a, const char **b)
{
  return stricmp(WDL_get_filepart(*a),WDL_get_filepart(*b));
}

struct fontScoreMatched {
  int score1,score2;
  const char *fn;

  static int sortfunc(const void *a, const void *b)
  {
    const int v = ((const fontScoreMatched *)a)->score1 - ((const fontScoreMatched *)b)->score1;
    return v?v:((const fontScoreMatched *)a)->score2 - ((const fontScoreMatched *)b)->score2;
  }

};

static FT_Face MatchFont(const char *lfFaceName, int weight, int italic, int exact, char *matchFnOut, size_t matchFnOutSize)
{
  const int fn_len = strlen(lfFaceName), ntab=2;
  WDL_PtrList<char> *tab[ntab]= { &s_freetype_regfonts, &s_freetype_fontlist };
  int x;
  bool match;
  static WDL_TypedBuf<fontScoreMatched> matchlist;
  matchlist.Resize(0,false);

  for (x=0;x<ntab;x++) 
  {
    WDL_PtrList<char> *t = tab[x];
    int px = t->LowerBound(lfFaceName,&match,sortByFilePart);
    for (;;) 
    {
      const char *fn = t->Get(px++);
      if (!fn) break;
      const char *fnp = WDL_get_filepart(fn);
      if (strnicmp(fnp,lfFaceName,fn_len)) break;

      fontScoreMatched s;
      const char *residual = fnp + fn_len;
      const char *dash = strstr(residual,"-");
      const char *ext = WDL_get_fileext(residual);

      if (!dash)
      {
        if (!strnicmp(residual,"Bold",4) ||
            !strnicmp(residual,"Italic",6) ||
            !strnicmp(residual,"Light",5) ||
            !strnicmp(residual,"Oblique",7))
          dash = residual;
        else if (ext > residual && ext <= residual+2)
        {
          char c1 = residual[0],c2=residual[1];
          if (c1>0) c1=toupper_safe(c1);
          if (c2>0) c2=toupper_safe(c2);
          if ((c1 == 'B' || c1 == 'I' || c1 == 'L') &&
              (c2 == 'B' || c2 == 'I' || c2 == 'L' || c2 == '.'))
            dash=residual;
        }
      }

      s.fn = fn;
      s.score1 = (int)((dash?dash:ext)-residual); // characters between font and either "-" or "."

      if (exact > 0)
      {
        if (s.score1) continue;
      }
      else if (exact < 0 && !s.score1) 
      {
        continue;
      }

      s.score2 = 0;

      if (dash) { if (*dash == '-') dash++; }
      else dash=residual;

      while (*dash && *dash != '.')
      {
        if (*dash > 0 && isalnum_safe(*dash)) s.score2++;
        dash++;
      }

      if (WDL_stristr(residual,"Regular")) s.score2 -= 7; // ignore "Regular"
      if (italic && WDL_stristr(residual,"Italic")) s.score2 -= 6+7;
      else if (italic && WDL_stristr(residual,"Oblique")) s.score2 -= 7+3; // if Italic isnt available, use Oblique
      if (weight >= FW_BOLD && WDL_stristr(residual,"Bold")) s.score2 -= 4+7;
      else if (weight <= FW_LIGHT && WDL_stristr(residual,"Light")) s.score2 -= 5+7;

      if (ext > residual && ext <= residual+2)
      {
        char c1 = residual[0],c2=residual[1];
        if (c1>0) c1=toupper_safe(c1);
        if (c2>0) c2=toupper_safe(c2);

        if (weight >= FW_BOLD && (c1 == 'B' || c2 == 'B')) s.score2 -= 2;
        else if (weight <= FW_LIGHT && (c1 == 'L' || c2 == 'L')) s.score2 -= 2;
        if (italic && (c1 == 'I' || c2 == 'I')) s.score2 -= 2;
      }

      s.score2 = s.score2*ntab + x; 

      matchlist.Add(s);
    }
  } 
  if (matchlist.GetSize()>1)
    qsort(matchlist.Get(),matchlist.GetSize(),sizeof(fontScoreMatched),fontScoreMatched::sortfunc);

  for (x=0; x < matchlist.GetSize(); x ++)
  {
    const fontScoreMatched *s = matchlist.Get()+x;
 
    FT_Face face=NULL;
    //printf("trying '%s' for '%s' score %d,%d w %d i %d\n",s->fn,lfFaceName,s->score1,s->score2,weight,italic);
    FT_New_Face(s_freetype,s->fn,0,&face);
    if (face) 
    {
      lstrcpyn_safe(matchFnOut,s->fn,matchFnOutSize);
      return face;
    }
  }
  return NULL;
}
#endif // !SWELL_FONTCONFIG

#endif // SWELL_FREETYPE

HDC SWELL_CreateMemContext(HDC hdc, int w, int h)
{
  LICE_MemBitmap * bm = new LICE_MemBitmap(w,h);
  if (WDL_NOT_NORMALLY(!bm)) return 0;
  LICE_Clear(bm,LICE_RGBA(0,0,0,0));

  HDC__ *ctx=SWELL_GDP_CTX_NEW();
  ctx->surface = bm;
  ctx->surface_offs.x=0;
  ctx->surface_offs.y=0;
  ctx->dirty_rect_valid=false;

  SetTextColor(ctx,0);
  return ctx;
}


void SWELL_DeleteGfxContext(HDC ctx)
{
  HDC__ *ct=(HDC__ *)ctx;
  if (HDC_VALID(ct))
  {   
    delete ct->surface;
    ct->surface=0;
    SWELL_GDP_CTX_DELETE(ct);
  }
}

HPEN CreatePen(int attr, int wid, int col)
{
  return CreatePenAlpha(attr,wid,col,1.0f);
}

HBRUSH CreateSolidBrush(int col)
{
  return CreateSolidBrushAlpha(col,1.0f);
}

HPEN CreatePenAlpha(int attr, int wid, int col, float alpha)
{
  HGDIOBJ__ *pen=GDP_OBJECT_NEW();
  pen->type=TYPE_PEN;
  pen->wid=wid<0?0:wid;
  pen->alpha = alpha;
  pen->color=LICE_RGBA_FROMNATIVE(col);
  return pen;
}
HBRUSH  CreateSolidBrushAlpha(int col, float alpha)
{
  HGDIOBJ__ *brush=GDP_OBJECT_NEW();
  brush->type=TYPE_BRUSH;
  brush->color=LICE_RGBA_FROMNATIVE(col);
  brush->alpha = alpha;
  brush->wid=0; 
  return brush;
}

void SetBkColor(HDC ctx, int col)
{
  HDC__ *ct=(HDC__ *)ctx;
  if (!HDC_VALID(ct)) return;
  ct->curbkcol=LICE_RGBA_FROMNATIVE(col,255);
}

void SetBkMode(HDC ctx, int col)
{
  HDC__ *ct=(HDC__ *)ctx;
  if (!HDC_VALID(ct)) return;
  ct->curbkmode=col;
}

HGDIOBJ GetStockObject(int wh)
{
  switch (wh)
  {
    case NULL_BRUSH:
    {
      static HGDIOBJ__ br={0,};
      br.type=TYPE_BRUSH;
      br.wid=-1;
      return &br;
    }
    case NULL_PEN:
    {
      static HGDIOBJ__ pen={0,};
      pen.type=TYPE_PEN;
      pen.wid=-1;
      return &pen;
    }
  }
  WDL_ASSERT(false);
  return 0;
}

HFONT CreateFont(int lfHeight, int lfWidth, int lfEscapement, int lfOrientation, int lfWeight, char lfItalic, 
  char lfUnderline, char lfStrikeOut, char lfCharSet, char lfOutPrecision, char lfClipPrecision, 
         char lfQuality, char lfPitchAndFamily, const char *lfFaceName)
{
  HGDIOBJ__ *font = GDP_OBJECT_NEW();
  font->typedata = NULL;
  font->type=TYPE_FONT;
  font->alpha = 1.0f;
#ifdef SWELL_FREETYPE
  if (!s_freetype_failed && !s_freetype) 
  {
    s_freetype_failed = !!FT_Init_FreeType(&s_freetype);
    if (s_freetype)
    {
#ifdef SWELL_FONTCONFIG
      if (!s_fontconfig) s_fontconfig = FcInitLoadConfigAndFonts();
#else
      ScanFontDirectory("/usr/share/fonts");

      qsort(s_freetype_fontlist.GetList(),s_freetype_fontlist.GetSize(),sizeof(const char *),(int (*)(const void *,const void*))sortByFilePart);
#endif
    }
  }
  if (lfWidth<0) lfWidth=-lfWidth;
  if (lfHeight<0) lfHeight=-lfHeight;

  static WDL_PtrList<fontConfigCacheEnt> cache;
  const int cache_flag = wdl_max(lfWeight,0) | (lfItalic ? (1<<30) : 0);
  FT_Face face=NULL;
  for (int x=0;x<cache.GetSize();x++)
  {
    fontConfigCacheEnt *ent = cache.Get(x);
    if (ent->m_flags==cache_flag && 
        ent->m_w == lfWidth && 
        ent->m_h == lfHeight && 
        !strcmp(ent->m_name,lfFaceName?lfFaceName:""))
    {
      face = ent->m_face;
      swell_last_font_filename = ent->m_fndesc;
      FT_Reference_Face(face);
      if (x < cache.GetSize()-1) 
      {
        cache.Delete(x);
        cache.Add(ent); // make this cache entry most recent
      }
      break;
    }
  }

  if (!face && s_freetype)
  {
    int face_idx=0;
    char face_fn[1024];
    face_fn[0]=0;
#ifdef SWELL_FONTCONFIG
    if (!face && s_fontconfig)
    {
      FcPattern *pat = FcPatternCreate();
      if (pat)
      {
        if (lfFaceName && *lfFaceName) FcPatternAddString(pat,FC_FAMILY,(FcChar8*)lfFaceName);
        if (lfWeight > 0)
        {
          int wt;
          if (lfWeight >= FW_HEAVY) wt=FC_WEIGHT_HEAVY;
          else if (lfWeight >= FW_EXTRABOLD) wt=FC_WEIGHT_EXTRABOLD;
          else if (lfWeight >= FW_BOLD) wt=FC_WEIGHT_BOLD;
          else if (lfWeight >= FW_SEMIBOLD) wt=FC_WEIGHT_SEMIBOLD;
          else if (lfWeight >= FW_MEDIUM) wt=FC_WEIGHT_MEDIUM;
          else if (lfWeight >= FW_NORMAL) wt=FC_WEIGHT_NORMAL;
          else if (lfWeight >= FW_LIGHT) wt=FC_WEIGHT_LIGHT;
          else if (lfWeight >= FW_EXTRALIGHT) wt=FC_WEIGHT_EXTRALIGHT;
          else wt=FC_WEIGHT_THIN;
          FcPatternAddInteger(pat,FC_WEIGHT,wt);
        }
        if (lfItalic)
          FcPatternAddInteger(pat,FC_SLANT,FC_SLANT_ITALIC);
        FcConfigSubstitute(s_fontconfig,pat,FcMatchPattern);
        FcDefaultSubstitute(pat);
        FcResult result;
        FcPattern *hit = FcFontMatch(s_fontconfig,pat,&result);
        if (hit)
        {
          FcChar8 *fn = NULL;
          if (FcPatternGetString(hit,FC_FILE,0,&fn) == FcResultMatch && fn && *fn) 
          {
            if (FcPatternGetInteger(hit,FC_INDEX,0,&face_idx) != FcResultMatch) face_idx=0;
            FT_New_Face(s_freetype,(const char *)fn,face_idx,&face);
            if (face) lstrcpyn_safe(face_fn,(const char *)fn,sizeof(face_fn));
          }
          FcPatternDestroy(hit);
        }
        FcPatternDestroy(pat);
      }
    }
#else

    if (!face && lfFaceName && *lfFaceName) face = MatchFont(lfFaceName,lfWeight,lfItalic,0, face_fn, sizeof(face_fn));

    if (!face)
    {
      static const char *fallbacklist[2];
      const int wl = (lfFaceName && (
                        !strnicmp(lfFaceName,"Courier",7) ||
                        !strnicmp(lfFaceName,"Fixed",5)
                        )) ? 1 : 0;
      if (!fallbacklist[wl])
      {
        static const char *ent[2] = { "ft_font_fallback", "ft_font_fallback_fixedwidth" };
        static const char *def[2] = { 
          "// LiberationSans Cantarell FreeSans DejaVuSans NotoSans Oxygen Arial Verdana", 
          "// LiberationMono FreeMono DejaVuSansMono NotoMono OxygenMono Courier" 
        };
        char tmp[1024];
        GetPrivateProfileString(".swell",ent[wl],"",tmp,sizeof(tmp),"");
        if (!tmp[0])
          WritePrivateProfileString(".swell",ent[wl],def[wl],"");
        const char *rp = tmp;
        while (*rp == ' ' || *rp == '\t') rp++;
        if (!*rp || *rp == '/') rp = def[wl] + 3;

        char *b = (char*) malloc(strlen(rp) + 2);
        fallbacklist[wl] = b ? b : "FreeSans\0";
        if (b)
        {
          for(;;)
          {
            while (*rp == ' ' || *rp == '\t') rp++;
            while (*rp && *rp != ' ' && *rp != '\t') *b++ = *rp++;
            *b++=0;
            if (!*rp) break;
          }
          *b++=0;
        }
      }
      for (int exact=0;exact<2 && !face;exact++)
      {
        const char *l = fallbacklist[wl];
        while (*l && !face)
        {
          face = MatchFont(l,lfWeight,lfItalic,exact?-1:1, face_fn, sizeof(face_fn));
          l += strlen(l)+1;
        }
      }
    }
#endif

    if (face)
    {
      if (face_idx) snprintf_append(face_fn,sizeof(face_fn)," <%d>",face_idx);
      fontConfigCacheEnt *ce = new fontConfigCacheEnt(lfFaceName?lfFaceName:"",cache_flag,lfWidth,lfHeight,face, face_fn);
      cache.Add(ce);
      if (cache.GetSize()>SWELL_FREETYPE_CACHE_SIZE) cache.Delete(0,true);
      swell_last_font_filename = ce->m_fndesc;

      FT_Set_Char_Size(face,lfWidth*64, lfHeight*64,0,0); // 72dpi
    }
  }
  font->typedata = face;
#endif
 
  return font;
}


HFONT CreateFontIndirect(LOGFONT *lf)
{
  return CreateFont(lf->lfHeight, lf->lfWidth,lf->lfEscapement, lf->lfOrientation, lf->lfWeight, lf->lfItalic, 
                    lf->lfUnderline, lf->lfStrikeOut, lf->lfCharSet, lf->lfOutPrecision,lf->lfClipPrecision, 
                    lf->lfQuality, lf->lfPitchAndFamily, lf->lfFaceName);
}

int GetTextFace(HDC ctx, int nCount, LPTSTR lpFaceName)
{
  if (lpFaceName && nCount>0) lpFaceName[0]=0;
#ifdef SWELL_FREETYPE
  HDC__ *ct=(HDC__*)ctx;
  if (!HDC_VALID(ct) || WDL_NOT_NORMALLY(nCount<1 || !lpFaceName) || !ct->curfont) return 0;

  const FT_FaceRec *p = (const FT_FaceRec *)ct->curfont->typedata;
  if (p)
  {
    lstrcpyn_safe(lpFaceName, p->family_name, nCount);
    return 1;
  }
#endif
  return 0;
}

void DeleteObject(HGDIOBJ pen)
{
  if (HGDIOBJ_VALID(pen))
  {
    HGDIOBJ__ *p=(HGDIOBJ__ *)pen;
    if (--p->additional_refcnt < 0)
    {
      if (WDL_NORMALLY(p->type == TYPE_PEN || p->type == TYPE_BRUSH || p->type == TYPE_FONT || p->type == TYPE_BITMAP))
      {
        if (p->type == TYPE_FONT)
        {
#ifdef SWELL_FREETYPE
          if (p->typedata)
          {
            FT_Done_Face((FT_Face)p->typedata);
            p->typedata = 0;
          }
#endif
        }
        else if (p->type == TYPE_PEN || p->type == TYPE_BRUSH)
        {
          if (p->wid<0) return;
        }
        else if (p->type == TYPE_BITMAP)
        { 
          if (p->wid>0) delete (LICE_IBitmap *)p->typedata;
          p->typedata = NULL;
        }
  
        GDP_OBJECT_DELETE(p);
      }
    }
  }
}


HGDIOBJ SelectObject(HDC ctx, HGDIOBJ pen)
{
  HDC__ *c=(HDC__ *)ctx;
  HGDIOBJ__ *p= pen;
  HGDIOBJ__ **mod=0;
  if (!HDC_VALID(c)||WDL_NOT_NORMALLY(!p)) return 0;
  
  if (p == (HGDIOBJ__ *)TYPE_PEN) mod=&c->curpen;
  else if (p == (HGDIOBJ__ *)TYPE_BRUSH) mod=&c->curbrush;
  else if (p == (HGDIOBJ__ *)TYPE_FONT) mod=&c->curfont;

  if (mod)
  {
    HGDIOBJ__ *np=*mod;
    *mod=0;
    return np?np:p;
  }
  
  if (!HGDIOBJ_VALID(p)) return 0;

  if (p->type == TYPE_PEN) mod=&c->curpen;
  else if (p->type == TYPE_BRUSH) mod=&c->curbrush;
  else if (p->type == TYPE_FONT) mod=&c->curfont;
  else return 0;
  
  HGDIOBJ__ *op=*mod;
  if (!op) op=(HGDIOBJ__ *)(INT_PTR)p->type;
  if (op != p)
  {
    *mod=p;
  }
  return op;
}


static void swell_DirtyContext(HDC__ *out, int x1, int y1, int x2, int y2)
{
  if (x2<x1) { int t=x1; x1=x2; x2=t; } 
  if (y2<y1) { int t=y1; y1=y2; y2=t; } 
  x1+=out->surface_offs.x;
  x2+=out->surface_offs.x;
  y1+=out->surface_offs.y;
  y2+=out->surface_offs.y;

  if (!out->dirty_rect_valid)
  { 
    out->dirty_rect_valid=true;
    out->dirty_rect.left = x1;
    out->dirty_rect.top = y1;
    out->dirty_rect.right = x2;
    out->dirty_rect.bottom = y2;
  } 
  else
  {
    if (out->dirty_rect.left > x1) out->dirty_rect.left=x1;
    if (out->dirty_rect.top > y1) out->dirty_rect.top = y1;
    if (out->dirty_rect.right < x2) out->dirty_rect.right = x2;
    if (out->dirty_rect.bottom < y2) out->dirty_rect.bottom = y2;
  }
}


void SWELL_FillRect(HDC ctx, const RECT *r, HBRUSH br)
{
  HDC__ *c=(HDC__ *)ctx;
  HGDIOBJ__ *b=(HGDIOBJ__ *) br;
  if (!HDC_VALID(c) || !HGDIOBJ_VALID(b,TYPE_BRUSH)) return;
  if (!c->surface) return;

  if (b->wid<0) return;
  LICE_FillRect(c->surface,
      r->left+c->surface_offs.x,
      r->top+c->surface_offs.y,
      r->right-r->left,r->bottom-r->top,b->color,b->alpha,LICE_BLIT_MODE_COPY);
  swell_DirtyContext(ctx,r->left,r->top,r->right,r->bottom);
}

void RoundRect(HDC ctx, int x, int y, int x2, int y2, int xrnd, int yrnd)
{
	xrnd/=3;
	yrnd/=3;
	POINT pts[10]={ // todo: curves between edges
		{x,y+yrnd},
		{x+xrnd,y},
		{x2-xrnd,y},
		{x2,y+yrnd},
		{x2,y2-yrnd},
		{x2-xrnd,y2},
		{x+xrnd,y2},
		{x,y2-yrnd},		
    {x,y+yrnd},
		{x+xrnd,y},
};
	
	WDL_GDP_Polygon(ctx,pts,sizeof(pts)/sizeof(pts[0]));
}

void Ellipse(HDC ctx, int l, int t, int r, int b)
{
  HDC__ *c=(HDC__ *)ctx;
  if (!HDC_VALID(c) || !c->surface) return;
  
  swell_DirtyContext(ctx,l,t,r,b);
  
  l += c->surface_offs.x;
  t += c->surface_offs.y;
  r += c->surface_offs.x;
  b += c->surface_offs.y;

  int rad = wdl_min(r-l, b-t)/2; // todo: actual ellipse, for now just circles

  bool wantPen = HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid >= 0;
  if (HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid >= 0)
  {
    int use_rad = rad;
    if (use_rad > 0) LICE_FillCircle(c->surface,l+use_rad,t+use_rad,use_rad,c->curbrush->color,c->curbrush->alpha,LICE_BLIT_MODE_COPY,!wantPen);
  }
  if (wantPen)
  {
    LICE_Circle(c->surface,l+rad,t+rad,rad,c->curpen->color,c->curpen->alpha,LICE_BLIT_MODE_COPY,true);
  }
}

void Rectangle(HDC ctx, int l, int t, int r, int b)
{
  HDC__ *c=(HDC__ *)ctx;
  if (!HDC_VALID(c) || !c->surface) return;
  
  //CGRect rect=CGRectMake(l,t,r-l,b-t);

  swell_DirtyContext(ctx,l,t,r,b);
  
  l += c->surface_offs.x;
  t += c->surface_offs.y;
  r += c->surface_offs.x;
  b += c->surface_offs.y;

  if (HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid >= 0)
  {
    LICE_FillRect(c->surface,l,t,r-l,b-t,c->curbrush->color,c->curbrush->alpha,LICE_BLIT_MODE_COPY);
  }
  if (HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid >= 0)
  {
    if (r>l+1 && b>t+1)
      LICE_DrawRect(c->surface,l,t,r-l-1,b-t-1,c->curpen->color,c->curpen->alpha,LICE_BLIT_MODE_COPY);
  }
}

void Polygon(HDC ctx, POINT *pts, int npts)
{
  HDC__ *c=(HDC__ *)ctx;
  if (!HDC_VALID(c) || !c->surface) return;
  const bool fill=HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH)&&c->curbrush->wid>=0;
  const bool outline = HGDIOBJ_VALID(c->curpen,TYPE_PEN)&&c->curpen->wid>=0;
  if ((!fill && !outline) || npts<2 || !pts) return;

  const int dx=c->surface_offs.x;
  const int dy=c->surface_offs.y;

  int minx=c->surface->getWidth()+1, maxx=0;
  int miny=c->surface->getHeight()+1, maxy=0;

  if (fill)
  {
    int _tmp[256], *tmp=_tmp;
    if (npts>128)
    {
      tmp = (int *)malloc(npts*2*sizeof(int));
    }
    if (tmp)
    {
      for (int x = 0; x < npts; x ++)
      {
        const int xp = pts[x].x, yp = pts[x].y;
        if (xp < minx) minx=xp;
        if (xp > maxx) maxx=xp;
        if (yp < miny) miny=yp;
        if (yp > maxy) maxy=yp;
        tmp[x] = xp + dx;
        tmp[x+npts] = yp + dy;
      }
      LICE_FillConvexPolygon(c->surface,tmp, tmp+npts,npts,
        c->curbrush->color,c->curbrush->alpha,LICE_BLIT_MODE_COPY);
    }
    if (tmp != _tmp) free(tmp);
  }
  if (outline)
  {
    int lx=0,ly=0,sx=0,sy=0;
    for (int x = 0; x < npts; x ++)
    {
      const int xp = pts[x].x, yp = pts[x].y;
      if (xp < minx) minx=xp;
      if (xp > maxx) maxx=xp;
      if (yp < miny) miny=yp;
      if (yp > maxy) maxy=yp;
      if (x)
        LICE_Line(c->surface,xp+dx,yp+dy,lx+dx,ly+dy,c->curpen->color,c->curpen->alpha,LICE_BLIT_MODE_COPY,true);
      else { sx=xp; sy=yp; }
      lx=xp;
      ly=yp;
    }
    LICE_Line(c->surface,sx+dx,sy+dy,lx+dx,ly+dy,c->curpen->color,c->curpen->alpha,LICE_BLIT_MODE_COPY,true);
  }

  if (maxx>minx && maxy>miny)
    swell_DirtyContext(ctx, minx,miny,maxx,maxy);
}

void MoveToEx(HDC ctx, int x, int y, POINT *op)
{
  HDC__ *c=(HDC__ *)ctx;
  if (!HDC_VALID(c)) return;
  if (op) 
  { 
    op->x = (int) (c->lastpos_x);
    op->y = (int) (c->lastpos_y);
  }
  c->lastpos_x=(float)x;
  c->lastpos_y=(float)y;
}

void PolyBezierTo(HDC ctx, POINT *pts, int np)
{
  HDC__ *c=(HDC__ *)ctx;
  if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0||np<3) return;
  
  int x; 
  float xp=c->lastpos_x,yp=c->lastpos_y;
  for (x = 0; x < np-2; x += 3)
  {
      // todo
      xp=(float)pts[x+2].x;
      yp=(float)pts[x+2].y;    
  }
  c->lastpos_x=(float)xp;
  c->lastpos_y=(float)yp;
}


void SWELL_LineTo(HDC ctx, int x, int y)
{
  HDC__ *c=(HDC__ *)ctx;
  if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0) return;

  float fx=(float)x,fy=(float)y;

  int dx=c->surface_offs.x;
  int dy=c->surface_offs.y;
  int lx = (int)c->lastpos_x, ly = (int) c->lastpos_y;
  if (c->surface) 
    LICE_Line(c->surface,x+dx,y+dy,lx+dx,ly+dy,c->curpen->color,c->curpen->alpha,LICE_BLIT_MODE_COPY,false);
  
  c->lastpos_x=fx;
  c->lastpos_y=fy;

  if (lx<x) { int a=x; x=lx; lx=a; }
  if (ly<y) { int a=y; y=ly; ly=a; }

  swell_DirtyContext(ctx, x-1,y-1,lx+1,ly+1);
 
}

void PolyPolyline(HDC ctx, POINT *pts, DWORD *cnts, int nseg)
{
  HDC__ *c=(HDC__ *)ctx;
  if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0||nseg<1) return;

  while (nseg-->0)
  {
    DWORD cnt=*cnts++;
    if (!cnt) continue;
    if (!--cnt) { pts++; continue; }
    
 //   CGContextMoveToPoint(c->ctx,(float)pts->x,(float)pts->y);
    pts++;
    
    while (cnt--)
    {
//      CGContextAddLineToPoint(c->ctx,(float)pts->x,(float)pts->y);
      pts++;
    }
  }
//  CGContextStrokePath(c->ctx);
}
void *SWELL_GetCtxGC(HDC ctx)
{
  HDC__ *ct=(HDC__ *)ctx;
  if (!HDC_VALID(ct)) return 0;
  return NULL; 
}


void SWELL_SetPixel(HDC ctx, int x, int y, int c)
{
  HDC__ *ct=(HDC__ *)ctx;
  if (!HDC_VALID(ct) || !ct->surface) return;
  LICE_PutPixel(ct->surface,x+ct->surface_offs.x, y+ct->surface_offs.y, LICE_RGBA_FROMNATIVE(c,255),1.0f,LICE_BLIT_MODE_COPY);
  swell_DirtyContext(ct,x,y,x+1,y+1);
}

HFONT SWELL_GetDefaultFont()
{
  static HFONT def;
  if (!def)
  {
    def = CreateFont(g_swell_ctheme.default_font_size,0,0,0,FW_NORMAL,0,0,0,0,0,0,0,0,g_swell_deffont_face);
  }
  return def;
}

BOOL GetTextMetrics(HDC ctx, TEXTMETRIC *tm)
{
  HDC__ *ct=(HDC__ *)ctx;
  if (tm) // give some sane defaults
  {
    tm->tmInternalLeading=0;
    tm->tmAscent=8;
    tm->tmDescent=0;
    tm->tmHeight=8;
    tm->tmAveCharWidth = 8;
  }
  if (!HDC_VALID(ct)||WDL_NOT_NORMALLY(!tm)) return 0;

#ifdef SWELL_FREETYPE
  HGDIOBJ__  *font  = HGDIOBJ_VALID(ct->curfont,TYPE_FONT) ? ct->curfont : SWELL_GetDefaultFont();
  if (font && font->typedata)
  {
    FT_Face face=(FT_Face) font->typedata;
    tm->tmAscent = face->size->metrics.ascender/64;
    tm->tmDescent = -face->size->metrics.descender/64;
    tm->tmHeight = (face->size->metrics.ascender - face->size->metrics.descender)/64;
    tm->tmAveCharWidth = face->size->metrics.height / 112;

    tm->tmInternalLeading = (face->size->metrics.ascender + face->size->metrics.descender - face->size->metrics.height)/64;
    if (tm->tmInternalLeading<0) tm->tmInternalLeading=0;
  }
#endif
  
  return 1;
}


int DrawText(HDC ctx, const char *buf, int buflen, RECT *r, int align)
{
  WDL_ASSERT((align & DT_SINGLELINE) || !(align & (DT_VCENTER | DT_BOTTOM)));
  HDC__ *ct=(HDC__ *)ctx;
  if (WDL_NOT_NORMALLY(!r)) return 0;

  int lineh = 8;
  int charw = 8;

  HGDIOBJ__  *font  = NULL;
  int ascent=8, descent=0;
#ifdef SWELL_FREETYPE
  font  = HDC_VALID(ct) && HGDIOBJ_VALID(ct->curfont,TYPE_FONT) ? ct->curfont : SWELL_GetDefaultFont();
  FT_Face face = NULL;
  if (font && font->typedata) 
  {
    face=(FT_Face)font->typedata;
    lineh = face->size->metrics.height/64;
    ascent = face->size->metrics.ascender/64;
    descent = face->size->metrics.descender/64;
    charw = face->size->metrics.height / 112;
  }
#endif

  if (align&DT_CALCRECT)
  {
    int xpos=0;
    int ypos=0;

    r->bottom=r->top;
    bool in_prefix=false;
    while (buflen && *buf) // if buflen<0, go forever
    {
      int c=0, charlen = wdl_utf8_parsechar(buf,&c);
      buf+=charlen;
      if (buflen > 0) 
      {
        buflen -= charlen;
        if (buflen < 0) buflen=0;
      }
      if (!c) break;

      if (c=='&' && !in_prefix && !(align&DT_NOPREFIX)) 
      {
        in_prefix = true;
        continue;
      }
      in_prefix=false;

      if (c == '\n' && (align & DT_SINGLELINE)) c=' ';
 
      if (c == '\n') { ypos += lineh; xpos=0; }
      else if (c != '\r')
      {
        if (font)
        {
#ifdef SWELL_FREETYPE
          if (c != '\t' && !FT_Load_Char(face, c, FT_LOAD_DEFAULT) && face->glyph)
          {
            // measure character
            FT_GlyphSlot g = face->glyph;
            int rext = xpos;
            if ((align&(DT_BOTTOM|DT_VCENTER|DT_CENTER|DT_RIGHT))!=DT_RIGHT)
              rext += (g->metrics.width + g->metrics.horiBearingX)/64;

            xpos += g->metrics.horiAdvance/64;
            if (rext<xpos) rext=xpos;
            if (r->left+rext > r->right) r->right = r->left+rext;

            int bext = r->top + ypos + ascent - descent;
            if (bext > r->bottom) r->bottom = bext;
            continue;
          }
#endif
        }
        xpos += c=='\t' ? charw*5 : charw;
        int bext = r->top + ypos + ascent - descent;
        if (bext > r->bottom) r->bottom = bext;
        if (r->left+xpos>r->right) r->right=r->left+xpos;
      }
    }
    return r->bottom-r->top;
  }
  if (!HDC_VALID(ct)) return 0;

  RECT use_r = *r;
  if ((align & DT_VCENTER) && use_r.top > use_r.bottom)
  {
    use_r.top = r->bottom;
    use_r.bottom = r->top;
  }

  use_r.left += ct->surface_offs.x;
  use_r.right += ct->surface_offs.x;
  use_r.top += ct->surface_offs.y;
  use_r.bottom += ct->surface_offs.y;

  int xpos = use_r.left;
  int ypos = use_r.top;
  if (align&(DT_CENTER|DT_VCENTER|DT_RIGHT|DT_BOTTOM))
  {
    RECT tr={0,};
    DrawText(ctx,buf,buflen,&tr,align|DT_CALCRECT);

    if (align&DT_CENTER)
      xpos -= ((tr.right-tr.left) - (use_r.right-use_r.left))/2;
    else if (align&DT_RIGHT)
      xpos = use_r.right - (tr.right-tr.left);

    if (align&DT_VCENTER)
      ypos -= ((tr.bottom-tr.top) - (use_r.bottom-use_r.top))/2;
    else if (align&DT_BOTTOM)
      ypos = use_r.bottom - (tr.bottom-tr.top);
  }
  LICE_IBitmap *surface = ct->surface;
  int fgcol = ct->cur_text_color_int;
  int bgcol = ct->curbkcol;
  int bgmode = ct->curbkmode;


  int clip_x1=wdl_max(use_r.left,0), clip_y1 = wdl_max(use_r.top,0);
  int clip_w=0, clip_h=0;
  if (surface)
  {
    clip_w = wdl_min(use_r.right,surface->getWidth())-clip_x1;
    clip_h = wdl_min(use_r.bottom,surface->getHeight())-clip_y1;
    if (clip_w<0)clip_w=0;
    if (clip_h<0)clip_h=0;
  }

  LICE_SubBitmap clipbm(surface,clip_x1,clip_y1,clip_w,clip_h);
  if (surface && !(align&DT_NOCLIP)) { surface = &clipbm; xpos-=clip_x1; ypos-=clip_y1; }

  int left_xpos = xpos,start_ypos = ypos, max_ypos=ypos,max_xpos=0;
  bool in_prefix=false;

  while (buflen && *buf)
  {
    int c=0, charlen = wdl_utf8_parsechar(buf,&c);
    if (buflen>0)
    {
      buflen -= charlen;
      if (buflen<0) buflen=0;
    }
    buf+=charlen;

    bool doUl=in_prefix;

    if (c=='&' && !in_prefix && !(align&DT_NOPREFIX)) 
    {
      in_prefix = true;
      continue;
    }
    in_prefix=false;

    if (c == '\n' && (align & DT_SINGLELINE)) c=' ';

    if (c=='\n' && !(align&DT_SINGLELINE)) { xpos=left_xpos; ypos+=lineh; }
    else if (c=='\r')  {} 
    else 
    {
      bool needr=true;
      if (font)
      {
#ifdef SWELL_FREETYPE
        if (c != '\t' && !FT_Load_Char(face, c, FT_LOAD_RENDER) && face->glyph)
        {
          FT_GlyphSlot g = face->glyph;
          const int ha = g->metrics.horiAdvance/64;
          if (bgmode==OPAQUE) LICE_FillRect(surface,xpos,ypos,ha,(align & DT_SINGLELINE) ? (ascent-descent) : lineh,bgcol,1.0f,LICE_BLIT_MODE_COPY);
  
          if (g->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
          {
            LICE_DrawMonoGlyph(surface,xpos+g->bitmap_left,ypos+ascent-g->bitmap_top,fgcol,(const unsigned char*)g->bitmap.buffer,g->bitmap.width,g->bitmap.pitch,g->bitmap.rows,1.0f,LICE_BLIT_MODE_COPY);
          }
          else  // FT_PIXEL_MODE_GRAY (hopefully!)
          {
            LICE_DrawGlyphEx(surface,xpos+g->bitmap_left,ypos+ascent-g->bitmap_top,fgcol,(LICE_pixel_chan *)g->bitmap.buffer,g->bitmap.width,g->bitmap.pitch,g->bitmap.rows,1.0f,LICE_BLIT_MODE_COPY);
          }
          if (doUl) 
          {
            int xw = g->metrics.width/64;
            if (xw > 1) xw--;
            LICE_Line(surface,xpos + g->metrics.horiBearingX/64,ypos+ascent+1,
                              xpos + xw,ypos+ascent+1,fgcol,1.0f,LICE_BLIT_MODE_COPY,false);
          }
  
          int rext = xpos + (g->metrics.width + g->metrics.horiBearingX)/64;
          if (rext<=xpos) rext=xpos + ha;
          if (rext > max_xpos) max_xpos=rext;
          xpos += ha;
          const int bext = ypos + ascent-descent;
          if (max_ypos < bext) max_ypos=bext;
          needr=false;
        }
#endif
      }

      if (needr)
      {
        if (c=='\t') 
        {
          if (bgmode==OPAQUE) LICE_FillRect(surface,xpos,ypos,charw*5,(align & DT_SINGLELINE) ? (ascent-descent) : lineh,bgcol,1.0f,LICE_BLIT_MODE_COPY);
          xpos+=charw*5;
         
          const int bext = ypos+ascent-descent;
          if (max_ypos < bext) max_ypos=bext;
        }
        else 
        {
          if (bgmode==OPAQUE) LICE_FillRect(surface,xpos,ypos,charw,(align & DT_SINGLELINE) ? (ascent-descent) : lineh,bgcol,1.0f,LICE_BLIT_MODE_COPY);
          LICE_DrawChar(surface,xpos,ypos,c,fgcol,1.0f,LICE_BLIT_MODE_COPY);
          if (doUl) LICE_Line(surface,xpos,ypos+(ascent-descent)+1,xpos+charw,ypos+(ascent-descent)+1,fgcol,1.0f,LICE_BLIT_MODE_COPY,false);
  
          const int bext=ypos+ascent-descent+(doUl ? 2:1);
          if (max_ypos < bext) max_ypos=bext;
          xpos+=charw;
        }
      }
    }
    if(xpos>max_xpos)max_xpos=xpos;
  }
  if (surface==&clipbm)
    swell_DirtyContext(ct,clip_x1+left_xpos,clip_y1+start_ypos,clip_x1+max_xpos,clip_y1+max_ypos);
  else
    swell_DirtyContext(ct,left_xpos,start_ypos,max_xpos,max_ypos);
  return max_ypos - start_ypos;
}


int GetTextColor(HDC ctx)
{
  HDC__ *ct=(HDC__ *)ctx;
  if (!HDC_VALID(ct)) return -1;
  return ct->cur_text_color_int;
}
void SetTextColor(HDC ctx, int col)
{
  HDC__ *ct=(HDC__ *)ctx;
  if (!HDC_VALID(ct)) return;
  ct->cur_text_color_int = LICE_RGBA_FROMNATIVE(col,255);
  
}


////////// todo: some sort of HICON emul

HICON LoadNamedImage(const char *name, bool alphaFromMask)
{
#ifdef SWELL_TARGET_GDK
  char buf[1024];
  GdkPixbuf *pb = NULL;
  if (strstr(name,"/")) 
  {
    lstrcpyn_safe(buf,name,sizeof(buf));
    pb = gdk_pixbuf_new_from_file(buf,NULL);
  }
  else
  {
    GetModuleFileName(NULL,buf,sizeof(buf));
    WDL_remove_filepart(buf);
    snprintf_append(buf,sizeof(buf),"/Resources/%s.png",name);
    pb = gdk_pixbuf_new_from_file(buf,NULL);
    if (!pb)
    {
      WDL_remove_fileext(buf);
      lstrcatn(buf,".ico",sizeof(buf));
      pb = gdk_pixbuf_new_from_file(buf,NULL);
    }
    if (!pb)
    {
      WDL_remove_fileext(buf);
      lstrcatn(buf,".bmp",sizeof(buf));
      pb = gdk_pixbuf_new_from_file(buf,NULL);
    }
  }
  if (pb)
  {
    HGDIOBJ__ *ret=NULL;
    const int w = gdk_pixbuf_get_width(pb), h = gdk_pixbuf_get_height(pb);
    const int bpc = gdk_pixbuf_get_bits_per_sample(pb), chan = gdk_pixbuf_get_n_channels(pb);
    const int alpha = gdk_pixbuf_get_has_alpha(pb);
    const guchar *rd = gdk_pixbuf_get_pixels(pb);
    if (bpc == 8 && (chan == 4 || chan == 3) && w > 0 && h>0 && rd)
    {
      LICE_MemBitmap *bm = new LICE_MemBitmap(w,h);
      LICE_pixel_chan *wr = (LICE_pixel_chan*)bm->getBits();
      if (wr)
      {
        const int rdadv = gdk_pixbuf_get_rowstride(pb);
        const int wradv = bm->getRowSpan()*4;
        const unsigned char alphamod = !alpha ? 255 : 0;
        int y;
        for (y=0;y <h; y++)
        {
          int x;
          for (x=0;x<w;x++)
          {
            wr[LICE_PIXEL_R] = rd[0];
            wr[LICE_PIXEL_G] = rd[1];
            wr[LICE_PIXEL_B] = rd[2];
            wr[LICE_PIXEL_A] = chan==4 ? (rd[LICE_PIXEL_A] | alphamod) : 255;
            wr+=4; rd+=chan;
          }
          wr+=wradv-w*4;
          rd+=rdadv-w*chan;
        }
        ret=GDP_OBJECT_NEW();
        ret->type=TYPE_BITMAP;
        ret->alpha = 1.0f;
        ret->wid=1;
        ret->typedata = bm;
      }
      else delete wr;
    }
    g_object_unref(pb);
    return ret;
  }
  

#endif
  return 0; // todo
}

void DrawImageInRect(HDC hdcOut, HICON in, const RECT *r)
{
  HDC__ *out = (HDC__ *)hdcOut;
  if (!HDC_VALID(out) || !HGDIOBJ_VALID(in,TYPE_BITMAP) || !out->surface || !in->typedata) return;

  const int x = r->left, y=r->top, w=r->right-x, h=r->bottom-y;
  LICE_IBitmap *src=(LICE_IBitmap *)in->typedata;
  LICE_ScaledBlit(out->surface,src,
            x+out->surface_offs.x,y+out->surface_offs.y,w,h,
            0,0, src->getWidth(),src->getHeight(),
            1.0f,LICE_BLIT_MODE_COPY|LICE_BLIT_USE_ALPHA|LICE_BLIT_FILTER_BILINEAR);
  swell_DirtyContext(out,x,y,x+w,y+h);
}


BOOL GetObject(HICON icon, int bmsz, void *_bm)
{
  memset(_bm,0,bmsz);
  if (WDL_NOT_NORMALLY(bmsz < 2*(int)sizeof(LONG))) return false;
  BITMAP *bm=(BITMAP *)_bm;
  HGDIOBJ__ *i = (HGDIOBJ__ *)icon;
  if (!HGDIOBJ_VALID(i,TYPE_BITMAP) || !i->typedata) return false;

  bm->bmWidth = ((LICE_IBitmap *)i->typedata)->getWidth();
  bm->bmHeight = ((LICE_IBitmap *)i->typedata)->getHeight();

  if (bmsz >= (int)sizeof(BITMAP))
  {
    bm->bmWidthBytes = ((LICE_IBitmap *)i->typedata)->getRowSpan()*4;
    bm->bmPlanes = 1;
    bm->bmBitsPixel = 32;
    bm->bmBits = ((LICE_IBitmap *)i->typedata)->getBits();
  }

  return true;
}


////////////////////////////////////


void BitBltAlphaFromMem(HDC hdcOut, int x, int y, int w, int h, void *inbufptr, int inbuf_span, int inbuf_h, int xin, int yin, int mode, bool useAlphaChannel, float opacity)
{
 // todo: use LICE_WrapperBitmap?
}

void BitBltAlpha(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int mode, bool useAlphaChannel, float opacity)
{
  HDC__ *in = (HDC__ *)hdcIn;
  HDC__ *out = (HDC__ *)hdcOut;
  if (!HDC_VALID(out) || !HDC_VALID(in)) return;
  if (!in->surface || !out->surface) return;
  LICE_Blit(out->surface,in->surface,
            x+out->surface_offs.x,y+out->surface_offs.y,
            xin+in->surface_offs.x,yin+in->surface_offs.y,w,h,
            opacity,LICE_BLIT_MODE_COPY|(useAlphaChannel?LICE_BLIT_USE_ALPHA:0));
  swell_DirtyContext(out,x,y,x+w,y+h);
}

void BitBlt(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int mode)
{
  HDC__ *in = (HDC__ *)hdcIn;
  HDC__ *out = (HDC__ *)hdcOut;
  if (!HDC_VALID(out) || !HDC_VALID(in)) return;
  if (!in->surface || !out->surface) return;
  LICE_Blit(out->surface,in->surface,
            x+out->surface_offs.x,y+out->surface_offs.y,
            xin+in->surface_offs.x,yin+in->surface_offs.y,w,h,
            1.0f,LICE_BLIT_MODE_COPY | (mode == (int)SRCCOPY_USEALPHACHAN ? LICE_BLIT_USE_ALPHA : 0));
  swell_DirtyContext(out,x,y,x+w,y+h);
}

void StretchBltFromMem(HDC hdcOut, int x, int y, int w, int h, const void *bits, int srcw, int srch, int srcspan)
{
  HDC__ *out = (HDC__ *)hdcOut;
  if (!HDC_VALID(out) || !bits) return;
  if (!out->surface) return;

  LICE_WrapperBitmap srcbm((LICE_pixel*)bits,srcw,srch,srcspan,false);
  LICE_ScaledBlit(out->surface,&srcbm,
            x+out->surface_offs.x,y+out->surface_offs.y,w,h,
            0,0,srcw,srch,
            1.0f,LICE_BLIT_MODE_COPY);
  swell_DirtyContext(out,x,y,x+w,y+h);
}

void StretchBlt(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int srcw, int srch, int mode)
{
  HDC__ *in = (HDC__ *)hdcIn;
  HDC__ *out = (HDC__ *)hdcOut;
  if (!HDC_VALID(out) || !HDC_VALID(in)) return;
  if (!in->surface || !out->surface) return;
  LICE_ScaledBlit(out->surface,in->surface,
            x+out->surface_offs.x,y+out->surface_offs.y,w,h,
            xin+in->surface_offs.x,yin+in->surface_offs.y,srcw,srch,
            1.0f,LICE_BLIT_MODE_COPY | (mode == (int)SRCCOPY_USEALPHACHAN ? LICE_BLIT_USE_ALPHA : 0));
  swell_DirtyContext(out,x,y,x+w,y+h);
}

void SWELL_FillDialogBackground(HDC hdc, const RECT *r, int level)
{
  HBRUSH br = CreateSolidBrush(g_swell_ctheme._3dface);
  FillRect(hdc,r,br);
  DeleteObject(br);
}

HGDIOBJ SWELL_CloneGDIObject(HGDIOBJ a)
{ 
  if (HGDIOBJ_VALID(a))
  {
    a->additional_refcnt++;
    return a;
  }
  return NULL;
}

void SWELL_PushClipRegion(HDC ctx)
{
//  HDC__ *ct=(HDC__ *)ctx;
//  if (ct && ct->ctx) CGContextSaveGState(ct->ctx);
}

void SWELL_SetClipRegion(HDC ctx, const RECT *r)
{
//  HDC__ *ct=(HDC__ *)ctx;
//  if (ct && ct->ctx) CGContextClipToRect(ct->ctx,CGRectMake(r->left,r->top,r->right-r->left,r->bottom-r->top));

}

void SWELL_PopClipRegion(HDC ctx)
{
//  HDC__ *ct=(HDC__ *)ctx;
//  if (ct && ct->ctx) CGContextRestoreGState(ct->ctx);
}

void *SWELL_GetCtxFrameBuffer(HDC ctx)
{
  HDC__ *ct=(HDC__ *)ctx;
  if (HDC_VALID(ct)&&ct->surface) return ct->surface->getBits();
  return 0;
}



struct swell_gdpLocalContext
{
  HDC__ ctx;
  RECT clipr;
};



HDC SWELL_internalGetWindowDC(HWND h, bool calcsize_on_first)
{
  if (WDL_NOT_NORMALLY(!h)) return NULL;

  int xoffs=0,yoffs=0;
  int wndw = h->m_position.right-h->m_position.left;
  int wndh = h->m_position.bottom-h->m_position.top;

  bool vis=true;
  HWND starth = h;
  int ltrim=0, ttrim=0, rtrim=0, btrim=0;
  for (;;)
  {
    if ((calcsize_on_first || h!=starth)  && h->m_wndproc)
    {
      RECT r;
      GetWindowContentViewRect(h,&r);
      NCCALCSIZE_PARAMS p={{r,},};
      h->m_wndproc(h,WM_NCCALCSIZE,FALSE,(LPARAM)&p);
      RECT r2=r;
      r=p.rgrc[0];

      // todo: handle left edges and adjust postions/etc accordingly
      if (h==starth)  // if initial window, adjust rects to client area (this implies calcsize_on_first being false)
      {
        wndw=r.right-r.left;
        wndh=r.bottom-r.top;
      }
      yoffs += r.top-r2.top;
      xoffs += r.left-r2.left;
    } 
    if (!h->m_visible) vis = false;

    if (h->m_backingstore || !h->m_parent) break; // found our target window

    xoffs += h->m_position.left;
    yoffs += h->m_position.top;

    ltrim = wdl_max(ltrim, -xoffs);
    ttrim = wdl_max(ttrim, -yoffs);
    rtrim = wdl_max(rtrim, xoffs+wndw - h->m_position.right);
    btrim = wdl_max(btrim, yoffs+wndh - h->m_position.bottom);

    h = h->m_parent;
  }

  swell_gdpLocalContext *p = (swell_gdpLocalContext*)SWELL_GDP_CTX_NEW();

  p->clipr.left=p->clipr.right=xoffs + ltrim;
  p->clipr.top=p->clipr.bottom=yoffs + ttrim;

  if (h->m_backingstore && vis)
  {
    p->ctx.surface=new LICE_SubBitmap(h->m_backingstore,
        xoffs+ltrim,yoffs+ttrim,
        wndw-ltrim-rtrim,wndh-ttrim-btrim);
    p->clipr.right += p->ctx.surface->getWidth();
    p->clipr.bottom += p->ctx.surface->getHeight();
  }
  if (xoffs<0) p->ctx.surface_offs.x = xoffs;
  if (yoffs<0) p->ctx.surface_offs.y = yoffs;
  p->ctx.surface_offs.x -= ltrim;
  p->ctx.surface_offs.y -= ttrim;

  p->ctx.curfont = starth->m_font;
  // todo: other GDI defaults?

  return (HDC)p;
}
HDC GetWindowDC(HWND h)
{
  return SWELL_internalGetWindowDC(h,0);
}

HDC GetDC(HWND h)
{
  return SWELL_internalGetWindowDC(h,1);
}

void ReleaseDC(HWND h, HDC hdc)
{
  if (WDL_NOT_NORMALLY(!h) || !HDC_VALID(hdc)) return;
  swell_gdpLocalContext *p = (swell_gdpLocalContext*)hdc;


  if (!h->m_paintctx)
  {
    // handle blitting?
    HWND par = h;
    while (par && !par->m_backingstore) par=par->m_parent;
    if (par) 
    {
      if (p->ctx.dirty_rect_valid)
      {
        RECT r = p->clipr, dr=p->ctx.dirty_rect; 
        dr.left += r.left;
        dr.top += r.top;
        dr.right += r.left;
        dr.bottom += r.top;
#if 1
        if (dr.left > r.left) r.left=dr.left;
        if (dr.top > r.top) r.top=dr.top;
        if (dr.right < r.right) r.right=dr.right;
        if (dr.bottom < r.bottom) r.bottom=dr.bottom;
#endif

        if (r.top<r.bottom && r.left<r.right) swell_oswindow_updatetoscreen(par,&r);
      }
    }
  }
  delete p->ctx.surface;
  SWELL_GDP_CTX_DELETE(hdc);
}



HDC BeginPaint(HWND hwnd, PAINTSTRUCT *ps)
{
  if (WDL_NOT_NORMALLY(!ps)) return 0;
  memset(ps,0,sizeof(PAINTSTRUCT));
  if (WDL_NOT_NORMALLY(!hwnd)) return 0;
  if (WDL_NOT_NORMALLY(!hwnd->m_paintctx)) return NULL;

  swell_gdpLocalContext *ctx = (swell_gdpLocalContext *)hwnd->m_paintctx;
  ps->rcPaint = ctx->clipr;
  ps->hdc = &ctx->ctx;
  return &ctx->ctx;
}


// paint hwnd into bmout, where bmout points at bmout_xpos,bmout_ypos in window coordinates
void SWELL_internalLICEpaint(HWND hwnd, LICE_IBitmap *bmout, int bmout_xpos, int bmout_ypos, bool forceref)
{
#ifdef _DEBUG
  extern int g_swell_in_paint;
  g_swell_in_paint++;
#endif
  // todo: check to see if any children completely intersect clip region, if so then we don't need to render ourselves
  // todo: opaque flag for windows? (to disable above behavior)
  // todo: implement/use per-window backing store.
  //   we would want to only really use them for top level windows, and on those, only update windows (and children of windows) who had backingstore invalidated.
  if (hwnd->m_invalidated) forceref=true;

  if (forceref || hwnd->m_child_invalidated)
  {
    swell_gdpLocalContext ctx;
    memset(&ctx,0,sizeof(ctx));
    ctx.ctx.surface = bmout;
    ctx.ctx.surface_offs.x = -bmout_xpos;
    ctx.ctx.surface_offs.y = -bmout_ypos;
    ctx.clipr.left = bmout_xpos;
    ctx.clipr.top = bmout_ypos;
    ctx.clipr.right = ctx.clipr.left + bmout->getWidth();
    ctx.clipr.bottom = ctx.clipr.top + bmout->getHeight();

    void *oldpaintctx = hwnd->m_paintctx;
    if (forceref) hwnd->m_paintctx = &ctx;

    LICE_SubBitmap tmpsub(NULL,0,0,0,0);
    if (hwnd->m_wndproc) // this happens after m_paintctx is set -- that way GetWindowDC()/GetDC()/ReleaseDC() know to not actually update the screen
    {
      RECT r2;
      GetWindowContentViewRect(hwnd,&r2);
      WinOffsetRect(&r2,-r2.left,-r2.top);

      NCCALCSIZE_PARAMS p;
      memset(&p,0,sizeof(p));
      p.rgrc[0]=r2;
      hwnd->m_wndproc(hwnd,WM_NCCALCSIZE,FALSE,(LPARAM)&p);
      RECT r = p.rgrc[0];
      if (forceref) hwnd->m_wndproc(hwnd,WM_NCPAINT,(WPARAM)1,0);

      // dx,dy is offset from the window's nonclient root
      const int dx = r.left-r2.left,dy=r.top-r2.top;

      WinOffsetRect(&ctx.clipr,-dx,-dy);
      ctx.ctx.surface_offs.x += dx;
      ctx.ctx.surface_offs.y += dy;
      bmout_xpos -= dx;
      bmout_ypos -= dy;

      // make sure we can't overwrite the nonclient area
      const int xo = wdl_max(-bmout_xpos,0), yo = wdl_max(-bmout_ypos,0);
      if (xo || yo)
      {
        tmpsub.m_parent = ctx.ctx.surface;
        tmpsub.m_x = xo;
        tmpsub.m_y = yo;
        tmpsub.m_w = ctx.ctx.surface->getWidth()-xo;
        tmpsub.m_h = ctx.ctx.surface->getHeight()-yo;
        if (tmpsub.m_w<0) tmpsub.m_w=0;
        if (tmpsub.m_h<0) tmpsub.m_h=0;
        ctx.ctx.surface = &tmpsub;
        ctx.ctx.surface_offs.x -= xo;
        ctx.ctx.surface_offs.y -= yo;
      }

      // constrain clip rect to right/bottom extents
      if (ctx.clipr.left < 0) ctx.clipr.left=0;
      if (ctx.clipr.top < 0) ctx.clipr.top=0;
      int newr = r.right-r.left;
      if (ctx.clipr.right > newr) ctx.clipr.right=newr;
      int newb = r.bottom-r.top;
      if (ctx.clipr.bottom > newb) ctx.clipr.bottom=newb;
    }

    // paint
    if (forceref)
    {
      if (hwnd->m_wndproc && ctx.clipr.right > ctx.clipr.left && ctx.clipr.bottom > ctx.clipr.top) 
      {
        ctx.ctx.curfont = hwnd->m_font;
        hwnd->m_wndproc(hwnd,WM_PAINT,(WPARAM)&ctx,0);
      }

      hwnd->m_paintctx = oldpaintctx;
   
      // it might be good to blit here on some OSes, rather than from the top level caller...
      hwnd->m_invalidated=false;
    }
  }

  bool okToClearChild=true;
  if (forceref || hwnd->m_child_invalidated)
  {
    HWND h = hwnd->m_children;
    while (h) 
    {
      if (h->m_visible && (forceref || h->m_invalidated||h->m_child_invalidated))
      {
        int width = h->m_position.right - h->m_position.left, 
            height = h->m_position.bottom - h->m_position.top; // max width possible for this window
        int xp = h->m_position.left - bmout_xpos, yp = h->m_position.top - bmout_ypos;

        if (okToClearChild && !forceref)
        {
          if (xp < 0 || xp+width > bmout->getWidth() ||
              yp < 0 || yp+height > bmout->getHeight()) okToClearChild = false; // extends outside of our region
        }
        // if xp/yp < 0, that means that the clip region starts inside the window, so we need to pass a positive render offset, and decrease the potential draw width
        // if xp/yp > 0, then the clip region starts before the window, so we use the subbitmap and pass 0 for the offset parm
        int subx = 0, suby=0;
        if (xp<0) width+=xp; 
        else { subx=xp; xp=0; }

        if (yp<0) height+=yp;
        else { suby=yp; yp=0; }

        LICE_SubBitmap subbm(bmout,subx,suby,width,height); // the right/bottom will automatically be clipped to the clip rect etc
        if (subbm.getWidth()>0 && subbm.getHeight()>0)
          SWELL_internalLICEpaint(h,&subbm,-xp,-yp,forceref);
      }
      h = h->m_next;
    }
  }
  if (okToClearChild) hwnd->m_child_invalidated=false;
#ifdef _DEBUG
  g_swell_in_paint--;
#endif
}

HBITMAP CreateBitmap(int width, int height, int numplanes, int bitsperpixel, unsigned char* bits)
{
  if (WDL_NOT_NORMALLY(width < 1 || height < 1 || numplanes != 1 || bitsperpixel != 32 || !bits)) return NULL;
  LICE_MemBitmap *bm = new LICE_MemBitmap(width,height);
  if (WDL_NOT_NORMALLY(!bm->getBits())) { delete bm; return NULL; }
  int y;
  LICE_pixel *wr = bm->getBits();
  for (y=0;y<height; y++)
  {
    memcpy(wr,bits,width*4);
    bits += width*4;
    wr += bm->getRowSpan();
  }
  
  

  HGDIOBJ__* icon=GDP_OBJECT_NEW();
  icon->type=TYPE_BITMAP;
  icon->wid=1;
  icon->typedata = (void*)bm;
  return icon;
}

HICON CreateIconIndirect(ICONINFO* iconinfo)
{
  if (WDL_NOT_NORMALLY(!iconinfo || !iconinfo->fIcon)) return 0;  
  HGDIOBJ__* i=iconinfo->hbmColor;
  if (!HGDIOBJ_VALID(i,TYPE_BITMAP) ) return 0;

  if (!i->typedata) return 0;

  LICE_MemBitmap *bm = new LICE_MemBitmap;
  LICE_Copy(bm,(LICE_IBitmap*)i->typedata);

  HGDIOBJ__* icon=GDP_OBJECT_NEW();
  icon->type=TYPE_BITMAP;
  icon->wid=1;
  icon->typedata = (void*)bm;

  return icon;
}

HIMAGELIST ImageList_CreateEx()
{
  return (HIMAGELIST)new WDL_PtrList<HGDIOBJ__>;
}

BOOL ImageList_Remove(HIMAGELIST list, int idx)
{
  WDL_PtrList<HGDIOBJ__>* imglist=(WDL_PtrList<HGDIOBJ__>*)list;
  if (WDL_NORMALLY(imglist) && idx < imglist->GetSize())
  {
    if (idx < 0) 
    {
      int x,n=imglist->GetSize();
      for (x=0;x<n;x++)
      {
        HGDIOBJ__ *a = imglist->Get(x);
        if (a) DeleteObject(a);
      }
      imglist->Empty();
    }
    else 
    {
      HGDIOBJ__ *a = imglist->Get(idx);
      imglist->Set(idx, NULL); 
      if (a) DeleteObject(a);
    }
    return TRUE;
  }
  
  return FALSE;
}

void ImageList_Destroy(HIMAGELIST list)
{
  if (WDL_NOT_NORMALLY(!list)) return;
  WDL_PtrList<HGDIOBJ__> *p=(WDL_PtrList<HGDIOBJ__>*)list;
  ImageList_Remove(list,-1);
  delete p;
}

int ImageList_ReplaceIcon(HIMAGELIST list, int offset, HICON image)
{
  if (WDL_NOT_NORMALLY(!image || !list)) return -1;
  WDL_PtrList<HGDIOBJ__> *l=(WDL_PtrList<HGDIOBJ__> *)list;

  HGDIOBJ__ *imgsrc = (HGDIOBJ__*)image;
  if (!HGDIOBJ_VALID(imgsrc,TYPE_BITMAP)) return -1;

  HGDIOBJ__* icon=GDP_OBJECT_NEW();
  LICE_MemBitmap *bm = new LICE_MemBitmap;
  LICE_Copy(bm,(LICE_IBitmap*)imgsrc->typedata);

  icon->type=TYPE_BITMAP;
  icon->alpha = 1.0f;
  icon->wid=1;
  icon->typedata = (void*)bm;

  image = (HICON) icon;

  if (offset<0||offset>=l->GetSize()) 
  {
    l->Add(image);
    offset=l->GetSize()-1;
  }
  else
  {
    HICON old=l->Get(offset);
    l->Set(offset,image);
    if (old) DeleteObject(old);
  }
  return offset;
}

int ImageList_Add(HIMAGELIST list, HBITMAP image, HBITMAP mask)
{
  if (WDL_NOT_NORMALLY(!image || !list)) return -1;
  WDL_PtrList<HGDIOBJ__> *l=(WDL_PtrList<HGDIOBJ__> *)list;
  
  HGDIOBJ__ *imgsrc = (HGDIOBJ__*)image;
  if (!HGDIOBJ_VALID(imgsrc,TYPE_BITMAP)) return -1;
  
  HGDIOBJ__* icon=GDP_OBJECT_NEW();
  LICE_MemBitmap *bm = new LICE_MemBitmap;
  LICE_Copy(bm,(LICE_IBitmap*)imgsrc->typedata);

  icon->type=TYPE_BITMAP;
  icon->wid=1;
  icon->typedata = (void*)bm;

  image = (HICON) icon;
  
  l->Add(image);
  return l->GetSize();
}

int AddFontResourceEx(LPCTSTR str, DWORD fl, void *pdv)
{
#ifdef SWELL_FREETYPE
  if (str && *str)
  {
#ifdef SWELL_FONTCONFIG
    if (!s_fontconfig) s_fontconfig = FcInitLoadConfigAndFonts();
    return s_fontconfig && FcConfigAppFontAddFile(s_fontconfig,(const FcChar8 *)str) != FcFalse;
#else
    if (s_freetype_regfonts.FindSorted(str,sortByFilePart)>=0) return 0;
    s_freetype_regfonts.InsertSorted(strdup(str), sortByFilePart);
#endif
    return 1;
  } 
#endif
  return 0;
}

int GetGlyphIndicesW(HDC ctx, wchar_t *buf, int len, unsigned short *indices, int flags)
{
#ifdef SWELL_FREETYPE
  if (ctx)
  {
    HGDIOBJ font  = HDC_VALID(ctx) && HGDIOBJ_VALID(ctx->curfont,TYPE_FONT) ? ctx->curfont : SWELL_GetDefaultFont();
    if (font)
    {
      FT_Face face=(FT_Face)font->typedata;
      if (face)
      {
        for (int i=0; i < len; ++i)
        {
          int c = FT_Get_Char_Index(face,buf[i]);
          indices[i] = c ? c : 0xFFFF;
        }
        return len;
      }
    }
  }
#endif

  for (int i=0; i < len; ++i) indices[i]=0xFFFF;
  return len;
}

#endif

#endif // SWELL_LICE_GDI
