/* lmplib.c
   
   Copyright 2006-2009 Taco Hoekwater <taco@luatex.org>

   This file is part of LuaTeX.

   LuaTeX is free software; you can redistribute it and/or modify it under
   the terms of the GNU Lesser General Public License as published by the Free
   Software Foundation; either version 3 of the License, or (at your
   option) any later version.

   LuaTeX is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
   License for more details.

   You should have received a copy of the GNU Lesser General Public License along
   with LuaTeX; if not, see <http://www.gnu.org/licenses/>. */

#include "config.h"
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <math.h> /* temporary */

#ifdef WIN32
#define R_OK 4
#endif

#ifndef pdfTeX
#  include <lua.h>
#  include <lauxlib.h>
#  include <lualib.h>
#else
#  include <../lua51/lua.h>
#  include <../lua51/lauxlib.h>
#  include <../lua51/lualib.h>
#endif

#include "mplib.h"
#include "mplibps.h"
#include "mplibsvg.h"

   /*@unused@*/ static const char _svn_version[] =
    "$Id: lmplib.c 1364 2008-07-04 16:09:46Z taco $ $URL: http://scm.foundry.supelec.fr/svn/luatex/trunk/src/texk/web2c/luatexdir/lua/lmplib.c $";

/* metatable identifiers and tests */

#define MPLIB_METATABLE     "MPlib"
#define MPLIB_FIG_METATABLE "MPlib.fig"
#define MPLIB_GR_METATABLE  "MPlib.gr"

#define is_mp(L,b) (MP *)luaL_checkudata(L,b,MPLIB_METATABLE)
#define is_fig(L,b) (struct mp_edge_object **)luaL_checkudata(L,b,MPLIB_FIG_METATABLE)
#define is_gr_object(L,b) (struct mp_graphic_object **)luaL_checkudata(L,b,MPLIB_GR_METATABLE)

/* Lua string pre-hashing */

#define mplib_init_S(a) do {                                            \
    lua_pushliteral(L,#a);                                              \
    mplib_##a##_ptr = (char *)lua_tostring(L,-1);                       \
    mplib_##a##_index = luaL_ref (L,LUA_REGISTRYINDEX);                 \
  } while (0)

#define mplib_push_S(a) do {                                    \
    lua_rawgeti(L,LUA_REGISTRYINDEX,mplib_##a##_index);         \
  } while (0)

#define mplib_is_S(a,i) (mplib_##a##_ptr==(char *)lua_tostring(L,i))

#define mplib_make_S(a)                                                 \
  static int mplib_##a##_index = 0;                                     \
  static char *mplib_##a##_ptr = NULL

static int mplib_type_Ses[mp_special_code + 1] = { 0 }; /* [0] is not used */

mplib_make_S(fill);
mplib_make_S(outline);
mplib_make_S(text);
mplib_make_S(special);
mplib_make_S(start_bounds);
mplib_make_S(stop_bounds);
mplib_make_S(start_clip);
mplib_make_S(stop_clip);

mplib_make_S(left_type);
mplib_make_S(right_type);
mplib_make_S(x_coord);
mplib_make_S(y_coord);
mplib_make_S(left_x);
mplib_make_S(left_y);
mplib_make_S(right_x);
mplib_make_S(right_y);

mplib_make_S(color);
mplib_make_S(dash);
mplib_make_S(depth);
mplib_make_S(dsize);
mplib_make_S(font);
mplib_make_S(height);
mplib_make_S(htap);
mplib_make_S(linecap);
mplib_make_S(linejoin);
mplib_make_S(miterlimit);
mplib_make_S(path);
mplib_make_S(pen);
mplib_make_S(postscript);
mplib_make_S(prescript);
mplib_make_S(transform);
mplib_make_S(type);
mplib_make_S(width);

static void mplib_init_Ses(lua_State * L)
{
    mplib_init_S(fill);
    mplib_init_S(outline);
    mplib_init_S(text);
    mplib_init_S(start_bounds);
    mplib_init_S(stop_bounds);
    mplib_init_S(start_clip);
    mplib_init_S(stop_clip);
    mplib_init_S(special);

    mplib_type_Ses[mp_fill_code] = mplib_fill_index;
    mplib_type_Ses[mp_stroked_code] = mplib_outline_index;
    mplib_type_Ses[mp_text_code] = mplib_text_index;
    mplib_type_Ses[mp_start_bounds_code] = mplib_start_bounds_index;
    mplib_type_Ses[mp_stop_bounds_code] = mplib_stop_bounds_index;
    mplib_type_Ses[mp_start_clip_code] = mplib_start_clip_index;
    mplib_type_Ses[mp_stop_clip_code] = mplib_stop_clip_index;
    mplib_type_Ses[mp_special_code] = mplib_special_index;

    mplib_init_S(left_type);
    mplib_init_S(right_type);
    mplib_init_S(x_coord);
    mplib_init_S(y_coord);
    mplib_init_S(left_x);
    mplib_init_S(left_y);
    mplib_init_S(right_x);
    mplib_init_S(right_y);

    mplib_init_S(color);
    mplib_init_S(dash);
    mplib_init_S(depth);
    mplib_init_S(dsize);
    mplib_init_S(font);
    mplib_init_S(height);
    mplib_init_S(htap);
    mplib_init_S(linecap);
    mplib_init_S(linejoin);
    mplib_init_S(miterlimit);
    mplib_init_S(path);
    mplib_init_S(pen);
    mplib_init_S(postscript);
    mplib_init_S(prescript);
    mplib_init_S(transform);
    mplib_init_S(type);
    mplib_init_S(width);
}


