/** \file
 * \brief External API
 *
 * See Copyright Notice in cd.h
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <memory.h>
#include <stdarg.h>


#include "cd.h"
#include "wd.h"
#include "cd_private.h"
#include "cdirgb.h"

/* This appears only here to avoid changing the cd.h header fo bug fixes */
#define CD_VERSION_FIX ""
#define CD_VERSION_FIX_NUMBER 0
/* #define CD_VERSION_FIX_DATE "" */

const char cd_ident[] =
  "$CD: " CD_VERSION CD_VERSION_FIX " " CD_COPYRIGHT " $\n"
  "$URL: www.tecgraf.puc-rio.br/cd $\n";

static char *tecver = "TECVERID.str:CD:LIB:" CD_VERSION CD_VERSION_FIX;

char* cdVersion(void)
{              
  (void)cd_ident;
	(void)tecver;
  return CD_VERSION CD_VERSION_FIX;
}

char* cdVersionDate(void)
{
#ifdef CD_VERSION_FIX_DATE
  return CD_VERSION_FIX_DATE;
#else
  return CD_VERSION_DATE;
#endif
}
 
int cdVersionNumber(void)
{
  return CD_VERSION_NUMBER+CD_VERSION_FIX_NUMBER;
}

static void cd_setdefaultfunc(cdCanvas* canvas)
{
  canvas->cxGetTextSize = cdgettextsizeEX;
  canvas->cxGetFontDim = cdgetfontdimEX;
  canvas->cxRect = cdSimRect;
}

static void cd_setdefaultattrib(cdCanvas* canvas)
{
  /* clipping attributes */
  canvas->clip_mode = CD_CLIPOFF;

  /* color attributes */
  canvas->foreground = CD_BLACK;
  canvas->background = CD_WHITE;

  canvas->back_opacity = CD_TRANSPARENT;
  canvas->write_mode = CD_REPLACE;

  /* primitive attributes */
  canvas->mark_type = CD_STAR;
  canvas->mark_size = 10;

  canvas->line_width = 1;
  canvas->line_style = CD_CONTINUOUS;
  canvas->line_cap = CD_CAPFLAT;
  canvas->line_join = CD_MITER;

  canvas->hatch_style = CD_HORIZONTAL;
  canvas->interior_style = CD_SOLID;
  canvas->fill_mode = CD_EVENODD;

  strcpy(canvas->font_type_face, "System");
  canvas->font_style     = CD_PLAIN;
  canvas->font_size      = CD_STANDARD;

  canvas->text_alignment = CD_BASE_LEFT;

  canvas->matrix[0] = 1;  /* identity */
  canvas->matrix[3] = 1;

  /* everything else is 0 because of the memset */
}

