/** \file
 * \brief Draw Functions
 *
 * See Copyright Notice in "iup.h"
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include <Xm/Xm.h>
#include <X11/Xlib.h>

#ifdef IUP_USE_XFT
#include <X11/Xft/Xft.h>
#endif

#include "iup.h"

#include "iup_attrib.h"
#include "iup_class.h"
#include "iup_str.h"
#include "iup_object.h"
#include "iup_image.h"
#include "iup_drvdraw.h"
#include "iup_draw.h"

#include "iupmot_drv.h"
#include "iupmot_color.h"


struct _IdrawCanvas{
  Ihandle* ih;
  int w, h;

  Window wnd;
  Pixmap pixmap;
  GC pixmap_gc, gc;

  int clip_x1, clip_y1, clip_x2, clip_y2;
};

static int motDrawGetGeometry(Display *dpy, Drawable wnd, int *_w, int *_h, int *_d)
{
  Window root;
  int x, y;
  unsigned int w = 0, h = 0, b = 0, d = 0;
  Status status = XGetGeometry(dpy, wnd, &root, &x, &y, &w, &h, &b, &d);
  if (status == 0)
  {
    *_w = 0;
    *_h = 0;
    *_d = 0;
    return 0;
  }
  *_w = (int)w;
  *_h = (int)h;
  *_d = (int)d;
  return 1;
}

IUP_SDK_API IdrawCanvas* iupdrvDrawCreateCanvas(Ihandle* ih)
{
  IdrawCanvas* dc;
  int depth;

  dc = calloc(1, sizeof(IdrawCanvas));
  if (!dc)
    return NULL;

  dc->ih = ih;
  dc->wnd = (Window)IupGetAttribute(ih, "DRAWABLE");
  if (!dc->wnd)
  {
    free(dc);
    return NULL;
  }

  if (!motDrawGetGeometry(iupmot_display, dc->wnd, &dc->w, &dc->h, &depth))
  {
    free(dc);
    return NULL;
  }

  if (dc->w <= 0) dc->w = 1;
  if (dc->h <= 0) dc->h = 1;

  dc->pixmap = XCreatePixmap(iupmot_display, dc->wnd, dc->w, dc->h, depth);
  if (!dc->pixmap)
  {
    free(dc);
    return NULL;
  }

  dc->pixmap_gc = XCreateGC(iupmot_display, dc->pixmap, 0, NULL);
  if (!dc->pixmap_gc)
  {
    XFreePixmap(iupmot_display, dc->pixmap);
    free(dc);
    return NULL;
  }

  dc->gc = XCreateGC(iupmot_display, dc->wnd, 0, NULL);
  if (!dc->gc)
  {
    XFreeGC(iupmot_display, dc->pixmap_gc);
    XFreePixmap(iupmot_display, dc->pixmap);
    free(dc);
    return NULL;
  }

  iupAttribSet(ih, "DRAWDRIVER", "X11");

  return dc;
}

IUP_SDK_API void iupdrvDrawKillCanvas(IdrawCanvas* dc)
{
  if (!dc)
    return;

  if (dc->pixmap_gc)
    XFreeGC(iupmot_display, dc->pixmap_gc);
  if (dc->pixmap)
    XFreePixmap(iupmot_display, dc->pixmap);
  if (dc->gc)
    XFreeGC(iupmot_display, dc->gc);

  free(dc);
}

IUP_SDK_API void iupdrvDrawUpdateSize(IdrawCanvas* dc)
{
  int w = 0, h = 0, depth = 0;

  if (!dc || !dc->wnd)
    return;

  if (!motDrawGetGeometry(iupmot_display, dc->wnd, &w, &h, &depth))
    return;

  if (w <= 0 || h <= 0)
    return;

  if (w != dc->w || h != dc->h)
  {
    dc->w = w;
    dc->h = h;

    if (dc->pixmap_gc)
      XFreeGC(iupmot_display, dc->pixmap_gc);
    if (dc->pixmap)
      XFreePixmap(iupmot_display, dc->pixmap);

    dc->pixmap = XCreatePixmap(iupmot_display, dc->wnd, dc->w, dc->h, depth);
    if (!dc->pixmap)
    {
      dc->pixmap_gc = NULL;
      dc->w = 0;
      dc->h = 0;
      return;
    }

    dc->pixmap_gc = XCreateGC(iupmot_display, dc->pixmap, 0, NULL);
    if (!dc->pixmap_gc)
    {
      XFreePixmap(iupmot_display, dc->pixmap);
      dc->pixmap = None;
      dc->w = 0;
      dc->h = 0;
      return;
    }
  }
}

IUP_SDK_API void iupdrvDrawFlush(IdrawCanvas* dc)
{
  if (!dc || !dc->wnd || !dc->pixmap || !dc->gc)
    return;

  XCopyArea(iupmot_display, dc->pixmap, dc->wnd, dc->gc, 0, 0, dc->w, dc->h, 0, 0);
}

IUP_SDK_API void iupdrvDrawGetSize(IdrawCanvas* dc, int *w, int *h)
{
  if (!dc)
  {
    if (w) *w = 0;
    if (h) *h = 0;
    return;
  }

  if (w) *w = dc->w;
  if (h) *h = dc->h;
}

static void iDrawSetLineStyle(IdrawCanvas* dc, int style)
{
  XGCValues gcval;
  if (style == IUP_DRAW_STROKE || style == IUP_DRAW_FILL)
    gcval.line_style = LineSolid;
  else
  {
    if (style == IUP_DRAW_STROKE_DASH)
    {
      char dashes[2] = { 9, 3 };
      XSetDashes(iupmot_display, dc->pixmap_gc, 0, dashes, 2);
    }
    else if (style == IUP_DRAW_STROKE_DOT)
    {
      char dashes[2] = { 1, 2 };
      XSetDashes(iupmot_display, dc->pixmap_gc, 0, dashes, 2);
    }
    else if (style == IUP_DRAW_STROKE_DASH_DOT)
    {
      char dashes[4] = { 7, 3, 1, 3 };
      XSetDashes(iupmot_display, dc->pixmap_gc, 0, dashes, 4);
    }
    else if (style == IUP_DRAW_STROKE_DASH_DOT_DOT)
    {
      char dashes[6] = { 7, 3, 1, 3, 1, 3 };
      XSetDashes(iupmot_display, dc->pixmap_gc, 0, dashes, 6);
    }

    gcval.line_style = LineOnOffDash;
  }

  XChangeGC(iupmot_display, dc->pixmap_gc, GCLineStyle, &gcval);
}

static void iDrawSetLineWidth(IdrawCanvas* dc, int line_width)
{
  XGCValues gcval;

  if (line_width == 1)
    gcval.line_width = 0;
  else
    gcval.line_width = line_width;

  XChangeGC(iupmot_display, dc->pixmap_gc, GCLineWidth, &gcval);
}

/* Combined function to set both line style and width in one XChangeGC call */
static void iDrawSetLineStyleAndWidth(IdrawCanvas* dc, int style, int line_width)
{
  XGCValues gcval;

  /* Set line width */
  if (line_width == 1)
    gcval.line_width = 0;
  else
    gcval.line_width = line_width;

  /* Set line style */
  if (style == IUP_DRAW_STROKE || style == IUP_DRAW_FILL)
    gcval.line_style = LineSolid;
  else
  {
    if (style == IUP_DRAW_STROKE_DASH)
    {
      char dashes[2] = { 9, 3 };
      XSetDashes(iupmot_display, dc->pixmap_gc, 0, dashes, 2);
    }
    else if (style == IUP_DRAW_STROKE_DOT)
    {
      char dashes[2] = { 1, 2 };
      XSetDashes(iupmot_display, dc->pixmap_gc, 0, dashes, 2);
    }
    else if (style == IUP_DRAW_STROKE_DASH_DOT)
    {
      char dashes[4] = { 7, 3, 1, 3 };
      XSetDashes(iupmot_display, dc->pixmap_gc, 0, dashes, 4);
    }
    else if (style == IUP_DRAW_STROKE_DASH_DOT_DOT)
    {
      char dashes[6] = { 7, 3, 1, 3, 1, 3 };
      XSetDashes(iupmot_display, dc->pixmap_gc, 0, dashes, 6);
    }

    gcval.line_style = LineOnOffDash;
  }

  /* Batch update both line width and style in one call */
  XChangeGC(iupmot_display, dc->pixmap_gc, GCLineWidth | GCLineStyle, &gcval);
}