/* Enumeration arrays to map MPlib enums to Lua strings */

static const char *interaction_options[] =
    { "unknown", "batch", "nonstop", "scroll", "errorstop", NULL };

static const char *mplib_filetype_names[] =
    { "term", "error", "mp", "log", "ps", "mem", "tfm", "map", "pfb", "enc", NULL };

static const char *knot_type_enum[] =
    { "endpoint", "explicit", "given", "curl", "open", "end_cycle" };

static const char *fill_fields[] =
    { "type", "path", "htap", "pen", "color", "linejoin", "miterlimit",
    "prescript", "postscript", NULL };

static const char *stroked_fields[] =
    { "type", "path", "pen", "color", "linejoin", "miterlimit", "linecap",
      "dash", "prescript", "postscript", NULL };

static const char *text_fields[] =
    { "type", "text", "dsize", "font", "color", "width", "height", "depth",
      "transform", "prescript", "postscript", NULL };

static const char *special_fields[] =
    { "type", "prescript", NULL };

static const char *start_bounds_fields[] =
    { "type", "path", NULL };

static const char *start_clip_fields[] = 
    { "type", "path", NULL };

static const char *stop_bounds_fields[] = 
    { "type", NULL };

static const char *stop_clip_fields[] = 
    { "type", NULL };

static const char *no_fields[] = 
    { NULL };


/* The list of supported MPlib options (not all make sense) */

typedef enum {
    P_ERROR_LINE, P_MAX_LINE, 
    P_MAIN_MEMORY, P_HASH_SIZE, P_PARAM_SIZE, P_IN_OPEN, P_RANDOM_SEED,
    P_INTERACTION, P_INI_VERSION, P_MEM_NAME, P_JOB_NAME, P_FIND_FILE, 
    P__SENTINEL } mplib_parm_idx;

typedef struct {
    const char *name;           /* parameter name */
    mplib_parm_idx idx;         /* parameter index */
} mplib_parm_struct;

static mplib_parm_struct mplib_parms[] = {
    {"error_line",        P_ERROR_LINE  },
    {"print_line",        P_MAX_LINE    },
    {"main_memory",       P_MAIN_MEMORY },
    {"hash_size",         P_HASH_SIZE   },
    {"param_size",        P_PARAM_SIZE  },
    {"max_in_open",       P_IN_OPEN     },
    {"random_seed",       P_RANDOM_SEED },
    {"interaction",       P_INTERACTION },
    {"ini_version",       P_INI_VERSION },
    {"mem_name",          P_MEM_NAME    },
    {"job_name",          P_JOB_NAME    },
    {"find_file",         P_FIND_FILE   },
    {NULL,                P__SENTINEL   }
};


/* Start by defining the needed callback routines for the library  */

static char *mplib_find_file(MP mp, const char *fname, const char *fmode, int ftype)
{
    lua_State *L = (lua_State *)mp_userdata(mp);
    lua_checkstack(L, 4);
    lua_getfield(L, LUA_REGISTRYINDEX, "mplib_file_finder");
    if (lua_isfunction(L, -1)) {
        char *s = NULL, *x = NULL;
        lua_pushstring(L, fname);
        lua_pushstring(L, fmode);
        if (ftype >= mp_filetype_text) {
          lua_pushnumber(L, (lua_Number)(ftype - mp_filetype_text));
        } else {
            lua_pushstring(L, mplib_filetype_names[ftype]);
        }
        if (lua_pcall(L, 3, 1, 0) != 0) {
            fprintf(stdout, "Error in mp.find_file: %s\n",
                    (char *) lua_tostring(L, -1));
            return NULL;
        }
        x = (char *) lua_tostring(L, -1);
        if (x != NULL)
            s = strdup(x);
        lua_pop(L, 1);          /* pop the string */
        return s;
    } else {
        lua_pop(L, 1);
    }
    if (fmode[0] != 'r' || (!access(fname, R_OK)) || ftype) {
        return strdup(fname);
    }
    return NULL;
}

static int mplib_find_file_function(lua_State * L)
{
    if (!(lua_isfunction(L, -1) || lua_isnil(L, -1))) {
        return 1;               /* error */
    }
    lua_pushstring(L, "mplib_file_finder");
    lua_pushvalue(L, -2);
    lua_rawset(L, LUA_REGISTRYINDEX);
    return 0;
}

#define xfree(A) if ((A)!=NULL) { free((A)); A = NULL; }

