/* lmplib.c

   Copyright 2012 Taco Hoekwater <taco@luatex.org>

   This file is part of the MetaPost tarball, but belongs to 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 <w2c/config.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <math.h>

#ifndef pdfTeX
#  include <lua.h>
#  include <lauxlib.h>
#  include <lualib.h>
#else
#  include "lua.h"
#  include "lauxlib.h"
#  include "lualib.h"
#define luaL_reg luaL_Reg
#define lua_objlen lua_rawlen
#endif

#ifndef luaL_reg
#define luaL_reg luaL_Reg
#endif

#ifndef lua_objlen
#define lua_objlen lua_rawlen
#endif

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

int luaopen_mplib(lua_State * L);

/*tex

    We need a few metatable identifiers in order to access the metatables for the
    main object and result userdata.

*/

#define MPLIB_METATABLE     "MPlib.meta"
#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)

/*tex

    We pre-hash the \LUA\ strings which is much faster. The approach is similar to the one
    used at the \TEX\ end.

*/

#define mplib_init_S(a) do {                            \
    lua_pushliteral(L,#a);                              \
    mplib_##a##_ptr = 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==lua_tostring(L,i))

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

/*tex In the next array entry 0 is not used */

static int mplib_type_Ses[mp_special_code + 1] = { 0 };

mplib_make_S(term);
mplib_make_S(error);
mplib_make_S(log);
mplib_make_S(fig);
mplib_make_S(status);

mplib_make_S(memory);
mplib_make_S(hash);
mplib_make_S(params);
mplib_make_S(open);
mplib_make_S(cycle);

mplib_make_S(offset);
mplib_make_S(dashes);

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(left_tension);
mplib_make_S(right_tension);
mplib_make_S(left_curl);
mplib_make_S(right_curl);
mplib_make_S(direction_x);
mplib_make_S(direction_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);
mplib_make_S(elliptical);

static void mplib_init_Ses(lua_State * L)
{
    mplib_init_S(term);
    mplib_init_S(error);
    mplib_init_S(log);
    mplib_init_S(fig);
    mplib_init_S(status);

    mplib_init_S(memory);
    mplib_init_S(hash);
    mplib_init_S(params);
    mplib_init_S(open);
    mplib_init_S(cycle);

    mplib_init_S(offset);
    mplib_init_S(dashes);

    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(left_tension);
    mplib_init_S(right_tension);
    mplib_init_S(left_curl);
    mplib_init_S(right_curl);
    mplib_init_S(direction_x);
    mplib_init_S(direction_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);
    mplib_init_S(elliptical);
}

/*tex

    Here are some enumeration arrays to map MPlib enums to \LUA\ strings. If needed
    we can also predefine keys here, as we do with nodes.

*/

static const char *math_options[] =
    { "scaled", "double", "binary", "decimal", NULL };

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

static const char *mplib_filetype_names[] =
    { "term", "error", "mp", "log", "ps", "png", "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 };

/*tex

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

*/

typedef enum {
    P_ERROR_LINE,
    P_MAX_LINE,
    P_RANDOM_SEED,
    P_MATH_MODE,
    P_INTERACTION,
    P_INI_VERSION,
    P_MEM_NAME,
    P_JOB_NAME,
    P_FIND_FILE,
    P_RUN_SCRIPT,
    P_MAKE_TEXT,
    P_SCRIPT_ERROR,
    P_EXTENSIONS,
    P_UTF8_MODE,
    P__SENTINEL
} mplib_parm_idx;

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

static mplib_parm_struct mplib_parms[] = {
    {"error_line",   P_ERROR_LINE   },
    {"print_line",   P_MAX_LINE     },
    {"random_seed",  P_RANDOM_SEED  },
    {"interaction",  P_INTERACTION  },
    {"job_name",     P_JOB_NAME     },
    {"find_file",    P_FIND_FILE    },
    {"run_script",   P_RUN_SCRIPT   },
    {"make_text",    P_MAKE_TEXT    },
    {"script_error", P_SCRIPT_ERROR },
    {"extensions",   P_EXTENSIONS   },
    {"math_mode",    P_MATH_MODE    },
    {"utf8_mode",    P_UTF8_MODE    },
    {NULL,           P__SENTINEL    }
};

/*tex

    We 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;
        const char *x = NULL;
        lua_pushstring(L, fname);
        lua_pushstring(L, fmode);
        if (ftype >= mp_filetype_text) {
            lua_pushinteger(L, (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", lua_tostring(L, -1));
            return NULL;
        }
        x = lua_tostring(L, -1);
        if (x != NULL)
            s = strdup(x);
        /*tex pop the string */
        lua_pop(L, 1);
        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))) {
        /*tex An error. */
        return 1;
    }
    lua_pushstring(L, "mplib.file_finder");
    lua_pushvalue(L, -2);
    lua_rawset(L, LUA_REGISTRYINDEX);
    return 0;
}