IUP_SDK_API void iupdrvDrawRectangle(IdrawCanvas* dc, int x1, int y1, int x2, int y2, long color, int style, int line_width)
{
  XSetForeground(iupmot_display, dc->pixmap_gc, iupmotColorGetPixel(iupDrawRed(color),iupDrawGreen(color),iupDrawBlue(color)));

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  if (style==IUP_DRAW_FILL)
    XFillRectangle(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
  else
  {
    iDrawSetLineStyleAndWidth(dc, style, line_width);  /* Batch GC update */

    XDrawRectangle(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, x2 - x1, y2 - y1);
  }
}

IUP_SDK_API void iupdrvDrawLine(IdrawCanvas* dc, int x1, int y1, int x2, int y2, long color, int style, int line_width)
{
  XSetForeground(iupmot_display, dc->pixmap_gc, iupmotColorGetPixel(iupDrawRed(color),iupDrawGreen(color),iupDrawBlue(color)));

  iDrawSetLineStyleAndWidth(dc, style, line_width);  /* Batch GC update */

  XDrawLine(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, x2, y2);
}

IUP_SDK_API void iupdrvDrawArc(IdrawCanvas* dc, int x1, int y1, int x2, int y2, double a1, double a2, long color, int style, int line_width)
{
  XSetForeground(iupmot_display, dc->pixmap_gc, iupmotColorGetPixel(iupDrawRed(color),iupDrawGreen(color),iupDrawBlue(color)));

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  if (style==IUP_DRAW_FILL)
  {
    XSetArcMode(iupmot_display, dc->pixmap_gc, ArcPieSlice);
    XFillArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1, iupRound(a1 * 64), iupRound((a2 - a1) * 64));
  }
  else
  {
    iDrawSetLineStyleAndWidth(dc, style, line_width);  /* Batch GC update */

    XDrawArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1, iupRound(a1 * 64), iupRound((a2 - a1) * 64));   /* angle = 1/64ths of a degree */
  }
}