void cdUpdateAttributes(cdCanvas* canvas)
{
  cdCtxCanvas* ctxcanvas = canvas->ctxcanvas;

  if (canvas->cxBackground) canvas->cxBackground(ctxcanvas, canvas->background);
  if (canvas->cxForeground) canvas->cxForeground(ctxcanvas, canvas->foreground);
  if (canvas->cxBackOpacity) canvas->cxBackOpacity(ctxcanvas, canvas->back_opacity);
  if (canvas->cxWriteMode) canvas->cxWriteMode(ctxcanvas, canvas->write_mode);

  if (canvas->cxLineStyle) canvas->cxLineStyle(ctxcanvas, canvas->line_style);
  if (canvas->cxLineWidth) canvas->cxLineWidth(ctxcanvas, canvas->line_width);
  if (canvas->cxLineCap) canvas->cxLineCap(ctxcanvas, canvas->line_cap);
  if (canvas->cxLineJoin) canvas->cxLineJoin(ctxcanvas, canvas->line_join);

  if (canvas->cxHatch) canvas->cxHatch(ctxcanvas, canvas->hatch_style);
  if (canvas->stipple && canvas->cxStipple) canvas->cxStipple(ctxcanvas, canvas->stipple_w, canvas->stipple_h, canvas->stipple);
  if (canvas->pattern && canvas->cxPattern) canvas->cxPattern(ctxcanvas, canvas->pattern_w, canvas->pattern_h, canvas->pattern);
  if (canvas->cxInteriorStyle) canvas->cxInteriorStyle(ctxcanvas, canvas->interior_style);

  if (canvas->native_font[0] && canvas->cxNativeFont)
    canvas->cxNativeFont(ctxcanvas, canvas->native_font);
  else if (canvas->cxFont) 
    canvas->cxFont(ctxcanvas, canvas->font_type_face, canvas->font_style, canvas->font_size);
  if (canvas->cxTextAlignment) canvas->cxTextAlignment(ctxcanvas, canvas->text_alignment);
  if (canvas->cxTextOrientation) canvas->cxTextOrientation(ctxcanvas, canvas->text_orientation);

  if (canvas->use_matrix && canvas->cxTransform) canvas->cxTransform(ctxcanvas, canvas->matrix);

  if (canvas->clip_mode == CD_CLIPAREA && canvas->cxClipArea) canvas->cxClipArea(ctxcanvas, canvas->clip_rect.xmin, canvas->clip_rect.xmax, canvas->clip_rect.ymin, canvas->clip_rect.ymax);
  if (canvas->clip_mode == CD_CLIPAREA && canvas->cxFClipArea) canvas->cxFClipArea(ctxcanvas, canvas->clip_frect.xmin, canvas->clip_frect.xmax, canvas->clip_frect.ymin, canvas->clip_frect.ymax);
  if (canvas->clip_mode == CD_CLIPPOLYGON && canvas->clip_poly) canvas->cxPoly(ctxcanvas, CD_CLIP, canvas->clip_poly, canvas->clip_poly_n);
  if (canvas->clip_mode == CD_CLIPPOLYGON && canvas->clip_fpoly) canvas->cxFPoly(ctxcanvas, CD_CLIP, canvas->clip_fpoly, canvas->clip_poly_n);
  if (canvas->clip_mode != CD_CLIPOFF && canvas->cxClip) canvas->cxClip(ctxcanvas, canvas->clip_mode);
}

static void set_userdata_attrib(cdCtxCanvas* ctxcanvas, char* data)
{
  cdCanvas* canvas = ((cdCtxCanvasBase*)ctxcanvas)->canvas;
  canvas->userdata = data;
}

static char* get_userdata_attrib(cdCtxCanvas* ctxcanvas)
{
  cdCanvas* canvas = ((cdCtxCanvasBase*)ctxcanvas)->canvas;
  return canvas->userdata;
}

static cdAttribute userdata_attrib =
{
  "USERDATA",
  set_userdata_attrib,
  get_userdata_attrib
};

cdCanvas* cdCreateCanvasf(cdContext *context, const char* format, ...)
{
  char data[10240];
  va_list arglist;
  va_start(arglist, format);
  vsnprintf(data, 10240, format, arglist);
  va_end(arglist);

  return cdCreateCanvas(context, data);
}