static void mplib_warning(const char *str)
{
    fprintf(stdout,"mplib warning: %s\n",str);
}

static void mplib_script_error(MP mp, const char *str)
{
    lua_State *L = (lua_State *)mp_userdata(mp);
    lua_checkstack(L, 1);
    lua_getfield(L, LUA_REGISTRYINDEX, "mplib.script_error");
    if (lua_isfunction(L, -1)) {
        lua_pushstring(L, str);
        /*tex We assume that the function is okay. */
        lua_pcall(L, 1, 0, 0);
    } else {
        mplib_warning(str);
        lua_pop(L, 1);
    }
}

static int mplib_script_error_function(lua_State * L)
{
    if (!(lua_isfunction(L, -1) || lua_isnil(L, -1))) {
        /*tex An error. */
        return 1;
    }
    lua_pushstring(L, "mplib.script_error");
    lua_pushvalue(L, -2);
    lua_rawset(L, LUA_REGISTRYINDEX);
    return 0;
}

static char *mplib_run_script(MP mp, const char *str, size_t len)
{
    lua_State *L = (lua_State *)mp_userdata(mp);
    lua_checkstack(L, 1);
    lua_getfield(L, LUA_REGISTRYINDEX, "mplib.run_script");
    if (lua_isfunction(L, -1)) {
        char *s = NULL;
        const char *x = NULL;
        lua_pushlstring(L, str, len);
        if (lua_pcall(L, 1, 1, 0) != 0) {
            fprintf(stdout,"mplib warning: error in script: %s\n",lua_tostring(L, -1));
            return NULL;
        }
        x = lua_tostring(L, -1);
        if (x != NULL)
            s = strdup(x);
        /*tex Pop the string. */
        lua_pop(L, 1);
        return s;
    } else {
        lua_pop(L, 1);
    }
    return NULL;
}

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

static char *mplib_make_text(MP mp, const char *str, size_t len, int mode)
{
    lua_State *L = (lua_State *)mp_userdata(mp);
    lua_checkstack(L, 1);
    lua_getfield(L, LUA_REGISTRYINDEX, "mplib.make_text");
    if (lua_isfunction(L, -1)) {
        char *s = NULL;
        const char *x = NULL;
        lua_pushlstring(L, str, len);
        lua_pushinteger(L, mode);
        if (lua_pcall(L, 2, 1, 0) != 0) {
            mplib_script_error(mp, lua_tostring(L, -1));
            return NULL;
        }
        x = lua_tostring(L, -1);
        if (x != NULL)
            s = strdup(x);
        /*tex Pop the string. */
        lua_pop(L, 1);
        return s;
    } else {
        lua_pop(L, 1);
    }
    return NULL;
}

static int mplib_make_text_function(lua_State * L)
{
    if (!(lua_isfunction(L, -1) || lua_isnil(L, -1))) {
        /*tex An error. */
        return 1;
    }
    lua_pushstring(L, "mplib.make_text");
    lua_pushvalue(L, -2);
    lua_rawset(L, LUA_REGISTRYINDEX);
    return 0;
}

static int mplib_get_numeric(lua_State * L)
{
    MP *mp = is_mp(L, 1);
    if (*mp != NULL) {
        size_t l;
        const char *s = lua_tolstring(L, 2, &l);
        if (s != NULL) {
            lua_pushnumber(L, mp_get_numeric_value(*mp,s,l));
            return 1;
        }
    }
    lua_pushnumber(L,0);
    return 1;
}

static int mplib_get_boolean(lua_State * L)
{
    MP *mp = is_mp(L, 1);
    if (*mp != NULL) {
        size_t l;
        const char *s = lua_tolstring(L, 2, &l);
        if (s != NULL) {
            lua_pushboolean(L, mp_get_boolean_value(*mp,s,l));
            return 1;
        }
    }
    lua_pushboolean(L,0);
    return 1;
}