IUP_SDK_API void iupdrvDrawEllipse(IdrawCanvas* dc, int x1, int y1, int x2, int y2, long color, int style, int line_width)
{
  XSetForeground(iupmot_display, dc->pixmap_gc, iupmotColorGetPixel(iupDrawRed(color),iupDrawGreen(color),iupDrawBlue(color)));

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  /* Draw full ellipse using X11 arc with 360 degree span */
  /* angle in X11 is 1/64ths of a degree, so 360*64 = 23040 */
  if (style == IUP_DRAW_FILL)
  {
    XSetArcMode(iupmot_display, dc->pixmap_gc, ArcPieSlice);
    XFillArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1, 0, 23040);
  }
  else
  {
    iDrawSetLineStyleAndWidth(dc, style, line_width);  /* Batch GC update */
    XDrawArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1, 0, 23040);
  }
}

IUP_SDK_API void iupdrvDrawPolygon(IdrawCanvas* dc, int* points, int count, long color, int style, int line_width)
{
  int i;
  XPoint stack_pnt[256]; /* Stack buffer for small polygons - avoid malloc overhead */
  XPoint* pnt;
  int use_heap = 0;
  int pnt_count;

  /* Use stack buffer for small polygons, heap for large ones */
  /* For stroked polygons, we need count+1 points to close the path */
  pnt_count = (style == IUP_DRAW_FILL) ? count : count + 1;

  if (pnt_count <= 256)
    pnt = stack_pnt;
  else
  {
    pnt = (XPoint*)malloc(pnt_count * sizeof(XPoint)); /* XPoint uses short for coordinates */
    use_heap = 1;
  }

  for (i = 0; i < count; i++)
  {
    pnt[i].x = (short)points[2*i];
    pnt[i].y = (short)points[2*i+1];
  }

  /* For stroked polygons, close the path by adding first point at the end */
  if (style != IUP_DRAW_FILL)
  {
    pnt[count].x = pnt[0].x;
    pnt[count].y = pnt[0].y;
  }

  XSetForeground(iupmot_display, dc->pixmap_gc, iupmotColorGetPixel(iupDrawRed(color),iupDrawGreen(color),iupDrawBlue(color)));

  if (style==IUP_DRAW_FILL)
    XFillPolygon(iupmot_display, dc->pixmap, dc->pixmap_gc, pnt, count, Complex, CoordModeOrigin);
  else
  {
    iDrawSetLineStyleAndWidth(dc, style, line_width);  /* Batch GC update */

    XDrawLines(iupmot_display, dc->pixmap, dc->pixmap_gc, pnt, pnt_count, CoordModeOrigin);
  }

  if (use_heap)
    free(pnt);
}

IUP_SDK_API void iupdrvDrawPixel(IdrawCanvas* dc, int x, int y, long color)
{
  XSetForeground(iupmot_display, dc->pixmap_gc, iupmotColorGetPixel(iupDrawRed(color), iupDrawGreen(color), iupDrawBlue(color)));
  XDrawPoint(iupmot_display, dc->pixmap, dc->pixmap_gc, x, y);
}