cdCanvas *cdCreateCanvas(cdContext* context, void *data_str)
{
  cdCanvas *canvas;

  /* useful for NULL drivers, that do nothing and exist only for portability */
  if (!context)
    return NULL;

  {
    static int first = 1;
    char* env = getenv("CD_QUIET");
    if (first && env && strcmp(env, "NO") == 0)  /* default is quiet */
    {
      printf("CD  " CD_VERSION CD_VERSION_FIX " " CD_COPYRIGHT "\n");
      first = 0;
    }
  }

  /* allocates and initialize everything with 0s */
  canvas = (cdCanvas*)malloc(sizeof(cdCanvas));
  memset(canvas, 0, sizeof(cdCanvas));

  canvas->signature[0] = 'C';
  canvas->signature[1] = 'D';

  canvas->vector_font = cdCreateVectorFont(canvas);
  canvas->simulation = cdCreateSimulation(canvas);

  canvas->context = context;

  /* initialize default attributes, must be before creating the canvas */
  cd_setdefaultattrib(canvas);

  context->cxCreateCanvas(canvas, data_str);
  if (!canvas->ctxcanvas)
  {
    cdKillVectorFont(canvas->vector_font);
    cdKillSimulation(canvas->simulation);
    memset(canvas, 0, sizeof(cdCanvas));
    free(canvas);
    return NULL;
  }

  /* default simulation functions */
  cd_setdefaultfunc(canvas);

  /* initialize canvas table */
  context->cxInitTable(canvas);

  /* update the default attributes, must be after InitTable */
  cdCanvasActivate(canvas);
  cdUpdateAttributes(canvas);

  /* must be after creating the canvas, so that we know canvas width and height */
  canvas->clip_rect.xmax = canvas->w-1;
  canvas->clip_rect.ymax = canvas->h-1;

  wdSetDefaults(canvas);

  cdRegisterAttribute(canvas, &userdata_attrib);

  return canvas;
}

void cdKillCanvas(cdCanvas *canvas)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return;

  cdCanvasDeactivate(canvas);
  
  canvas->cxKillCanvas(canvas->ctxcanvas);

  if (canvas->pattern) free(canvas->pattern);
  if (canvas->stipple) free(canvas->stipple);
  if (canvas->poly) free(canvas->poly);
  if (canvas->clip_poly) free(canvas->clip_poly);
  if (canvas->fpoly) free(canvas->fpoly);
  if (canvas->clip_fpoly) free(canvas->clip_fpoly);
  if (canvas->line_dashes) free(canvas->line_dashes);
  if (canvas->path) free(canvas->path);

  cdKillVectorFont(canvas->vector_font);
  cdKillSimulation(canvas->simulation);

  memset(canvas, 0, sizeof(cdCanvas));
  free(canvas);
}

cdContext* cdCanvasGetContext(cdCanvas *canvas)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return NULL;
  return canvas->context;
}

int cdCanvasActivate(cdCanvas *canvas)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return CD_ERROR;
  if (!canvas->cxActivate) return CD_OK;

  if (canvas->cxActivate(canvas->ctxcanvas) == CD_ERROR)
    return CD_ERROR;
  
  return CD_OK;
}

void cdCanvasDeactivate(cdCanvas *canvas)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas) || !canvas->cxDeactivate) return;
  canvas->cxDeactivate(canvas->ctxcanvas);
}

unsigned long cdContextCaps(cdContext *context)
{
  if (!context)
    return (unsigned long)CD_ERROR;
  return context->caps;
}

int cdContextIsPlus(cdContext *context)
{
  if (!context)
    return CD_ERROR;
  return context->type&CD_CTX_PLUS? 1: 0;
}

int cdContextType(cdContext *context)
{
  if (!context)
    return CD_ERROR;
  return context->type&0x00FF;
}