static int mplib_get_string(lua_State * L)
{
    MP *mp = is_mp(L, 1);
    if (*mp != NULL) {
        size_t l;
        const char *s = lua_tolstring(L, 2, &l);
        if (s != NULL) {
            char *r = mp_get_string_value(*mp,s,l) ;
            if (r != NULL) {
                lua_pushstring(L, r);
                return 1;
            }
        }
    }
    lua_pushstring(L,"");
    return 1;
}

#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;
        /*tex Required: */
        options->noninteractive = 1;
        options->extensions = 0 ;
        options->find_file = mplib_find_file;
        options->run_script = mplib_run_script;
        options->make_text = mplib_make_text;
    /*  options->script_error = mplib_script_error; */
        options->print_found_names = 1;
        options->ini_version = 1;
        options->utf8_mode = 0;
        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;
                }
                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_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_MATH_MODE:
                        options->math_mode = luaL_checkoption(L, -1, "scaled", math_options);
                        break;
                    case P_JOB_NAME:
                        options->job_name = strdup(lua_tostring(L, -1));
                        break;
                    case P_FIND_FILE:
                        if (mplib_find_file_function(L)) {
                            mplib_warning("function expected for 'find_file'");
                        }
                        break;
                    case P_RUN_SCRIPT:
                        if (mplib_run_script_function(L)) {
                            mplib_warning("function expected for 'run_script'");
                        }
                        break;
                    case P_MAKE_TEXT:
                        if (mplib_make_text_function(L)) {
                            mplib_warning("function expected for 'make_text'");
                        }
                        break;
                    case P_SCRIPT_ERROR:
                        if (mplib_script_error_function(L)) {
                            mplib_warning("function expected for 'script_error'");
                        }
                        break;
                    case P_EXTENSIONS:
                        options->extensions = (int)lua_tointeger(L, -1);
                        break;
                    case P_UTF8_MODE:
                        options->utf8_mode = (int)lua_toboolean(L, -1);
                        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.used != 0) {
        mplib_push_S(term);
        lua_pushlstring(L, res->term_out.data, res->term_out.used);
        lua_rawset(L,-3);
    }
    if (res->error_out.used != 0) {
        mplib_push_S(error);
        lua_pushlstring(L, res->error_out.data, res->error_out.used);
        lua_rawset(L,-3);
    }
    if (res->log_out.used != 0) {
        mplib_push_S(log);
        lua_pushlstring(L, res->log_out.data, res->log_out.used);
        lua_rawset(L,-3);
    }
    if (res->edges != NULL) {
        struct mp_edge_object **v;
        struct mp_edge_object *p = res->edges;
        int i = 1;
        mplib_push_S(fig);
        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_rawset(L,-3);
        res->edges = NULL;
    }
    mplib_push_S(status);
    lua_pushinteger(L, status);
    lua_rawset(L,-3);
    return 1;
}

static int mplib_execute(lua_State * L)
{
    MP *mp_ptr;
    if (lua_gettop(L)!=2) {
        lua_pushnil(L);
        return 1;
    }
    mp_ptr = is_mp(L, 1);
    if (*mp_ptr != NULL && lua_isstring(L, 2)) {
        size_t l;
        char *s = xstrdup(lua_tolstring(L, 2, &l));
        int h = mp_execute(*mp_ptr, s, l);
        mp_run_data *res = mp_rundata(*mp_ptr);
        free(s);
        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 = xstrdup(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));
        }
        free(fname);
    } 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);
        mplib_push_S(memory);
        lua_pushinteger(L, mp_memory_usage(*mp_ptr));
        lua_rawset(L,-3);
        mplib_push_S(hash);
        lua_pushinteger(L, mp_hash_usage(*mp_ptr));
        lua_rawset(L,-3);
        mplib_push_S(params);
        lua_pushinteger(L, mp_param_usage(*mp_ptr));
        lua_rawset(L,-3);
        mplib_push_S(open);
        lua_pushinteger(L, mp_open_usage(*mp_ptr));
        lua_rawset(L,-3);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int set_direction (lua_State * L, MP mp, mp_knot p) {
    double direction_x = 0, direction_y = 0;
    direction_x = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    mplib_push_S(direction_y);
    lua_rawget(L,-2);
    if (!lua_isnumber(L,-1)) {
        return 0;
    }
    direction_y = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    if (!mp_set_knot_direction(mp, p, direction_x, direction_y)) {
        return 0;
    }
    return 1;
}