IUP_SDK_API void iupdrvDrawRoundedRectangle(IdrawCanvas* dc, int x1, int y1, int x2, int y2, int corner_radius, long color, int style, int line_width)
{
  int diameter;

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  /* Clamp radius to prevent oversized corners */
  int max_radius = ((x2 - x1) < (y2 - y1)) ? (x2 - x1) / 2 : (y2 - y1) / 2;
  if (corner_radius > max_radius)
    corner_radius = max_radius;

  diameter = corner_radius * 2;

  XSetForeground(iupmot_display, dc->pixmap_gc, iupmotColorGetPixel(iupDrawRed(color), iupDrawGreen(color), iupDrawBlue(color)));

  if (style == IUP_DRAW_FILL)
  {
    /* Fill rounded rectangle by drawing filled arcs and rectangles */
    /* Top-right arc */
    XFillArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x2 - diameter, y1, diameter, diameter, 0 * 64, 90 * 64);
    /* Bottom-right arc */
    XFillArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x2 - diameter, y2 - diameter, diameter, diameter, 270 * 64, 90 * 64);
    /* Bottom-left arc */
    XFillArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y2 - diameter, diameter, diameter, 180 * 64, 90 * 64);
    /* Top-left arc */
    XFillArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, diameter, diameter, 90 * 64, 90 * 64);

    /* Fill center rectangle */
    XFillRectangle(iupmot_display, dc->pixmap, dc->pixmap_gc, x1 + corner_radius, y1, x2 - x1 - diameter + 1, y2 - y1 + 1);
    /* Fill left rectangle */
    XFillRectangle(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1 + corner_radius, corner_radius, y2 - y1 - diameter + 1);
    /* Fill right rectangle */
    XFillRectangle(iupmot_display, dc->pixmap, dc->pixmap_gc, x2 - corner_radius + 1, y1 + corner_radius, corner_radius, y2 - y1 - diameter + 1);
  }
  else
  {
    iDrawSetLineStyleAndWidth(dc, style, line_width);

    /* Draw rounded rectangle by drawing arcs and lines */
    /* Top-right arc */
    XDrawArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x2 - diameter, y1, diameter, diameter, 0 * 64, 90 * 64);
    /* Bottom-right arc */
    XDrawArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x2 - diameter, y2 - diameter, diameter, diameter, 270 * 64, 90 * 64);
    /* Bottom-left arc */
    XDrawArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y2 - diameter, diameter, diameter, 180 * 64, 90 * 64);
    /* Top-left arc */
    XDrawArc(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, diameter, diameter, 90 * 64, 90 * 64);

    /* Draw connecting lines */
    /* Top line */
    XDrawLine(iupmot_display, dc->pixmap, dc->pixmap_gc, x1 + corner_radius, y1, x2 - corner_radius, y1);
    /* Right line */
    XDrawLine(iupmot_display, dc->pixmap, dc->pixmap_gc, x2, y1 + corner_radius, x2, y2 - corner_radius);
    /* Bottom line */
    XDrawLine(iupmot_display, dc->pixmap, dc->pixmap_gc, x2 - corner_radius, y2, x1 + corner_radius, y2);
    /* Left line */
    XDrawLine(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y2 - corner_radius, x1, y1 + corner_radius);
  }
}

IUP_SDK_API void iupdrvDrawGetClipRect(IdrawCanvas* dc, int *x1, int *y1, int *x2, int *y2)
{
  if (x1) *x1 = dc->clip_x1;
  if (y1) *y1 = dc->clip_y1;
  if (x2) *x2 = dc->clip_x2;
  if (y2) *y2 = dc->clip_y2;
}

IUP_SDK_API void iupdrvDrawSetClipRect(IdrawCanvas* dc, int x1, int y1, int x2, int y2)
{
  XRectangle rect;

  if (x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0)
  {
    iupdrvDrawResetClip(dc);
    return;
  }

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  rect.x = (short)x1;
  rect.y      = (short)y1;
  rect.width = (unsigned short)(x2 - x1 + 1);
  rect.height = (unsigned short)(y2 - y1 + 1);

  XSetClipRectangles(iupmot_display, dc->pixmap_gc, 0, 0, &rect, 1, Unsorted);

  dc->clip_x1 = x1;
  dc->clip_y1 = y1;
  dc->clip_x2 = x2;
  dc->clip_y2 = y2;
}