int cdCanvasSimulate(cdCanvas* canvas, int mode)
{
  int old_sim_mode;
  cdContext* context;

  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return CD_ERROR;

  context = canvas->context;

  old_sim_mode = canvas->sim_mode;
  if (mode == CD_QUERY || cdCanvasGetContext(canvas) == CD_IMAGERGB)
    return old_sim_mode;

  /* default simulation functions */
  cd_setdefaultfunc(canvas);

  /* initialize canvas table */
  context->cxInitTable(canvas);

  canvas->sim_mode = mode;
  if (mode == CD_SIM_NONE)
    return old_sim_mode;

  /* when simulation is active must not set driver transform */
  canvas->cxTransform = NULL;

  if (mode & CD_SIM_LINE)
  {
    canvas->cxLine = cdSimLine;
    canvas->cxFLine = NULL;
  }

  if (mode & CD_SIM_RECT)
  {
    canvas->cxRect = cdSimRect;
    canvas->cxFRect = NULL;
  }

  if (mode & CD_SIM_BOX)
  {
    canvas->cxBox = cdSimBox;
    canvas->cxFBox = NULL;
  }

  if (mode & CD_SIM_ARC)
  {
    canvas->cxArc = cdSimArc;
    canvas->cxFArc = NULL;
  }

  if (mode & CD_SIM_SECTOR)
  {
    canvas->cxSector = cdSimSector;
    canvas->cxFSector = NULL;
  }

  if (mode & CD_SIM_CHORD)
  {
    canvas->cxChord = cdSimChord;
    canvas->cxFChord = NULL;
  }

  if (mode & CD_SIM_TEXT)
  {
    canvas->cxText = cdSimulationText;
    canvas->cxFText = NULL;
    canvas->cxNativeFont = NULL;
    canvas->cxFont = cdSimulationFont;
    canvas->cxGetFontDim = cdSimulationGetFontDim;
    canvas->cxGetTextSize = cdSimulationGetTextSize;
    canvas->cxTextOrientation = NULL;

    cdSimulationInitText(canvas->simulation);
    canvas->cxFont(canvas->ctxcanvas, canvas->font_type_face, canvas->font_style, canvas->font_size);
  }
  else
    canvas->cxFont(canvas->ctxcanvas, canvas->font_type_face, canvas->font_style, canvas->font_size);

  if (mode & CD_SIM_POLYLINE || mode & CD_SIM_POLYGON)
  {
    /* can NOT replace canvas->cxPoly because it will be used by the simulation,
       handle polygon simulation in cdCanvasEnd by calling cdPoly */
    canvas->cxFPoly = NULL;
  }

  return old_sim_mode;
}

cdState* cdCanvasSaveState(cdCanvas* canvas)
{
  cdState* state;

  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return NULL;

  state = (cdState*)malloc(sizeof(cdState));
  memcpy(state, canvas, sizeof(cdCanvas));  /* is actually a cdCanvas */

  if (state->pattern) 
  {
    int size = state->pattern_w*state->pattern_h*sizeof(long);
    state->pattern = (long*)malloc(size);
    memcpy(state->pattern, canvas->pattern, size);
  }

  if (state->stipple) 
  {
    int size = state->stipple_w*state->stipple_h;
    state->stipple = (unsigned char*)malloc(size);
    memcpy(state->stipple, canvas->stipple, size);
  }

  if (state->clip_poly) 
  {
    int size = state->clip_poly_n*sizeof(cdPoint);
    state->clip_poly = (cdPoint*)malloc(size);
    memcpy(state->clip_poly, canvas->clip_poly, size);
  }

  if (state->clip_fpoly) 
  {
    int size = state->clip_poly_n*sizeof(cdfPoint);
    state->clip_fpoly = (cdfPoint*)malloc(size);
    memcpy(state->clip_fpoly, canvas->clip_fpoly, size);
  }

  if (state->line_dashes) 
  {
    int size = state->line_dashes_count*sizeof(int);
    state->line_dashes = (int*)malloc(size);
    memcpy(state->line_dashes, canvas->line_dashes, size);
  }

  return state;
}

void cdReleaseState(cdState* state)
{
  assert(state);
  if (!state) return;

  if (state->stipple) 
    free(state->stipple);

  if (state->pattern) 
    free(state->pattern);

  if (state->clip_poly) 
    free(state->clip_poly);

  if (state->clip_fpoly) 
    free(state->clip_fpoly);

  if (state->line_dashes) 
    free(state->line_dashes);

  free(state);
}