static int set_left_curl (lua_State * L, MP mp, mp_knot p) {
    double curl = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    if (!mp_set_knot_left_curl(mp, p, curl)) {
        return 0;
    }
    return 1;
}

static int set_left_tension (lua_State * L, MP mp, mp_knot p) {
    double tension = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    if (!mp_set_knot_left_tension(mp, p, tension)) {
        return 0;
    }
    return 1;
}

static int set_left_control (lua_State * L, MP mp, mp_knot p) {
    double x, y;
    x = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    mplib_push_S(left_y);
    lua_rawget(L,-2);
    if (!lua_isnumber(L,-1)) {
        return 0;
    }
    y = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    if (!mp_set_knot_left_control(mp, p, x, y)) {
        return 0;
    }
    return 1;
}

static int set_right_curl (lua_State * L, MP mp, mp_knot p) {
    double curl = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    if (!mp_set_knot_right_curl(mp, p, curl)) {
        return 0;
    }
    return 1;
}

static int set_right_tension (lua_State * L, MP mp, mp_knot p) {
    double tension = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    if (!mp_set_knot_right_tension(mp, p, tension)) {
        return 0;
    }
    return 1;
}

static int set_right_control (lua_State * L, MP mp, mp_knot p) {
    double x, y;
    x = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    mplib_push_S(right_y);
    lua_rawget(L,-2);
    if (!lua_isnumber(L,-1)) {
        return 0;
    }
    y = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    if (!mp_set_knot_right_control(mp, p, x, y)) {
        return 0;
    }
    return 1;
}

#if 0

#define ROUNDED_ZERO(v) (fabs((v))<0.00001 ? 0 : (v))
#define PI 3.1415926535897932384626433832795028841971
#define RADIANS(a) (mp_number_as_double(mp,(a)) / 16.0) * PI/180.0

void mp_dump_path (MP mp, mp_knot h) {
    mp_knot p, q;
    if (h == NULL)
        return;
    p = h;
    do {
        q=mp_knot_next(mp,p);
        if ( (p==NULL)||(q==NULL) ) {
            printf("\n???");
            return; /* this won't happen */
        }
        printf ("(%g,%g)", mp_number_as_double(mp,mp_knot_x_coord(mp,p)),
                           mp_number_as_double(mp,mp_knot_y_coord(mp,p)));
        switch (mp_knot_right_type(mp,p)) {
            case mp_endpoint:
                if ( mp_knot_left_type(mp,p)==mp_open )
                    printf("{open?}");
                if ( (mp_knot_left_type(mp,q)!=mp_endpoint)||(q!=h) )
                    q=NULL; /* force an error */
                goto DONE;
            break;
        case mp_explicit:
            printf ("..controls (%g,%g)",
                mp_number_as_double(mp,mp_knot_right_x(mp,p)),
                mp_number_as_double(mp,mp_knot_right_y(mp,p)));
            printf(" and ");
            if ( mp_knot_left_type(mp,q)!=mp_explicit ) {
                printf("??");
            } else {
                printf ("(%g,%g)",mp_number_as_double(mp,mp_knot_left_x(mp,q)),
                                  mp_number_as_double(mp,mp_knot_left_y(mp,q)));
            }
            goto DONE;
            break;
        case mp_open:
            if ( (mp_knot_left_type(mp,p)!=mp_explicit) &&
                 (mp_knot_left_type(mp,p)!=mp_open) ) {
                printf("{open?}");
            }
            break;
        case mp_curl:
        case mp_given:
            if ( mp_knot_left_type(mp,p)==mp_open )
                printf("??");
            if ( mp_knot_right_type(mp,p)==mp_curl ) {
                printf("{curl %g}", mp_number_as_double(mp,mp_knot_right_curl(mp,p)));
            } else {
                double rad = RADIANS(mp_knot_right_curl(mp,p));
                double n_cos = ROUNDED_ZERO(cos(rad)*4096);
                double n_sin = ROUNDED_ZERO(sin(rad)*4096);
                printf("{%g,%g}", n_cos, n_sin);
            }
            break;
        }
        if ( mp_knot_left_type(mp,q)<=mp_explicit ) {
            printf("..control?"); /* can't happen */
        } else if ((mp_number_as_double(mp,mp_knot_right_tension(mp,p))!=(1.0))||
                   (mp_number_as_double(mp,mp_knot_left_tension(mp,q)) !=(1.0))) {
            printf("..tension ");
            if ( mp_number_as_double(mp,mp_knot_right_tension(mp,p))<0.0 )
                printf("atleast ");
            printf("%g", fabs(mp_number_as_double(mp,mp_knot_right_tension(mp,p))));
            if (mp_number_as_double(mp,mp_knot_right_tension(mp,p)) !=
                mp_number_as_double(mp,mp_knot_left_tension(mp,q))) {
                printf(" and ");
                if (mp_number_as_double(mp,mp_knot_left_tension(mp,q))< 0.0)
                    printf("atleast ");
                printf("%g", fabs(mp_number_as_double(mp,mp_knot_left_tension(mp,q))));
            }
        }
        DONE:
        p=q;
        if ( p!=h || mp_knot_left_type(mp,h)!=mp_endpoint) {
            printf ("\n ..");
            if ( mp_knot_left_type(mp,p) == mp_given ) {
                double rad = RADIANS(mp_knot_left_curl(mp,p));
                double n_cos = ROUNDED_ZERO(cos(rad)*4096);
                double n_sin = ROUNDED_ZERO(sin(rad)*4096);
                printf("{%g,%g}", n_cos, n_sin);
            } else if ( mp_knot_left_type(mp,p) ==mp_curl ){
                printf("{curl %g}", mp_number_as_double(mp,mp_knot_left_curl(mp,p)));
            }
        }
    } while (p!=h);
    if ( mp_knot_left_type(mp,h)!=mp_endpoint )
        printf("cycle");
    printf (";\n");
}