IUP_SDK_API void iupdrvDrawSetClipRoundedRect(IdrawCanvas* dc, int x1, int y1, int x2, int y2, int corner_radius)
{
  Region region;
  XPoint points[100];
  int num_points = 0;
  int i;
  double angle, step;
  int max_radius;
  double pi = 3.14159265359;

  if (x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0)
  {
    iupdrvDrawResetClip(dc);
    return;
  }

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  /* Clamp radius to prevent oversized corners */
  max_radius = ((x2 - x1) < (y2 - y1)) ? (x2 - x1) / 2 : (y2 - y1) / 2;
  if (corner_radius > max_radius)
    corner_radius = max_radius;

  /* Create a polygon approximation going clockwise from top-left */
  /* 8 points per corner arc */
  step = 90.0 / 8.0;

  /* Top-left corner arc: from top edge (270) to left edge (180) going clockwise */
  for (i = 0; i <= 8; i++)
  {
    angle = (270.0 - i * step) * pi / 180.0;
    points[num_points].x = (short)(x1 + corner_radius + (int)(corner_radius * cos(angle)));
    points[num_points].y = (short)(y1 + corner_radius + (int)(corner_radius * sin(angle)));
    num_points++;
  }

  /* Bottom-left corner arc: from left edge (180) to bottom edge (90) going clockwise */
  for (i = 1; i <= 8; i++)
  {
    angle = (180.0 - i * step) * pi / 180.0;
    points[num_points].x = (short)(x1 + corner_radius + (int)(corner_radius * cos(angle)));
    points[num_points].y = (short)(y2 - corner_radius + (int)(corner_radius * sin(angle)));
    num_points++;
  }

  /* Bottom-right corner arc: from bottom edge (90) to right edge (0) going clockwise */
  for (i = 1; i <= 8; i++)
  {
    angle = (90.0 - i * step) * pi / 180.0;
    points[num_points].x = (short)(x2 - corner_radius + (int)(corner_radius * cos(angle)));
    points[num_points].y = (short)(y2 - corner_radius + (int)(corner_radius * sin(angle)));
    num_points++;
  }

  /* Top-right corner arc: from right edge (0) to top edge (270 = -90) going clockwise */
  for (i = 1; i <= 8; i++)
  {
    angle = (0.0 - i * step) * pi / 180.0;
    points[num_points].x = (short)(x2 - corner_radius + (int)(corner_radius * cos(angle)));
    points[num_points].y = (short)(y1 + corner_radius + (int)(corner_radius * sin(angle)));
    num_points++;
  }

  /* Create X11 region from polygon */
  region = XPolygonRegion(points, num_points, WindingRule);
  XSetRegion(iupmot_display, dc->pixmap_gc, region);
  XDestroyRegion(region);

  dc->clip_x1 = x1;
  dc->clip_y1 = y1;
  dc->clip_x2 = x2;
  dc->clip_y2 = y2;
}

IUP_SDK_API void iupdrvDrawResetClip(IdrawCanvas* dc)
{
  XSetClipMask(iupmot_display, dc->pixmap_gc, None);

  dc->clip_x1 = 0;
  dc->clip_y1 = 0;
  dc->clip_x2 = 0;
  dc->clip_y2 = 0;
}

#ifdef IUP_USE_XFT
static void iDrawTextXft(IdrawCanvas* dc, const char* text, int len, int x, int y, int w, int h, long color, XftFont* xftfont, int flags)
{
  int num_line, off_x;
  XftDraw* xftdraw;
  XftColor xftcolor;
  XRenderColor rendercolor;
  XGlyphInfo extents;

  rendercolor.red = (unsigned short)(iupDrawRed(color) << 8);
  rendercolor.green = (unsigned short)(iupDrawGreen(color) << 8);
  rendercolor.blue = (unsigned short)(iupDrawBlue(color) << 8);
  rendercolor.alpha = 0xFFFF;
  XftColorAllocValue(iupmot_display, iupmot_visual, DefaultColormap(iupmot_display, iupmot_screen), &rendercolor, &xftcolor);

  xftdraw = XftDrawCreate(iupmot_display, dc->pixmap, iupmot_visual, DefaultColormap(iupmot_display, iupmot_screen));
  if (!xftdraw)
  {
    XftColorFree(iupmot_display, iupmot_visual, DefaultColormap(iupmot_display, iupmot_screen), &xftcolor);
    return;
  }

  if (flags & IUP_DRAW_CLIP)
  {
    XRectangle rect;
    rect.x = (short)x;
    rect.y = (short)y;
    rect.width = (unsigned short)w;
    rect.height = (unsigned short)h;
    XftDrawSetClipRectangles(xftdraw, 0, 0, &rect, 1);
  }

  num_line = iupStrLineCount(text, len);

  if (num_line == 1)
  {
    off_x = 0;
    if (flags & (IUP_DRAW_RIGHT | IUP_DRAW_CENTER))
    {
      XftTextExtentsUtf8(iupmot_display, xftfont, (FcChar8*)text, len, &extents);
      if (flags & IUP_DRAW_RIGHT)
      {
        off_x = w - extents.xOff;
        if (off_x < 0) off_x = 0;
      }
      else
      {
        off_x = (w - extents.xOff) / 2;
        if (off_x < 0) off_x = 0;
      }
    }

    XftDrawStringUtf8(xftdraw, &xftcolor, xftfont, x + off_x, y + xftfont->ascent, (FcChar8*)text, len);
  }
  else
  {
    int i, line_height, l_len, sum_len = 0;
    const char *p, *q;

    line_height = xftfont->ascent + xftfont->descent;

    p = text;
    for (i = 0; i < num_line; i++)
    {
      q = strchr(p, '\n');
      if (q)
        l_len = (int)(q - p);
      else
        l_len = (int)strlen(p);

      if (sum_len + l_len > len)
        l_len = len - sum_len;

      if (l_len)
      {
        off_x = 0;
        if (flags & (IUP_DRAW_RIGHT | IUP_DRAW_CENTER))
        {
          XftTextExtentsUtf8(iupmot_display, xftfont, (FcChar8*)p, l_len, &extents);
          if (flags & IUP_DRAW_RIGHT)
          {
            off_x = w - extents.xOff;
            if (off_x < 0) off_x = 0;
          }
          else
          {
            off_x = (w - extents.xOff) / 2;
            if (off_x < 0) off_x = 0;
          }
        }

        XftDrawStringUtf8(xftdraw, &xftcolor, xftfont, x + off_x, y + xftfont->ascent, (FcChar8*)p, l_len);
      }

      if (q)
        p = q + 1;

      sum_len += l_len;
      if (sum_len == len)
        break;

      y += line_height;
    }
  }

  XftDrawDestroy(xftdraw);
  XftColorFree(iupmot_display, iupmot_visual, DefaultColormap(iupmot_display, iupmot_screen), &xftcolor);
}
#endif