static int mplib_new(lua_State * L)
{
    MP *mp_ptr;
    mp_ptr = lua_newuserdata(L, sizeof(MP *));
    if (mp_ptr) {
        int i;
        struct MP_options *options = mp_options();
        options->userdata = (void *) L;
        options->noninteractive = 1;    /* required ! */
        options->find_file = mplib_find_file;
        options->print_found_names = 1;
        if (lua_type(L, 1) == LUA_TTABLE) {
            for (i = 0; mplib_parms[i].name != NULL; i++) {
                lua_getfield(L, 1, mplib_parms[i].name);
                if (lua_isnil(L, -1)) {
                    lua_pop(L, 1);
                    continue;   /* skip unset */
                }
                switch (mplib_parms[i].idx) {
                case P_ERROR_LINE:
                  options->error_line = (int)lua_tointeger(L, -1);
                    if (options->error_line<60) options->error_line =60;
                    if (options->error_line>250) options->error_line = 250;
                    options->half_error_line = (options->error_line/2)+10;
                    break;
                case P_MAX_LINE:
                    options->max_print_line = (int)lua_tointeger(L, -1);
                    if (options->max_print_line<60) options->max_print_line = 60;
                    break;
                case P_MAIN_MEMORY:
                    options->main_memory = (int)lua_tointeger(L, -1);
                    break;
                case P_HASH_SIZE:
                    options->hash_size = (unsigned)lua_tointeger(L, -1);
                    break;
                case P_PARAM_SIZE:
                    options->param_size = (int)lua_tointeger(L, -1);
                    break;
                case P_IN_OPEN:
                    options->max_in_open = (int)lua_tointeger(L, -1);
                    break;
                case P_RANDOM_SEED:
                  options->random_seed = (int)lua_tointeger(L, -1);
                    break;
                case P_INTERACTION:
                    options->interaction =
                        luaL_checkoption(L, -1, "errorstopmode",
                                         interaction_options);
                    break;
                case P_INI_VERSION:
                    options->ini_version = lua_toboolean(L, -1);
                    break;
                case P_MEM_NAME:
                    options->mem_name = strdup((char *) lua_tostring(L, -1));
                    break;
                case P_JOB_NAME:
                    options->job_name = strdup((char *) lua_tostring(L, -1));
                    break;
                case P_FIND_FILE:
                    if (mplib_find_file_function(L)) {  /* error here */
                        fprintf(stdout,
                                "Invalid arguments to mp.new({find_file=...})\n");
                    }
                    break;
                default:
                    break;
                }
                lua_pop(L, 1);
            }
        }
        *mp_ptr = mp_initialize(options);
        xfree(options->command_line);
        xfree(options->mem_name);
        free(options);
        if (*mp_ptr) {
            luaL_getmetatable(L, MPLIB_METATABLE);
            lua_setmetatable(L, -2);
            return 1;
        }
    }
    lua_pushnil(L);
    return 1;
}

static int mplib_collect(lua_State * L)
{
    MP *mp_ptr = is_mp(L, 1);
    if (*mp_ptr != NULL) {
      (void)mp_finish(*mp_ptr);
      *mp_ptr = NULL;
    }
    return 0;
}

static int mplib_tostring(lua_State * L)
{
    MP *mp_ptr = is_mp(L, 1);
    if (*mp_ptr != NULL) {
      (void)lua_pushfstring(L, "<MP %p>", *mp_ptr);
        return 1;
    }
    return 0;
}

static int mplib_wrapresults(lua_State * L, mp_run_data *res, int status)
{
    lua_checkstack(L, 5);
    lua_newtable(L);
    if (res->term_out.size != 0) {
        lua_pushstring(L, res->term_out.data);
        lua_setfield(L, -2, "term");
    }
    if (res->error_out.size != 0) {
        lua_pushstring(L, res->error_out.data);
        lua_setfield(L, -2, "error");
    }
    if (res->log_out.size != 0) {
        lua_pushstring(L, res->log_out.data);
        lua_setfield(L, -2, "log");
    }
    if (res->edges != NULL) {
        struct mp_edge_object **v;
        struct mp_edge_object *p = res->edges;
        int i = 1;
        lua_newtable(L);
        while (p != NULL) {
            v = lua_newuserdata(L, sizeof(struct mp_edge_object *));
            *v = p;
            luaL_getmetatable(L, MPLIB_FIG_METATABLE);
            lua_setmetatable(L, -2);
            lua_rawseti(L, -2, i);
            i++;
            p = p->next;
        }
        lua_setfield(L, -2, "fig");
        res->edges = NULL;
    }
    lua_pushnumber(L, (lua_Number)status);
    lua_setfield(L, -2, "status");
    return 1;
}