void cdCanvasRestoreState(cdCanvas* canvas, cdState* state)
{
  assert(canvas);
  assert(state);
  if (!state || !_cdCheckCanvas(canvas)) return;

  /* clippling must be done in low level because origin and invert y axis */
  canvas->clip_poly_n = state->clip_poly_n;

  if (canvas->clip_poly) 
  {
    free(canvas->clip_poly);
    canvas->clip_poly = NULL;
  }

  if (canvas->clip_fpoly) 
  {
    free(canvas->clip_fpoly);
    canvas->clip_fpoly = NULL;
  }

  if (state->clip_poly) 
  {
    int size = state->clip_poly_n*sizeof(cdPoint);
    canvas->clip_poly = (cdPoint*)malloc(size);
    memcpy(canvas->clip_poly, state->clip_poly, size);
  }

  if (state->clip_fpoly) 
  {
    int size = state->clip_poly_n*sizeof(cdfPoint);
    canvas->clip_fpoly = (cdfPoint*)malloc(size);
    memcpy(canvas->clip_fpoly, state->clip_fpoly, size);
  }

  cdCanvasClip(canvas, CD_CLIPOFF);
  if (canvas->clip_fpoly)
    canvas->cxFPoly(canvas->ctxcanvas, CD_CLIP, state->clip_fpoly, state->clip_poly_n);
  else if (canvas->clip_poly)
    canvas->cxPoly(canvas->ctxcanvas, CD_CLIP, state->clip_poly, state->clip_poly_n);
  cdCanvasClipArea(canvas, state->clip_rect.xmin, state->clip_rect.xmax, state->clip_rect.ymin, state->clip_rect.ymax);
  if (canvas->cxFClipArea)
    canvas->cxFClipArea(canvas->ctxcanvas, state->clip_frect.xmin, state->clip_frect.xmax, state->clip_frect.ymin, state->clip_frect.ymax);
  else if (canvas->cxClipArea)
    canvas->cxClipArea(canvas->ctxcanvas, state->clip_rect.xmin, state->clip_rect.xmax, state->clip_rect.ymin, state->clip_rect.ymax);
  cdCanvasClip(canvas, state->clip_mode);

  /* regular attributes */
  cdCanvasSetBackground(canvas, state->background);
  cdCanvasSetForeground(canvas, state->foreground);
  cdCanvasBackOpacity(canvas, state->back_opacity);
  cdCanvasWriteMode(canvas, state->write_mode);
  cdCanvasLineStyle(canvas, state->line_style);
  cdCanvasLineWidth(canvas, state->line_width);
  cdCanvasLineCap(canvas, state->line_cap);
  cdCanvasLineJoin(canvas, state->line_join);
  cdCanvasFillMode(canvas, state->fill_mode);
  cdCanvasLineStyleDashes(canvas, state->line_dashes, state->line_dashes_count);
  cdCanvasHatch(canvas, state->hatch_style);
  if (state->stipple) cdCanvasStipple(canvas, state->stipple_w, state->stipple_h, state->stipple);
  if (state->pattern) cdCanvasPattern(canvas, state->pattern_w, state->pattern_h, state->pattern);
  cdCanvasInteriorStyle(canvas, state->interior_style);
  if (state->native_font[0])
    cdCanvasNativeFont(canvas, state->native_font);
  else
    cdCanvasFont(canvas, state->font_type_face, state->font_style, state->font_size);
  cdCanvasTextAlignment(canvas, state->text_alignment);
  cdCanvasTextOrientation(canvas, state->text_orientation);
  cdCanvasMarkType(canvas, state->mark_type);
  cdCanvasMarkSize(canvas, state->mark_size);
  cdCanvasOrigin(canvas, state->origin.x, state->origin.y);
  if (state->use_matrix)
    cdCanvasTransform(canvas, state->matrix);
  wdCanvasWindow(canvas, state->window.xmin, state->window.xmax, state->window.ymin, state->window.ymax);
  wdCanvasViewport(canvas, state->viewport.xmin, state->viewport.xmax, state->viewport.ymin, state->viewport.ymax);
  cdCanvasSimulate(canvas, state->sim_mode);

  /* complex clipping regions are not saved */
  /* driver internal attributes are not saved */
}