static void iDrawTextX11(IdrawCanvas* dc, const char* text, int len, int x, int y, int w, int h, long color, XFontStruct* xfont, int flags)
{
  int num_line, width, off_x;

  XSetForeground(iupmot_display, dc->pixmap_gc, iupmotColorGetPixel(iupDrawRed(color), iupDrawGreen(color), iupDrawBlue(color)));
  XSetFont(iupmot_display, dc->pixmap_gc, xfont->fid);

  if (flags & IUP_DRAW_CLIP)
  {
    XRectangle rect;
    rect.x = (short)x;
    rect.y = (short)y;
    rect.width = (unsigned short)w;
    rect.height = (unsigned short)h;
    XSetClipRectangles(iupmot_display, dc->pixmap_gc, 0, 0, &rect, 1, Unsorted);
  }

  num_line = iupStrLineCount(text, len);

  if (num_line == 1)
  {
    off_x = 0;
    if (flags & (IUP_DRAW_RIGHT | IUP_DRAW_CENTER))
    {
      width = XTextWidth(xfont, text, len);
      if (flags & IUP_DRAW_RIGHT)
      {
        off_x = w - width;
        if (off_x < 0) off_x = 0;
      }
      else
      {
        off_x = (w - width) / 2;
        if (off_x < 0) off_x = 0;
      }
    }

    XDrawString(iupmot_display, dc->pixmap, dc->pixmap_gc, x + off_x, y + xfont->ascent, text, len);
  }
  else
  {
    int i, line_height, l_len, sum_len = 0;
    const char *p, *q;

    line_height = xfont->ascent + xfont->descent;

    p = text;
    for (i = 0; i < num_line; i++)
    {
      q = strchr(p, '\n');
      if (q)
        l_len = (int)(q - p);
      else
        l_len = (int)strlen(p);

      if (sum_len + l_len > len)
        l_len = len - sum_len;

      if (l_len)
      {
        off_x = 0;
        if (flags & (IUP_DRAW_RIGHT | IUP_DRAW_CENTER))
        {
          width = XTextWidth(xfont, p, l_len);
          if (flags & IUP_DRAW_RIGHT)
          {
            off_x = w - width;
            if (off_x < 0) off_x = 0;
          }
          else
          {
            off_x = (w - width) / 2;
            if (off_x < 0) off_x = 0;
          }
        }

        XDrawString(iupmot_display, dc->pixmap, dc->pixmap_gc, x + off_x, y + xfont->ascent, p, l_len);
      }

      if (q)
        p = q + 1;

      sum_len += l_len;
      if (sum_len == len)
        break;

      y += line_height;
    }
  }

  if (flags & IUP_DRAW_CLIP)
    XSetClipMask(iupmot_display, dc->pixmap_gc, None);
}

IUP_SDK_API void iupdrvDrawText(IdrawCanvas* dc, const char* text, int len, int x, int y, int w, int h, long color, const char* font, int flags, double text_orientation)
{
  (void)text_orientation;

#ifdef IUP_USE_XFT
  {
    XftFont* xftfont = (XftFont*)iupmotGetXftFont(font);
    if (xftfont)
    {
      iDrawTextXft(dc, text, len, x, y, w, h, color, xftfont, flags);
      return;
    }
  }
#endif

  {
    XFontStruct* xfont = (XFontStruct*)iupmotGetFontStruct(font);
    if (xfont)
      iDrawTextX11(dc, text, len, x, y, w, h, color, xfont, flags);
  }
}