static int mplib_execute(lua_State * L)
{
    MP *mp_ptr = is_mp(L, 1);
    if (*mp_ptr != NULL && lua_isstring(L, 2)) {
        size_t l;
        char *s = (char *) lua_tolstring(L, 2, &l);
        int h = mp_execute(*mp_ptr, s, l);
        mp_run_data *res = mp_rundata(*mp_ptr);
        return mplib_wrapresults(L, res, h);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int mplib_finish(lua_State * L)
{
    MP *mp_ptr = is_mp(L, 1);
    if (*mp_ptr != NULL) {
      int i;
      int h = mp_execute(*mp_ptr,NULL,0);
      mp_run_data *res = mp_rundata(*mp_ptr);
      i = mplib_wrapresults(L, res, h);
      (void)mp_finish(*mp_ptr);
       *mp_ptr = NULL;
       return i;
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int mplib_char_dimension(lua_State * L, int t)
{
  MP *mp_ptr = is_mp(L, 1);
  if (*mp_ptr != NULL) {
    char *fname = (char *)luaL_checkstring(L,2);
    int charnum = (int)luaL_checkinteger(L,3);
    if (charnum<0 || charnum>255) {
      lua_pushnumber(L, (lua_Number)0);
    } else {
      lua_pushnumber(L,(lua_Number)mp_get_char_dimension(*mp_ptr,fname,charnum,t));
    }
  } else {
    lua_pushnumber(L, (lua_Number)0);
  }
  return 1;
}

static int mplib_charwidth(lua_State * L) 
{
  return mplib_char_dimension(L, 'w');
}

static int mplib_chardepth(lua_State * L) 
{
  return mplib_char_dimension(L, 'd');
}

static int mplib_charheight(lua_State * L) 
{
  return mplib_char_dimension(L, 'h');
}

static int mplib_version(lua_State * L)
{
  char *s = mp_metapost_version();
  lua_pushstring(L, s);
  free(s);
  return 1;
}

static int mplib_statistics(lua_State * L)
{
    MP *mp_ptr = is_mp(L, 1);
    if (*mp_ptr != NULL) {
        lua_newtable(L);
        lua_pushnumber(L, (lua_Number)mp_memory_usage(*mp_ptr));
        lua_setfield(L, -2, "main_memory");
        lua_pushnumber(L, (lua_Number)mp_hash_usage(*mp_ptr));
        lua_setfield(L, -2, "hash_size");
        lua_pushnumber(L, (lua_Number)mp_param_usage(*mp_ptr));
        lua_setfield(L, -2, "param_size");
        lua_pushnumber(L, (lua_Number)mp_open_usage(*mp_ptr));
        lua_setfield(L, -2, "max_in_open");
    } else {
        lua_pushnil(L);
    }
    return 1;
}


/* figure methods */

static int mplib_fig_collect(lua_State * L)
{
    struct mp_edge_object **hh = is_fig(L, 1);
    if (*hh != NULL) {
        mp_gr_toss_objects(*hh);
        *hh = NULL;
    }
    return 0;
}

static int mplib_fig_body(lua_State * L)
{
    int i = 1;
    struct mp_graphic_object **v;
    struct mp_graphic_object *p;
    struct mp_edge_object **hh = is_fig(L, 1);
    lua_newtable(L);
    p = (*hh)->body;
    while (p != NULL) {
        v = lua_newuserdata(L, sizeof(struct mp_graphic_object *));
        *v = p;
        luaL_getmetatable(L, MPLIB_GR_METATABLE);
        lua_setmetatable(L, -2);
        lua_rawseti(L, -2, i);
        i++;
        p = p->next;
    }
    (*hh)->body = NULL;         /* prevent double free */
    return 1;
}

static int mplib_fig_copy_body(lua_State * L)
{
    int i = 1;
    struct mp_graphic_object **v;
    struct mp_graphic_object *p;
    struct mp_edge_object **hh = is_fig(L, 1);
    lua_newtable(L);
    p = (*hh)->body;
    while (p != NULL) {
        v = lua_newuserdata(L, sizeof(struct mp_graphic_object *));
        *v = mp_gr_copy_object((*hh)->parent, p);
        luaL_getmetatable(L, MPLIB_GR_METATABLE);
        lua_setmetatable(L, -2);
        lua_rawseti(L, -2, i);
        i++;
        p = p->next;
    }
    return 1;
}


static int mplib_fig_tostring(lua_State * L)
{
    struct mp_edge_object **hh = is_fig(L, 1);
    (void)lua_pushfstring(L, "<figure %p>", *hh);
    return 1;
}

static int mplib_fig_postscript(lua_State * L)
{
    mp_run_data *res;
    struct mp_edge_object **hh = is_fig(L, 1);
    int prologues = (int)luaL_optnumber(L, 2, (lua_Number)-1);
    int procset = (int)luaL_optnumber(L, 3, (lua_Number)-1);
    if (mp_ps_ship_out(*hh, prologues, procset) 
        && (res = mp_rundata((*hh)->parent))
        && (res->ps_out.size != 0)) {
        lua_pushstring(L, res->ps_out.data);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int mplib_fig_svg(lua_State * L)
{
    mp_run_data *res;
    struct mp_edge_object **hh = is_fig(L, 1);
    int prologues = (int)luaL_optnumber(L, 2, (lua_Number)-1);
    if (mp_svg_ship_out(*hh, prologues) 
        && (res = mp_rundata((*hh)->parent))
        && (res->ps_out.size != 0)) {
        lua_pushstring(L, res->ps_out.data);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int mplib_fig_filename(lua_State * L)
{
    struct mp_edge_object **hh = is_fig(L, 1);
    if (*hh != NULL) {
        char *s = (*hh)->filename;
        lua_pushstring(L, s);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int mplib_fig_width(lua_State * L)
{
    struct mp_edge_object **hh = is_fig(L, 1);
    if (*hh != NULL) {
      lua_pushnumber(L, (double) (*hh)->width / 65536.0);
    } else {
      lua_pushnil(L);
    }
    return 1;
}

static int mplib_fig_height(lua_State * L)
{
    struct mp_edge_object **hh = is_fig(L, 1);
    if (*hh != NULL) {
      lua_pushnumber(L, (double) (*hh)->height / 65536.0);
    } else {
      lua_pushnil(L);
    }
    return 1;
}

static int mplib_fig_depth(lua_State * L)
{
    struct mp_edge_object **hh = is_fig(L, 1);
    if (*hh != NULL) {
      lua_pushnumber(L, (double) (*hh)->depth / 65536.0);
    } else {
      lua_pushnil(L);
    }
    return 1;
}

static int mplib_fig_italcorr(lua_State * L)
{
    struct mp_edge_object **hh = is_fig(L, 1);
    if (*hh != NULL) {
      lua_pushnumber(L, (double) (*hh)->ital_corr / 65536.0);
    } else {
      lua_pushnil(L);
    }
    return 1;
}

static int mplib_fig_charcode(lua_State * L)
{
    struct mp_edge_object **hh = is_fig(L, 1);
    if (*hh != NULL) {
      lua_pushnumber(L, (lua_Number)(*hh)->charcode);
    } else {
      lua_pushnil(L);
    }
    return 1;
}



static int mplib_fig_bb(lua_State * L)
{
    struct mp_edge_object **hh = is_fig(L, 1);
    lua_newtable(L);
    lua_pushnumber(L, (double) (*hh)->minx / 65536.0);
    lua_rawseti(L, -2, 1);
    lua_pushnumber(L, (double) (*hh)->miny / 65536.0);
    lua_rawseti(L, -2, 2);
    lua_pushnumber(L, (double) (*hh)->maxx / 65536.0);
    lua_rawseti(L, -2, 3);
    lua_pushnumber(L, (double) (*hh)->maxy / 65536.0);
    lua_rawseti(L, -2, 4);
    return 1;
}

/* object methods */

static int mplib_gr_collect(lua_State * L)
{
    struct mp_graphic_object **hh = is_gr_object(L, 1);
    if (*hh != NULL) {
        mp_gr_toss_object(*hh);
        *hh = NULL;
    }
    return 0;
}

static int mplib_gr_tostring(lua_State * L)
{
    struct mp_graphic_object **hh = is_gr_object(L, 1);
    (void)lua_pushfstring(L, "<object %p>", *hh);
    return 1;
}

#define pyth(a,b) (sqrt((a)*(a) + (b)*(b)))

#define aspect_bound   (10.0/65536.0)
#define aspect_default (1.0/65536.0)

static double eps  = 0.0001;

static double coord_range_x (mp_knot *h, double dz) {
  double z;
  double zlo = 0.0, zhi = 0.0;
  mp_knot *f = h; 
  while (h != NULL) {
    z = (double)h->x_coord;
    if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
    z = (double)h->right_x;
    if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
    z = (double)h->left_x;
    if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
    h = h->next;
    if (h==f)
      break;
  }
  return (zhi - zlo <= dz ? aspect_bound : aspect_default);
}

static double coord_range_y (mp_knot *h, double dz) {
  double z;
  double zlo = 0.0, zhi = 0.0;
  mp_knot *f = h; 
  while (h != NULL) {
    z = (double)h->y_coord;
    if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
    z = (double)h->right_y;
    if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
    z = (double)h->left_y;
    if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
    h = h->next;
    if (h==f)
      break;
  }
  return (zhi - zlo <= dz ? aspect_bound : aspect_default);
}


static int mplib_gr_peninfo(lua_State * L) {
    double x_coord, y_coord, left_x, left_y, right_x, right_y;
    double wx, wy;
    double rx = 1.0, sx = 0.0, sy = 0.0, ry = 1.0, tx = 0.0, ty = 0.0;
    double divider = 1.0;
    double width = 1.0;
    mp_knot *p = NULL, *path = NULL;
    struct mp_graphic_object **hh = is_gr_object(L, -1);
    if (!*hh) {
      lua_pushnil(L);
      return 1;
    }
    if ((*hh)->type == mp_fill_code) {
      p    = ((mp_fill_object *)(*hh))->pen_p;
      path = ((mp_fill_object *)(*hh))->path_p;
    } else if ((*hh)->type == mp_stroked_code) {
      p    = ((mp_stroked_object *)(*hh))->pen_p;
      path = ((mp_stroked_object *)(*hh))->path_p;
    }
    if (p==NULL || path == NULL) {
      lua_pushnil(L);
      return 1;
    }
    x_coord = p->x_coord/65536.0;
    y_coord = p->y_coord/65536.0;
    left_x = p->left_x/65536.0;
    left_y = p->left_y/65536.0;
    right_x = p->right_x/65536.0;
    right_y = p->right_y/65536.0;
    if ((right_x == x_coord) && (left_y == y_coord)) {
      wx = fabs(left_x  - x_coord);
      wy = fabs(right_y - y_coord);
    } else {
      wx = pyth(left_x - x_coord, right_x - x_coord);
      wy = pyth(left_y - y_coord, right_y - y_coord);
    }
    if ((wy/coord_range_x(path, wx)) >= (wx/coord_range_y(path, wy)))
      width = wy;
    else
      width = wx;
    tx = x_coord; 
    ty = y_coord;
    sx = left_x - tx; 
    rx = left_y - ty; 
    ry = right_x - tx; 
    sy = right_y - ty;
    if (width !=1.0) {
      if (width == 0.0) {
        sx = 1.0; sy = 1.0;
      } else {
        rx/=width; ry/=width; sx/=width; sy/=width;
      }
    }
    if (fabs(sx) < eps) sx = eps;
    if (fabs(sy) < eps) sy = eps;
    divider = sx*sy - rx*ry;
    lua_newtable(L);
    lua_pushnumber(L,width); lua_setfield(L,-2,"width");
    lua_pushnumber(L,rx); lua_setfield(L,-2,"rx");
    lua_pushnumber(L,sx); lua_setfield(L,-2,"sx");
    lua_pushnumber(L,sy); lua_setfield(L,-2,"sy");
    lua_pushnumber(L,ry); lua_setfield(L,-2,"ry");
    lua_pushnumber(L,tx); lua_setfield(L,-2,"tx");
    lua_pushnumber(L,ty); lua_setfield(L,-2,"ty");
    return 1;
}


static int mplib_gr_fields(lua_State * L)
{
    const char **fields;
    int i;
    struct mp_graphic_object **hh = is_gr_object(L, 1);
    if (*hh) {
        switch ((*hh)->type) {
        case mp_fill_code:
            fields = fill_fields;
            break;
        case mp_stroked_code:
            fields = stroked_fields;
            break;
        case mp_text_code:
            fields = text_fields;
            break;
        case mp_special_code:
            fields = special_fields;
            break;
        case mp_start_clip_code:
            fields = start_clip_fields;
            break;
        case mp_start_bounds_code:
            fields = start_bounds_fields;
            break;
        case mp_stop_clip_code:
            fields = stop_clip_fields;
            break;
        case mp_stop_bounds_code:
            fields = stop_bounds_fields;
            break;
        default:
            fields = no_fields;
        }
        lua_newtable(L);
        for (i = 0; fields[i] != NULL; i++) {
            lua_pushstring(L, fields[i]);
            lua_rawseti(L, -2, (i + 1));
        }
    } else {
        lua_pushnil(L);
    }
    return 1;
}


#define mplib_push_number(L,x) lua_pushnumber(L,(lua_Number)(x)/65536.0)

#define MPLIB_PATH 0
#define MPLIB_PEN 1

static void mplib_push_path(lua_State * L, struct mp_knot *h, int is_pen)
{
    struct mp_knot *p;          /* for scanning the path */
    int i = 1;
    p = h;
    if (p != NULL) {
        lua_newtable(L);
        do {
            lua_createtable(L, 0, 6);
            if (!is_pen) {
                if (p->left_type != mp_explicit) {
                    mplib_push_S(left_type);
                    lua_pushstring(L, knot_type_enum[p->left_type]);
                    lua_rawset(L, -3);
                }
                if (p->right_type != mp_explicit) {
                    mplib_push_S(right_type);
                    lua_pushstring(L, knot_type_enum[p->right_type]);
                    lua_rawset(L, -3);
                }
            }
            mplib_push_S(x_coord);
            mplib_push_number(L, p->x_coord);
            lua_rawset(L, -3);
            mplib_push_S(y_coord);
            mplib_push_number(L, p->y_coord);
            lua_rawset(L, -3);
            mplib_push_S(left_x);
            mplib_push_number(L, p->left_x);
            lua_rawset(L, -3);
            mplib_push_S(left_y);
            mplib_push_number(L, p->left_y);
            lua_rawset(L, -3);
            mplib_push_S(right_x);
            mplib_push_number(L, p->right_x);
            lua_rawset(L, -3);
            mplib_push_S(right_y);
            mplib_push_number(L, p->right_y);
            lua_rawset(L, -3);
            lua_rawseti(L, -2, i);
            i++;
            if (p->right_type == mp_endpoint) {
                return;
            }
            p = p->next;
        } while (p != h);
    } else {
        lua_pushnil(L);
    }
}

/* this assumes that the top of the stack is a table 
   or nil already in the case
 */
static void mplib_push_pentype(lua_State * L, struct mp_knot *h)
{
    struct mp_knot *p;          /* for scanning the path */
    p = h;
    if (p == NULL) {
        /* do nothing */
    } else if (p == p->next) {
        mplib_push_S(type);
        lua_pushstring(L, "elliptical");
        lua_rawset(L, -3);
    } else {
    }
}

#define set_color_objects(pq)                           \
  object_color_model = pq->color_model;           \
  object_color_a = pq->color.a_val;              \
  object_color_b = pq->color.b_val;              \
  object_color_c = pq->color.c_val;              \
  object_color_d = pq->color.d_val;


static void mplib_push_color(lua_State * L, struct mp_graphic_object *p)
{
    int object_color_model;
    int object_color_a, object_color_b, object_color_c, object_color_d;
    if (p != NULL) {
        if (p->type == mp_fill_code) {
            mp_fill_object *h = (mp_fill_object *) p;
            set_color_objects(h);
        } else if (p->type == mp_stroked_code) {
            mp_stroked_object *h = (mp_stroked_object *) p;
            set_color_objects(h);
        } else {
            mp_text_object *h = (mp_text_object *) p;
            set_color_objects(h);
        }
        lua_newtable(L);
        if (object_color_model >= mp_grey_model) {
            mplib_push_number(L, object_color_a);
            lua_rawseti(L, -2, 1);
            if (object_color_model >= mp_rgb_model) {
                mplib_push_number(L, object_color_b);
                lua_rawseti(L, -2, 2);
                mplib_push_number(L, object_color_c);
                lua_rawseti(L, -2, 3);
                if (object_color_model == mp_cmyk_model) {
                    mplib_push_number(L, object_color_d);
                    lua_rawseti(L, -2, 4);
                }
            }
        }
    } else {
        lua_pushnil(L);
    }
}

/* the dash scale is not exported, the field has no external value */
static void mplib_push_dash(lua_State * L, struct mp_stroked_object *h)
{
    mp_dash_object *d;
    double ds;
    if (h != NULL && h->dash_p != NULL) {
        d = h->dash_p;
        lua_newtable(L);
        mplib_push_number(L, d->offset);
        lua_setfield(L, -2, "offset");
        if (d->array != NULL) {
            int i = 0;
            lua_newtable(L);
            while (*(d->array + i) != -1) {
                ds = *(d->array + i) / 65536.0;
                lua_pushnumber(L, ds);
                i++;
                lua_rawseti(L, -2, i);
            }
            lua_setfield(L, -2, "dashes");
        }
    } else {
        lua_pushnil(L);
    }
}

static void mplib_push_transform(lua_State * L, struct mp_text_object *h)
{
    int i = 1;
    if (h != NULL) {
        lua_createtable(L, 6, 0);
        mplib_push_number(L, h->tx);
        lua_rawseti(L, -2, i);
        i++;
        mplib_push_number(L, h->ty);
        lua_rawseti(L, -2, i);
        i++;
        mplib_push_number(L, h->txx);
        lua_rawseti(L, -2, i);
        i++;
        mplib_push_number(L, h->tyx);
        lua_rawseti(L, -2, i);
        i++;
        mplib_push_number(L, h->txy);
        lua_rawseti(L, -2, i);
        i++;
        mplib_push_number(L, h->tyy);
        lua_rawseti(L, -2, i);
        i++;
    } else {
        lua_pushnil(L);
    }
}

#define FIELD(A) (mplib_is_S(A,2))

static void mplib_fill(lua_State * L, struct mp_fill_object *h)
{
    if (FIELD(path)) {
        mplib_push_path(L, h->path_p, MPLIB_PATH);
    } else if (FIELD(htap)) {
        mplib_push_path(L, h->htap_p, MPLIB_PATH);
    } else if (FIELD(pen)) {
        mplib_push_path(L, h->pen_p, MPLIB_PEN);
        mplib_push_pentype(L, h->pen_p);
    } else if (FIELD(color)) {
        mplib_push_color(L, (mp_graphic_object *) h);
    } else if (FIELD(linejoin)) {
      lua_pushnumber(L, (lua_Number)h->ljoin);
    } else if (FIELD(miterlimit)) {
        mplib_push_number(L, h->miterlim);
    } else if (FIELD(prescript)) {
        lua_pushstring(L, h->pre_script);
    } else if (FIELD(postscript)) {
        lua_pushstring(L, h->post_script);
    } else {
        lua_pushnil(L);
    }
}

static void mplib_stroked(lua_State * L, struct mp_stroked_object *h)
{
    if (FIELD(path)) {
        mplib_push_path(L, h->path_p, MPLIB_PATH);
    } else if (FIELD(pen)) {
        mplib_push_path(L, h->pen_p, MPLIB_PEN);
        mplib_push_pentype(L, h->pen_p);
    } else if (FIELD(color)) {
        mplib_push_color(L, (mp_graphic_object *) h);
    } else if (FIELD(dash)) {
        mplib_push_dash(L, h);
    } else if (FIELD(linecap)) {
        lua_pushnumber(L, (lua_Number)h->lcap);
    } else if (FIELD(linejoin)) {
      lua_pushnumber(L, (lua_Number)h->ljoin);
    } else if (FIELD(miterlimit)) {
        mplib_push_number(L, h->miterlim);
    } else if (FIELD(prescript)) {
        lua_pushstring(L, h->pre_script);
    } else if (FIELD(postscript)) {
        lua_pushstring(L, h->post_script);
    } else {
        lua_pushnil(L);
    }
}

static void mplib_text(lua_State * L, struct mp_text_object *h)
{
    if (FIELD(text)) {
        lua_pushstring(L, h->text_p);
    } else if (FIELD(dsize)) {
        mplib_push_number(L, (h->font_dsize / 16));
    } else if (FIELD(font)) {
        lua_pushstring(L, h->font_name);
    } else if (FIELD(color)) {
        mplib_push_color(L, (mp_graphic_object *) h);
    } else if (FIELD(width)) {
        mplib_push_number(L, h->width);
    } else if (FIELD(height)) {
        mplib_push_number(L, h->height);
    } else if (FIELD(depth)) {
        mplib_push_number(L, h->depth);
    } else if (FIELD(transform)) {
        mplib_push_transform(L, h);
    } else if (FIELD(prescript)) {
        lua_pushstring(L, h->pre_script);
    } else if (FIELD(postscript)) {
        lua_pushstring(L, h->post_script);
    } else {
        lua_pushnil(L);
    }
}

static void mplib_special(lua_State * L, struct mp_special_object *h)
{
    if (FIELD(prescript)) {
        lua_pushstring(L, h->pre_script);
    } else {
        lua_pushnil(L);
    }
}

static void mplib_start_bounds(lua_State * L, struct mp_bounds_object *h)
{
    if (FIELD(path)) {
        mplib_push_path(L, h->path_p, MPLIB_PATH);
    } else {
        lua_pushnil(L);
    }
}

static void mplib_start_clip(lua_State * L, struct mp_clip_object *h)
{
    if (FIELD(path)) {
        mplib_push_path(L, h->path_p, MPLIB_PATH);
    } else {
        lua_pushnil(L);
    }
}

static int mplib_gr_index(lua_State * L)
{
    struct mp_graphic_object **hh = is_gr_object(L, 1);
    if (*hh) {
        struct mp_graphic_object *h = *hh;

        if (mplib_is_S(type, 2)) {
            lua_rawgeti(L, LUA_REGISTRYINDEX, mplib_type_Ses[h->type]);
        } else {
            switch (h->type) {
            case mp_fill_code:
                mplib_fill(L, (mp_fill_object *) h);
                break;
            case mp_stroked_code:
                mplib_stroked(L, (mp_stroked_object *) h);
                break;
            case mp_text_code:
                mplib_text(L, (mp_text_object *) h);
                break;
            case mp_special_code:
                mplib_special(L, (mp_special_object *) h);
                break;
            case mp_start_clip_code:
                mplib_start_clip(L, (mp_clip_object *) h);
                break;
            case mp_start_bounds_code:
                mplib_start_bounds(L, (mp_bounds_object *) h);
                break;
            case mp_stop_clip_code:
            case mp_stop_bounds_code:
            default:
                lua_pushnil(L);
            }
        }
    } else {
        lua_pushnil(L);
    }
    return 1;
}


static const struct luaL_reg mplib_meta[] = {
    {"__gc", mplib_collect},
    {"__tostring", mplib_tostring},
    {NULL, NULL}                /* sentinel */
};

static const struct luaL_reg mplib_fig_meta[] = {
    {"__gc",         mplib_fig_collect},
    {"__tostring",   mplib_fig_tostring},
    {"objects",      mplib_fig_body},
    {"copy_objects", mplib_fig_copy_body},
    {"filename",     mplib_fig_filename},
    {"postscript",   mplib_fig_postscript},
    {"svg",          mplib_fig_svg},
    {"boundingbox",  mplib_fig_bb},
    {"width",        mplib_fig_width},
    {"height",       mplib_fig_height},
    {"depth",        mplib_fig_depth},
    {"italcorr",     mplib_fig_italcorr},
    {"charcode",     mplib_fig_charcode},
    {NULL, NULL}                /* sentinel */
};

static const struct luaL_reg mplib_gr_meta[] = {
    {"__gc", mplib_gr_collect},
    {"__tostring", mplib_gr_tostring},
    {"__index", mplib_gr_index},
    {NULL, NULL}                /* sentinel */
};

static const struct luaL_reg mplib_d[] = {
    {"execute", mplib_execute},
    {"finish", mplib_finish},
    {"char_width", mplib_charwidth},
    {"char_height", mplib_charheight},
    {"char_depth", mplib_chardepth},
    {"statistics", mplib_statistics},
    {NULL, NULL}                /* sentinel */
};


static const struct luaL_reg mplib_m[] = {
    {"new", mplib_new},
    {"version",    mplib_version},
    {"fields", mplib_gr_fields},
    {"pen_info", mplib_gr_peninfo},
    {NULL, NULL}                /* sentinel */
};


int luaopen_mplib(lua_State * L)
{
    mplib_init_Ses(L);

    luaL_newmetatable(L, MPLIB_GR_METATABLE);
    lua_pushvalue(L, -1);       /* push metatable */
    lua_setfield(L, -2, "__index");     /* metatable.__index = metatable */
    luaL_register(L, NULL, mplib_gr_meta);      /* object meta methods */
    lua_pop(L, 1);

    luaL_newmetatable(L, MPLIB_FIG_METATABLE);
    lua_pushvalue(L, -1);       /* push metatable */
    lua_setfield(L, -2, "__index");     /* metatable.__index = metatable */
    luaL_register(L, NULL, mplib_fig_meta);     /* figure meta methods */
    lua_pop(L, 1);

    luaL_newmetatable(L, MPLIB_METATABLE);
    lua_pushvalue(L, -1);       /* push metatable */
    lua_setfield(L, -2, "__index");     /* metatable.__index = metatable */
    luaL_register(L, NULL, mplib_meta); /* meta methods */
    luaL_register(L, NULL, mplib_d);    /* dict methods */
    luaL_register(L, "mplib", mplib_m); /* module functions */
    return 1;
}