#endif

static int mplib_solve_path(lua_State * L)
{
    MP *mp_ptr;
    MP mp = NULL;
    int cyclic;
    const char *errormsg = NULL;
    mp_knot p, q, first;
    int numpoints, i;
    p = q = first = NULL;
    if (lua_gettop(L) != 3) {
        errormsg = "Wrong number of arguments";
        goto BAD;
    }
    mp_ptr = is_mp(L, 1);
    if (*mp_ptr == NULL || (!lua_istable(L,2)) || lua_objlen(L,2)<=0 || (!lua_isboolean(L,3))) {
        errormsg = "Wrong argument types";
        goto BAD;
    }
    mp = *mp_ptr;
    cyclic = lua_toboolean(L,3);
    lua_pop(L,1);
    /*tex We build up the path. */
    numpoints = lua_objlen(L,2);
    first = p = NULL;
    for (i=1;i<=numpoints;i++) {
        int left_set = 0, right_set = 0;
        double x_coord, y_coord;
        lua_rawgeti(L,-1,i);
        if (!lua_istable(L,-1)) {
            errormsg = "Wrong argument types";
            goto BAD;
        }
        mplib_push_S(x_coord);
        lua_rawget(L,-2);
        if (!lua_isnumber(L,-1)) {
            errormsg = "Missing X coordinate";
            goto BAD;
        }
        x_coord = (double)lua_tonumber(L,-1);
        lua_pop(L,1);
        mplib_push_S(y_coord);
        lua_rawget(L,-2);
        if (!lua_isnumber(L,-1)) {
            errormsg = "Missing y coordinate";
            goto BAD;
        }
        y_coord = (double)lua_tonumber(L,-1);
        lua_pop(L,1);
        q = p;
        if (q!=NULL) {
            /*tex

                We have to save the right_tension because |mp_append_knot|
                trashes it, believing that it is as yet uninitialized.

            */
            double saved_tension = mp_number_as_double(mp, mp_knot_right_tension(mp,p));
            p = mp_append_knot(mp, p, x_coord, y_coord);
            if ( ! p ) {
                errormsg = "knot creation failure";
                goto BAD;
            }
            (void)mp_set_knot_right_tension(mp, q, saved_tension);
        } else {
            p = mp_append_knot(mp, p, x_coord, y_coord);
            if ( ! p ) {
                errormsg = "knot creation failure";
                goto BAD;
            }
        }
        if (first == NULL)
            first = p;
        mplib_push_S(left_curl);
        lua_rawget(L,-2);
        if (lua_isnumber(L,-1)) {
            if (!set_left_curl (L, mp, p)) {
                errormsg = "failed to set left curl";
                goto BAD;
            }
            left_set  = 1;
        } else {
            /*tex A |nil| value. */
            lua_pop(L,1);
        }
        mplib_push_S(left_tension);
        lua_rawget(L,-2);
        if (lua_isnumber(L,-1)) {
            if (left_set) {
                errormsg = "Left side already set";
                goto BAD;
            } else {
                if (!set_left_tension(L, mp, p)) {
                    errormsg = "Failed to set left tension";
                    goto BAD;
                }
                left_set  = 1;
            }
        } else {
            /*tex A |nil| value. */
            lua_pop(L,1);
        }
        mplib_push_S(left_x);
        lua_rawget(L,-2);
        if (lua_isnumber(L,-1)) {
            if (left_set) {
                errormsg = "Left side already set";
                goto BAD;
            } else {
                if (!set_left_control(L, mp, p)) {
                    errormsg = "Failed to set left control";
                    goto BAD;
                }
            }
        } else {
            lua_pop(L,1);
        }
        mplib_push_S(right_curl);
        lua_rawget(L,-2);
        if (lua_isnumber(L,-1)) {
            if (!set_right_curl (L, mp, p)) {
                errormsg = "failed to set right curl";
                goto BAD;
            }
            right_set  = 1;
        } else {
            /*tex A |nil| value. */
            lua_pop(L,1);
        }
        mplib_push_S(right_tension);
        lua_rawget(L,-2);
        if (lua_isnumber(L,-1)) {
            if (right_set) {
                errormsg = "Right side already set";
                goto BAD;
            } else {
            if (!set_right_tension(L, mp, p)) {
                errormsg = "Failed to set right tension";
                goto BAD;
            }
            right_set = 1;
            }
        } else {
            lua_pop(L,1);
        }
        mplib_push_S(right_x);
        lua_rawget(L,-2);
        if (lua_isnumber(L,-1)) {
            if (right_set) {
                errormsg = "Right side already set";
                goto BAD;
            } else {
                if (!set_right_control(L, mp, p)) {
                    errormsg = "Failed to set right control";
                    goto BAD;
                }
            }
        } else {
            lua_pop(L,1);
        }
        mplib_push_S(direction_x);
        lua_rawget(L,-2);
        if (lua_isnumber(L,-1)) {
            if (!set_direction (L, mp, p)) {
                errormsg = "failed to set direction";
                goto BAD;
            }
        } else {
            /*tex A |nil| value. */
            lua_pop(L,1);
        }
        /*tex Up the next item */
        lua_pop(L,1);
    }
    if (cyclic) {
        mp_close_path_cycle (mp, p, first);
    } else {
        mp_close_path (mp, p, first);
    }
#if 0
    mp_dump_path(mp,first);
#endif
    /*tex We're finished reading arguments. */
    if (!mp_solve_path(mp,first)) {
        errormsg = "Failed to solve the path";
        goto BAD;
    }
    /*tex Squeeze the new values back into the table. */
    p = first;
    for (i=1;i<=numpoints;i++) {
        lua_rawgeti(L,-1, i);
        mplib_push_S(left_x);  lua_pushnumber(L, mp_number_as_double(mp, mp_knot_left_x(mp, p)));  lua_rawset(L,-3);
        mplib_push_S(left_y);  lua_pushnumber(L, mp_number_as_double(mp, mp_knot_left_y(mp, p)));  lua_rawset(L,-3);
        mplib_push_S(right_x); lua_pushnumber(L, mp_number_as_double(mp, mp_knot_right_x(mp, p))); lua_rawset(L,-3);
        mplib_push_S(right_y); lua_pushnumber(L, mp_number_as_double(mp, mp_knot_right_y(mp, p))); lua_rawset(L,-3);
        /*tex This is a bit overkill \unknown */
        mplib_push_S(left_tension);  lua_pushnil(L); lua_rawset(L,-3);
        mplib_push_S(right_tension); lua_pushnil(L); lua_rawset(L,-3);
        mplib_push_S(left_curl);     lua_pushnil(L); lua_rawset(L,-3);
        mplib_push_S(right_curl);    lua_pushnil(L); lua_rawset(L,-3);
        mplib_push_S(direction_x);   lua_pushnil(L); lua_rawset(L,-3);
        mplib_push_S(direction_y);   lua_pushnil(L); lua_rawset(L,-3);
        /*tex \unknown\ till here. */
        mplib_push_S(left_type);  lua_pushstring(L, knot_type_enum[mp_knot_left_type(mp, p)]);  lua_rawset(L, -3);
        mplib_push_S(right_type); lua_pushstring(L, knot_type_enum[mp_knot_right_type(mp, p)]); lua_rawset(L, -3);
        lua_pop(L,1);
        p = mp_knot_next(mp,p);
    }
    lua_pushboolean(L, 1);
    return 1;
  BAD:
    if (p != NULL) {
        mp_close_path (mp, p, first);
        mp_free_path (mp, p);
    }
    lua_pushboolean(L, 0);
    lua_pushstring(L, errormsg);
    return 2;
}