static cdAttribute* cd_findattrib(cdCanvas *canvas, const char* name, int *a)
{
  int i;

  for (i=0; i < canvas->attrib_n; i++)
  {
    if (strcmp(name, canvas->attrib_list[i]->name) == 0)
    {
      if (a) *a = i;
      return canvas->attrib_list[i];
    }
  }

  return NULL;
}

void cdRegisterAttribute(cdCanvas *canvas, cdAttribute* attrib)
{
  cdAttribute* old_attrib;
  int a;

  assert(canvas);
  assert(attrib);
  if (!attrib || !_cdCheckCanvas(canvas)) return;

  old_attrib = cd_findattrib(canvas, attrib->name, &a);

  if (old_attrib)
    canvas->attrib_list[a] = attrib;
  else
  {
    canvas->attrib_list[canvas->attrib_n] = attrib;
    canvas->attrib_n++;
  }
}

void cdCanvasSetAttribute(cdCanvas* canvas, const char* name, char *data)
{
  cdAttribute* attrib;

  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return;

  attrib = cd_findattrib(canvas, name, NULL);
  if (attrib && attrib->set)
    attrib->set(canvas->ctxcanvas, data);
}

void cdCanvasSetfAttribute(cdCanvas* canvas, const char* name, const char* format, ...)
{
  char data[10240];
  va_list arglist;
  va_start(arglist, format);
  vsnprintf(data, 10240, format, arglist);
  va_end(arglist);

  cdCanvasSetAttribute(canvas, name, data);
}

char* cdCanvasGetAttribute(cdCanvas* canvas, const char* name)
{
  cdAttribute* attrib;

  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return NULL;

  attrib = cd_findattrib(canvas, name, NULL);
  if (attrib && attrib->get)
    return attrib->get(canvas->ctxcanvas);

  return NULL;
}

int cdCanvasPlay(cdCanvas* canvas, cdContext* context, int xmin, int xmax, int ymin, int ymax, void *data)
{
  assert(context);
  assert(canvas);
  if (!_cdCheckCanvas(canvas) || !context || !context->cxPlay) return CD_ERROR;

  /* the all can be 0 here, do not use cdCheckBoxSize */
  if (xmin > xmax) _cdSwapInt(xmin, xmax);
  if (ymin > ymax) _cdSwapInt(ymin, ymax);

  return context->cxPlay(canvas, xmin, xmax, ymin, ymax, data);
}

int cdContextRegisterCallback(cdContext *context, int cb, cdCallback func)
{
  assert(context);
  if (!context || !context->cxRegisterCallback) return CD_ERROR;
  return context->cxRegisterCallback(cb, func);
}

void cdCanvasFlush(cdCanvas* canvas)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas) || !canvas->cxFlush) return;
  canvas->cxFlush(canvas->ctxcanvas);
}

void cdCanvasClear(cdCanvas* canvas)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas) || !canvas->cxClear) return;
  canvas->cxClear(canvas->ctxcanvas);
}

/* hidden function to simply control invert_yaxis behavior.
   several features will NOT behave as expected, such as 
   arc direction, text position, image position and vector text */
int cdCanvasYAxisMode(cdCanvas* canvas, int invert)
{
  int old_invert_yaxis;
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return CD_ERROR;

  if (invert == CD_QUERY)
    return canvas->invert_yaxis;

  old_invert_yaxis = canvas->invert_yaxis;
  canvas->invert_yaxis = invert;
  return old_invert_yaxis;
}

int cdCanvasUpdateYAxis(cdCanvas* canvas, int* y)
{
  assert(canvas);
  assert(y);
  if (!_cdCheckCanvas(canvas)) return CD_ERROR;

  if(!(canvas->context->caps&CD_CAP_YAXIS))
  {
    *y = _cdInvertYAxis(canvas, *y);

    if (canvas->use_origin)
      *y -= 2*canvas->origin.y;
  }

  return *y;
}