IUP_SDK_API void iupdrvDrawImage(IdrawCanvas* dc, const char* name, int make_inactive, const char* bgcolor, int x, int y, int w, int h)
{
  int img_w, img_h;
  int bpp;
  Pixmap pixmap = (Pixmap)iupImageGetImage(name, dc->ih, make_inactive, bgcolor);
  if (!pixmap)
    return;

  /* must use this info, since image can be a driver image loaded from resources */
  iupdrvImageGetInfo((void*)pixmap, &img_w, &img_h, &bpp);

  /* Image scaling not supported in X11 driver */
  if (w == -1 || w == 0) w = img_w;
  if (h == -1 || h == 0) h = img_h;
  (void)w;
  (void)h;

  XCopyArea(iupmot_display, pixmap, dc->pixmap, dc->pixmap_gc, 0, 0, img_w, img_h, x, y);
}

IUP_SDK_API void iupdrvDrawSelectRect(IdrawCanvas* dc, int x1, int y1, int x2, int y2)
{
  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  XSetFunction(iupmot_display, dc->pixmap_gc, GXxor);
  XSetForeground(iupmot_display, dc->pixmap_gc, iupmotColorGetPixel(255, 255, 255));
  XFillRectangle(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
  XSetFunction(iupmot_display, dc->pixmap_gc, GXcopy);
}

#include <Xm/XmP.h>
#include <Xm/DrawP.h>

IUP_SDK_API void iupdrvDrawFocusRect(IdrawCanvas* dc, int x1, int y1, int x2, int y2)
{
  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  XmeDrawHighlight(iupmot_display, dc->pixmap, dc->pixmap_gc, x1, y1, x2 - x1 + 1, y2 - y1 + 1, 1);
}

IUP_SDK_API void iupdrvDrawBezier(IdrawCanvas* dc, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, long color, int style, int line_width)
{
  /* X11/Motif does not have native Bezier support - use line approximation */
  XPoint points[21]; /* 20 segments should give smooth curve */
  int i, num_segments = 20;

  /* Set color and line properties */
  XSetForeground(iupmot_display, dc->pixmap_gc, iupmotColorGetPixel(iupDrawRed(color), iupDrawGreen(color), iupDrawBlue(color)));
  XSetLineAttributes(iupmot_display, dc->pixmap_gc, line_width, LineSolid, CapRound, JoinRound);

  /* Generate points along Bezier curve using parametric equation */
  for (i = 0; i <= num_segments; i++)
  {
    double t = (double)i / num_segments;
    double t1 = 1.0 - t;
    double t1_3 = t1 * t1 * t1;
    double t1_2_t = 3.0 * t1 * t1 * t;
    double t1_t_2 = 3.0 * t1 * t * t;
    double t_3 = t * t * t;

    points[i].x = (short)(t1_3 * x1 + t1_2_t * x2 + t1_t_2 * x3 + t_3 * x4);
    points[i].y = (short)(t1_3 * y1 + t1_2_t * y2 + t1_t_2 * y3 + t_3 * y4);
  }

  if (style == IUP_DRAW_FILL)
  {
    /* Fill as polygon */
    XFillPolygon(iupmot_display, dc->pixmap, dc->pixmap_gc, points, num_segments + 1, Nonconvex, CoordModeOrigin);
  }
  else
  {
    /* Draw as polyline */
    XDrawLines(iupmot_display, dc->pixmap, dc->pixmap_gc, points, num_segments + 1, CoordModeOrigin);
  }
}

IUP_SDK_API void iupdrvDrawQuadraticBezier(IdrawCanvas* dc, int x1, int y1, int x2, int y2, int x3, int y3, long color, int style, int line_width)
{
  /* Convert quadratic Bezier to cubic Bezier using the 2/3 formula */
  int cx1, cy1, cx2, cy2;

  cx1 = x1 + ((2 * (x2 - x1)) / 3);
  cy1 = y1 + ((2 * (y2 - y1)) / 3);
  cx2 = x3 + ((2 * (x2 - x3)) / 3);
  cy2 = y3 + ((2 * (y2 - y3)) / 3);

  iupdrvDrawBezier(dc, x1, y1, cx1, cy1, cx2, cy2, x3, y3, color, style, line_width);
}

static long x11InterpolateColor(long color1, long color2, float t)
{
  unsigned char r1 = iupDrawRed(color1), g1 = iupDrawGreen(color1), b1 = iupDrawBlue(color1), a1 = iupDrawAlpha(color1);
  unsigned char r2 = iupDrawRed(color2), g2 = iupDrawGreen(color2), b2 = iupDrawBlue(color2), a2 = iupDrawAlpha(color2);
  unsigned char r = (unsigned char)(r1 + t * (r2 - r1));
  unsigned char g = (unsigned char)(g1 + t * (g2 - g1));
  unsigned char b = (unsigned char)(b1 + t * (b2 - b1));
  unsigned char a = (unsigned char)(a1 + t * (a2 - a1));
  return iupDrawColor(r, g, b, a);
}

IUP_SDK_API void iupdrvDrawLinearGradient(IdrawCanvas* dc, int x1, int y1, int x2, int y2, float angle, long color1, long color2)
{
  int i, steps;
  float frac, dx, dy, length;
  int px1, py1, px2, py2;
  unsigned long pixel;

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  /* Calculate gradient direction */
  float rad = angle * 3.14159265359f / 180.0f;
  dx = (float)cos(rad);
  dy = (float)sin(rad);

  /* Number of steps for smooth gradient */
  length = (float)sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
  steps = (int)length;
  if (steps < 2) steps = 2;
  if (steps > 256) steps = 256;

  /* Draw gradient strips */
  for (i = 0; i < steps; i++)
  {
    frac = (float)i / (float)(steps - 1);
    long color = x11InterpolateColor(color1, color2, frac);

    pixel = iupmotColorGetPixel(iupDrawRed(color), iupDrawGreen(color), iupDrawBlue(color));
    XSetForeground(iupmot_display, dc->pixmap_gc, pixel);

    /* Calculate strip position */
    if (fabs(dx) > fabs(dy))  /* More horizontal */
    {
      px1 = x1 + (int)(frac * (x2 - x1));
      px2 = x1 + (int)((frac + 1.0f / steps) * (x2 - x1));
      py1 = y1;
      py2 = y2;
    }
    else  /* More vertical */
    {
      px1 = x1;
      px2 = x2;
      py1 = y1 + (int)(frac * (y2 - y1));
      py2 = y1 + (int)((frac + 1.0f / steps) * (y2 - y1));
    }

    XFillRectangle(iupmot_display, dc->pixmap, dc->pixmap_gc, px1, py1, px2 - px1 + 1, py2 - py1 + 1);
  }
}

IUP_SDK_API void iupdrvDrawRadialGradient(IdrawCanvas* dc, int cx, int cy, int radius, long colorCenter, long colorEdge)
{
  int i, steps;
  float t, r;
  unsigned long pixel;

  /* Number of steps for smooth gradient */
  steps = radius;
  if (steps < 2) steps = 2;
  if (steps > 256) steps = 256;

  /* Draw from outside to inside */
  for (i = steps - 1; i >= 0; i--)
  {
    t = (float)i / (float)(steps - 1);
    long color = x11InterpolateColor(colorCenter, colorEdge, t);
    r = (float)radius * t;

    pixel = iupmotColorGetPixel(iupDrawRed(color), iupDrawGreen(color), iupDrawBlue(color));
    XSetForeground(iupmot_display, dc->pixmap_gc, pixel);

    XSetArcMode(iupmot_display, dc->pixmap_gc, ArcPieSlice);
    XFillArc(iupmot_display, dc->pixmap, dc->pixmap_gc, (int)(cx - r), (int)(cy - r), (int)(2 * r), (int)(2 * r), 0, 23040);
  }
}

static void iX11CopyPixelsToRgba(unsigned char* dst, XImage* ximage, int w, int h)
{
  int x, y;
  for (y = 0; y < h; y++)
  {
    unsigned char* dst_line = dst + y * w * 4;
    for (x = 0; x < w; x++)
    {
      unsigned long pixel = XGetPixel(ximage, x, y);
      dst_line[x * 4 + 0] = (unsigned char)((pixel >> 16) & 0xFF);
      dst_line[x * 4 + 1] = (unsigned char)((pixel >> 8) & 0xFF);
      dst_line[x * 4 + 2] = (unsigned char)((pixel >> 0) & 0xFF);
      dst_line[x * 4 + 3] = 255;
    }
  }
}

IUP_SDK_API int iupdrvDrawGetImageData(IdrawCanvas* dc, unsigned char* data)
{
  XImage* ximage = XGetImage(iupmot_display, dc->pixmap, 0, 0, dc->w, dc->h, AllPlanes, ZPixmap);
  if (!ximage)
    return 0;

  iX11CopyPixelsToRgba(data, ximage, dc->w, dc->h);

  XDestroyImage(ximage);
  return 1;
}

IUP_SDK_API int iupdrvCanvasGetImageData(Ihandle* ih, unsigned char* data, int w, int h)
{
  XImage* ximage;

  Window wnd = XtWindow((Widget)ih->handle);
  if (!wnd)
    return 0;

  ximage = XGetImage(iupmot_display, wnd, 0, 0, w, h, AllPlanes, ZPixmap);
  if (!ximage)
    return 0;

  iX11CopyPixelsToRgba(data, ximage, w, h);

  XDestroyImage(ximage);
  return 1;
}