/*tex

    The next methods are for collecting the results from |fig|.

*/

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;
    }
    /*tex Prevent a double free: */
    (*hh)->body = NULL;
    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->ship_out.size != 0)) {
        lua_pushstring(L, res->ship_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->ship_out.size != 0)) {
        lua_pushstring(L, res->ship_out.data);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int mplib_fig_png(lua_State * L)
{
    mp_run_data *res;
    struct mp_edge_object **hh = is_fig(L, 1);
    const char *string = luaL_optstring(L, 2, NULL);
    if (mp_png_ship_out(*hh, string)
        && (res = mp_rundata((*hh)->parent))
        && (res->ship_out.size != 0)) {
        lua_pushlstring(L, res->ship_out.data, res->ship_out.size);
    } 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);
    } 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);
    } 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);
    } 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);
    } 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);
    lua_rawseti(L, -2, 1);
    lua_pushnumber(L, (double) (*hh)->miny);
    lua_rawseti(L, -2, 2);
    lua_pushnumber(L, (double) (*hh)->maxx);
    lua_rawseti(L, -2, 3);
    lua_pushnumber(L, (double) (*hh)->maxy);
    lua_rawseti(L, -2, 4);
    return 1;
}

/*tex

    The methods for the figure objects plus a few helpers.

*/

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