double cdfCanvasUpdateYAxis(cdCanvas* canvas, double* y)
{
  assert(canvas);
  assert(y);
  if (!_cdCheckCanvas(canvas)) return CD_ERROR;

  if(!(canvas->context->caps&CD_CAP_YAXIS))
  {
    *y = _cdInvertYAxis(canvas, *y);

    if (canvas->use_origin)
      *y -= 2*canvas->origin.y;
  }

  return *y;
}

int cdCanvasInvertYAxis(cdCanvas* canvas, int y)
{
  int yi;
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return CD_ERROR;

  yi = _cdInvertYAxis(canvas, y);

  if (canvas->use_origin)
    yi -= 2*canvas->origin.y;

  return yi;
}

double cdfCanvasInvertYAxis(cdCanvas* canvas, double y)
{
  double yi;
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return CD_ERROR;

  yi = _cdInvertYAxis(canvas, y);

  if (canvas->use_origin)
    yi -= 2*canvas->origin.y;

  return yi;
}

void cdCanvasGetSize(cdCanvas* canvas, int *width, int *height, double *width_mm, double *height_mm)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return;

  if (width) *width = canvas->w;
  if (height) *height = canvas->h;
  if (width_mm) *width_mm = canvas->w_mm;
  if (height_mm) *height_mm = canvas->h_mm;
}

void cdCanvasMM2Pixel(cdCanvas* canvas, double mm_dx, double mm_dy, int *dx, int *dy)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return;

  if (dx) *dx = cdRound(mm_dx*canvas->xres);
  if (dy) *dy = cdRound(mm_dy*canvas->yres);
}

void cdCanvasPixel2MM(cdCanvas* canvas, int dx, int dy, double *mm_dx, double *mm_dy)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return;

  if (mm_dx) *mm_dx = ((double)dx)/canvas->xres;
  if (mm_dy) *mm_dy = ((double)dy)/canvas->yres;
}

void cdfCanvasMM2Pixel(cdCanvas* canvas, double mm_dx, double mm_dy, double *dx, double *dy)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return;

  if (dx) *dx = mm_dx*canvas->xres;
  if (dy) *dy = mm_dy*canvas->yres;
}

void cdfCanvasPixel2MM(cdCanvas* canvas, double dx, double dy, double *mm_dx, double *mm_dy)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return;

  if (mm_dx) *mm_dx = dx/canvas->xres;
  if (mm_dy) *mm_dy = dy/canvas->yres;
}

/***** Context Plus Functions ********/

static int use_context_plus = 0;
static cdContext* context_plus[CD_CTXPLUS_COUNT] = {NULL, NULL, NULL, NULL, NULL, NULL};

int cdUseContextPlus(int use)
{
  int old_use_context_plus = use_context_plus;

  if (use == CD_QUERY)
    return use_context_plus;

  use_context_plus = use;
  return old_use_context_plus;
}

void cdInitContextPlusList(cdContext* ctx_list[])
{
  int ctx;
  for (ctx = 0; ctx < CD_CTXPLUS_COUNT; ctx++)
    if (ctx_list[ctx] != NULL)
      context_plus[ctx] = ctx_list[ctx];
}

cdContext* cdGetContextPlus(int ctx)
{
  if (ctx < 0 || ctx >= CD_CTXPLUS_COUNT)
    return NULL;

  return context_plus[ctx];
}

/***** OLD Compatibility Functions ********/

int cdRegisterCallback(cdContext *context, int cb, cdCallback func)
{
  return cdContextRegisterCallback(context, cb, func);
}

cdContext* cdGetContext(cdCanvas* canvas)
{
  return cdCanvasGetContext(canvas);
}

int * cdGetClipPoly(int *n)
{
  if (n) *n = 0;
  return NULL;
}

double* wdGetClipPoly(int *n)
{
  if (n) *n = 0;
  return NULL;
}