static double eps  = 0.0001;

static double coord_range_x (mp_gr_knot h, double dz) {
    double z;
    double zlo = 0.0, zhi = 0.0;
    mp_gr_knot f = h;
    while (h != NULL) {
        z = h->x_coord;
        if (z < zlo)
            zlo = z;
        else if (z > zhi)
            zhi = z;
        z = h->right_x;
        if (z < zlo)
            zlo = z;
        else if (z > zhi)
            zhi = z;
        z = 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_gr_knot h, double dz) {
    double z;
    double zlo = 0.0, zhi = 0.0;
    mp_gr_knot f = h;
    while (h != NULL) {
        z = h->y_coord;
        if (z < zlo)
            zlo = z;
        else if (z > zhi)
            zhi = z;
        z = h->right_y;
        if (z < zlo)
            zlo = z;
        else if (z > zhi)
            zhi = z;
        z = 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 width = 1.0;
    mp_gr_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;
    y_coord = p->y_coord;
    left_x  = p->left_x;
    left_y  = p->left_y;
    right_x = p->right_x;
    right_y = p->right_y;
    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;
    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;
}

/*tex

    Here is a helper that reports the valid field names of the possible
    objects.

*/

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))

#define MPLIB_PATH 0
#define MPLIB_PEN 1

static void mplib_push_path(lua_State * L, mp_gr_knot h, int is_pen)
{
    mp_gr_knot p;
    int i = 1;
    p = h;
    if (p != NULL) {
        lua_newtable(L);
        do {
            lua_createtable(L, 0, 6);
            if (!is_pen) {
                if (p->data.types.left_type != mp_explicit) {
                    mplib_push_S(left_type);
                    lua_pushstring(L, knot_type_enum[p->data.types.left_type]);
                    lua_rawset(L, -3);
                }
                if (p->data.types.right_type != mp_explicit) {
                    mplib_push_S(right_type);
                    lua_pushstring(L, knot_type_enum[p->data.types.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->data.types.right_type == mp_endpoint) {
                return;
            }
            p = p->next;
        } while (p != h);
    } else {
        lua_pushnil(L);
    }
}

static int mplib_get_path(lua_State * L)
{
    MP *mp = is_mp(L, 1);
    if (*mp != NULL) {
        size_t l;
        const char *s = lua_tolstring(L, 2, &l);
        if (s != NULL) {
            mp_knot p = mp_get_path_value(*mp,s,l) ;
            if (p != NULL) {
                int i = 1;
                mp_knot h = p;
                lua_newtable(L);
                do {
                    lua_createtable(L, 6, 1);
                    mplib_push_number(L, mp_number_as_double(*mp,p->x_coord));
                    lua_rawseti(L,-2,1);
                    mplib_push_number(L, mp_number_as_double(*mp,p->y_coord));
                    lua_rawseti(L,-2,2);
                    mplib_push_number(L, mp_number_as_double(*mp,p->left_x));
                    lua_rawseti(L,-2,3);
                    mplib_push_number(L, mp_number_as_double(*mp,p->left_y));
                    lua_rawseti(L,-2,4);
                    mplib_push_number(L, mp_number_as_double(*mp,p->right_x));
                    lua_rawseti(L,-2,5);
                    mplib_push_number(L, mp_number_as_double(*mp,p->right_y));
                    lua_rawseti(L,-2,6);
                    lua_rawseti(L,-2, i);
                    i++;
                    if (p->data.types.right_type == mp_endpoint) {
                        mplib_push_S(cycle);
                        lua_pushboolean(L,0);
                        lua_rawset(L,-3);
                        return 1;
                    }
                    p = p->next;
                } while (p != h);
                mplib_push_S(cycle);
                lua_pushboolean(L,1);
                lua_rawset(L,-3);
                return 1;
            }
        }
    }
    return 0;
}

/*tex

    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, mp_gr_knot h)
{
    mp_gr_knot p;
    p = h;
    if (p == NULL) {
        /*tex Do nothing. */
    } else if (p == p->next) {
        mplib_push_S(type);
        mplib_push_S(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;
    double 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);
    }
}

/*tex

    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_S(offset);
        mplib_push_number(L, d->offset);
        lua_rawset(L,-3);
        if (d->array != NULL) {
            int i = 0;
            mplib_push_S(dashes);
            lua_newtable(L);
            while (*(d->array + i) != -1) {
                ds = *(d->array + i);
                lua_pushnumber(L, ds);
                i++;
                lua_rawseti(L, -2, i);
            }
            lua_rawset(L,-3);
        }
    } 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_pushlstring(L, h->text_p, h->text_l);
    } 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 },
    /*tex sentinel */
    { NULL,         NULL}
};

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 },
    { "png",          mplib_fig_png },
    { "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 },
    /*tex sentinel */
    { NULL,           NULL}
};

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

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 },
    { "solve_path",  mplib_solve_path },
    { "get_numeric", mplib_get_numeric },
    { "get_number",  mplib_get_numeric },
    { "get_boolean", mplib_get_boolean },
    { "get_string",  mplib_get_string },
    { "get_path",    mplib_get_path },
    /*tex sentinel */
    {NULL,           NULL }
};

static const struct luaL_reg mplib_m[] = {
    { "new",         mplib_new },
    { "version",     mplib_version },
    { "fields",      mplib_gr_fields },
    /* indirect */
    { "execute",     mplib_execute },
    { "finish",      mplib_finish },
    { "char_width",  mplib_charwidth },
    { "char_height", mplib_charheight },
    { "char_depth",  mplib_chardepth },
    { "statistics",  mplib_statistics },
    { "solve_path",  mplib_solve_path },
    /* helpers */
    { "pen_info",    mplib_gr_peninfo },
    { "get_numeric", mplib_get_numeric },
    { "get_number",  mplib_get_numeric },
    { "get_boolean", mplib_get_boolean },
    { "get_string",  mplib_get_string },
    { "get_path",    mplib_get_path },
    /*tex sentinel */
    { NULL,          NULL}
};

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

    luaL_newmetatable(L, MPLIB_GR_METATABLE);
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");
    luaL_register(L, NULL, mplib_gr_meta);
    lua_pop(L, 1);

    luaL_newmetatable(L, MPLIB_FIG_METATABLE);
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");
    luaL_register(L, NULL, mplib_fig_meta);
    lua_pop(L, 1);

    luaL_newmetatable(L, MPLIB_METATABLE);
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");
    luaL_register(L, NULL, mplib_meta);
    luaL_register(L, NULL, mplib_d);
    luaL_register(L, "mplib", mplib_m);

    return 1;
}
