/* limglib.c

  Copyright 2006-2010 Taco Hoekwater <[email protected]>

  This file is part of LuaTeX.

  LuaTeX is free software; you can redistribute it and/or modify it under
  the terms of the GNU General Public License as published by the Free
  Software Foundation; either version 2 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 General Public License along
  with LuaTeX; if not, see <http://www.gnu.org/licenses/>. */

static const char _svn_version[] =
   "$Id: limglib.c 4051 2011-01-09 22:41:33Z hhenkel $ "
   "$URL: http://foundry.supelec.fr/svn/luatex/tags/beta-0.70.1/source/texk/web2c/luatexdir/lua/limglib.c $";

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "lua51/lua.h"
#include "lua51/lauxlib.h"
#include "ptexlib.h"
#include "lua/luatex-api.h"

/**********************************************************************/

#ifdef DEBUG
void stackDump(lua_State * L, char *s)
{
   int i, t, top = lua_gettop(L);
   printf("\n=== stackDump <%s>: ", s);
   for (i = top; i >= 1; i--) {        /* repeat for each level */
       t = lua_type(L, i);
       printf("%d: ", i);
       switch (t) {
       case LUA_TSTRING:      /* strings */
           printf("`%s'", lua_tostring(L, i));
           break;
       case LUA_TBOOLEAN:     /* booleans */
           printf(lua_toboolean(L, i) ? "true" : "false");
           break;
       case LUA_TNUMBER:      /* numbers */
           printf("%g", (double) lua_tonumber(L, i));
           break;
       default:               /* other values */
           printf("%s", lua_typename(L, t));
           break;
       }
       printf("  ");           /* put a separator */
   }
   printf("\n");
}
#endif

/**********************************************************************/

typedef enum { P__ZERO, P_ATTR, P_BBOX, P_COLORDEPTH, P_COLORSPACE, P_DEPTH,
   P_FILENAME, P_FILEPATH, P_HEIGHT, P_IMAGETYPE, P_INDEX, P_OBJNUM,
   P_PAGE, P_PAGEBOX, P_TOTALPAGES, P_ROTATION, P_STREAM, P_TRANSFORM,
   P_WIDTH, P_XRES, P_XSIZE, P_YRES, P_YSIZE, P__SENTINEL
} parm_idx;

static const parm_struct img_parms[] = {
   {NULL, P__ZERO},            /* dummy; lua indices run from 1 */
   {"attr", P_ATTR},
   {"bbox", P_BBOX},
   {"colordepth", P_COLORDEPTH},
   {"colorspace", P_COLORSPACE},
   {"depth", P_DEPTH},
   {"filename", P_FILENAME},
   {"filepath", P_FILEPATH},
   {"height", P_HEIGHT},
   {"imagetype", P_IMAGETYPE},
   {"index", P_INDEX},
   {"objnum", P_OBJNUM},
   {"page", P_PAGE},
   {"pagebox", P_PAGEBOX},
   {"pages", P_TOTALPAGES},
   {"rotation", P_ROTATION},
   {"stream", P_STREAM},
   {"transform", P_TRANSFORM},
   {"width", P_WIDTH},
   {"xres", P_XRES},
   {"xsize", P_XSIZE},
   {"yres", P_YRES},
   {"ysize", P_YSIZE},
   {NULL, P__SENTINEL}
};

#define imgtype_max 6
const char *imgtype_s[] =
   { "none", "pdf", "png", "jpg", "jp2", "jbig2", "stream", NULL };

#define pagebox_max 5
const char *pdfboxspec_s[] =
   { "none", "media", "crop", "bleed", "trim", "art", NULL };

/**********************************************************************/

static void image_to_lua(lua_State * L, image * a)
{                               /* key user ... */
   int i, j;
   image_dict *d = img_dict(a);
   assert(d != NULL);
   lua_pushvalue(L, -1);       /* k k u ... */
   lua_gettable(L, LUA_ENVIRONINDEX);  /* i? k u ... */
   if (!lua_isnumber(L, -1))   /* !i k u ... */
       luaL_error(L, "image_to_lua(): %s is not a valid image key",
                  lua_tostring(L, -2));
   i = (int) lua_tointeger(L, -1);     /* i k u ... */
   lua_pop(L, 2);              /* u ... */
   switch (i) {
   case P_WIDTH:
       if (is_wd_running(a))
           lua_pushnil(L);
       else
           lua_pushinteger(L, img_width(a));
       break;
   case P_HEIGHT:
       if (is_ht_running(a))
           lua_pushnil(L);
       else
           lua_pushinteger(L, img_height(a));
       break;
   case P_DEPTH:
       if (is_dp_running(a))
           lua_pushnil(L);
       else
           lua_pushinteger(L, img_depth(a));
       break;
   case P_TRANSFORM:
       lua_pushinteger(L, img_transform(a));
       break;
       /* now follow all image_dict entries */
   case P_FILENAME:
       if (img_filename(d) == NULL || strlen(img_filename(d)) == 0)
           lua_pushnil(L);
       else
           lua_pushstring(L, img_filename(d));
       break;
   case P_FILEPATH:
       if (img_filepath(d) == NULL || strlen(img_filepath(d)) == 0)
           lua_pushnil(L);
       else
           lua_pushstring(L, img_filepath(d));
       break;
   case P_ATTR:
       if (img_attr(d) == NULL || strlen(img_attr(d)) == 0)
           lua_pushnil(L);
       else
           lua_pushstring(L, img_attr(d));
       break;
   case P_PAGE:
       if (img_pagename(d) != NULL && strlen(img_pagename(d)) != 0)
           lua_pushstring(L, img_pagename(d));
       else
           lua_pushinteger(L, img_pagenum(d));
       break;
   case P_TOTALPAGES:
       lua_pushinteger(L, img_totalpages(d));
       break;
   case P_XSIZE:              /* Modify by /Rotate only for output */
       if ((img_rotation(d) & 1) == 0)
           lua_pushinteger(L, img_xsize(d));
       else
           lua_pushinteger(L, img_ysize(d));
       break;
   case P_YSIZE:              /* Modify by /Rotate only for output */
       if ((img_rotation(d) & 1) == 0)
           lua_pushinteger(L, img_ysize(d));
       else
           lua_pushinteger(L, img_xsize(d));
       break;
   case P_XRES:
       lua_pushinteger(L, img_xres(d));
       break;
   case P_YRES:
       lua_pushinteger(L, img_yres(d));
       break;
   case P_ROTATION:
       lua_pushinteger(L, img_rotation(d));
       break;
   case P_COLORSPACE:
       if (img_colorspace(d) == 0)
           lua_pushnil(L);
       else
           lua_pushinteger(L, img_colorspace(d));
       break;
   case P_COLORDEPTH:
       if (img_colordepth(d) == 0)
           lua_pushnil(L);
       else
           lua_pushinteger(L, img_colordepth(d));
       break;
   case P_IMAGETYPE:
       j = img_type(d);
       if (j >= 0 && j <= imgtype_max) {
           if (j == IMG_TYPE_NONE)
               lua_pushnil(L);
           else
               lua_pushstring(L, imgtype_s[j]);
       } else
           assert(0);
       break;
   case P_PAGEBOX:
       j = img_pagebox(d);
       if (j >= 0 && j <= pagebox_max) {
           if (j == PDF_BOX_SPEC_NONE)
               lua_pushnil(L);
           else
               lua_pushstring(L, pdfboxspec_s[j]);
       } else
           assert(0);
       break;
   case P_BBOX:
       if (!img_is_bbox(d)) {
           img_bbox(d)[0] = img_xorig(d);
           img_bbox(d)[1] = img_yorig(d);
           img_bbox(d)[2] = img_xorig(d) + img_xsize(d);
           img_bbox(d)[3] = img_yorig(d) + img_ysize(d);
       }
       lua_newtable(L);
       lua_pushinteger(L, 1);
       lua_pushinteger(L, img_bbox(d)[0]);
       lua_settable(L, -3);
       lua_pushinteger(L, 2);
       lua_pushinteger(L, img_bbox(d)[1]);
       lua_settable(L, -3);
       lua_pushinteger(L, 3);
       lua_pushinteger(L, img_bbox(d)[2]);
       lua_settable(L, -3);
       lua_pushinteger(L, 4);
       lua_pushinteger(L, img_bbox(d)[3]);
       lua_settable(L, -3);
       break;
   case P_OBJNUM:
       if (img_objnum(d) == 0)
           lua_pushnil(L);
       else
           lua_pushinteger(L, img_objnum(d));
       break;
   case P_INDEX:
       if (img_index(d) == 0)
           lua_pushnil(L);
       else
           lua_pushinteger(L, img_index(d));
       break;
   case P_STREAM:
       if (img_type(d) != IMG_TYPE_PDFSTREAM || img_pdfstream_ptr(d) == NULL
           || img_pdfstream_stream(d) == NULL
           || strlen(img_pdfstream_stream(d)) == 0)
           lua_pushnil(L);
       else
           lua_pushstring(L, img_pdfstream_stream(d));
       break;
   default:
       assert(0);
   }                           /* v u ... */
}

static void lua_to_image(lua_State * L, image * a)
{                               /* value key table ... */
   int i;
   image_dict *d = img_dict(a);
   assert(d != NULL);
   lua_pushvalue(L, -2);       /* k v k t ... */
   lua_gettable(L, LUA_ENVIRONINDEX);  /* i? v k t ... */
   if (!lua_isnumber(L, -1))   /* !i v k t ... */
       luaL_error(L, "lua_to_image(): %s is not a valid image key",
                  lua_tostring(L, -3));
   i = (int) lua_tointeger(L, -1);     /* i v k t ... */
   lua_pop(L, 1);              /* v k t ... */
   switch (i) {
   case P_WIDTH:
       if (lua_isnil(L, -1))
           set_wd_running(a);
       else if (lua_type(L, -1) == LUA_TNUMBER)
           img_width(a) = (int) lua_tointeger(L, -1);
       else if (lua_type(L, -1) == LUA_TSTRING)
           img_width(a) = dimen_to_number(L, lua_tostring(L, -1));
       else
           luaL_error(L,
                      "image.width needs integer or nil value or dimension string");
       break;
   case P_HEIGHT:
       if (lua_isnil(L, -1))
           set_ht_running(a);
       else if (lua_type(L, -1) == LUA_TNUMBER)
           img_height(a) = (int) lua_tointeger(L, -1);
       else if (lua_type(L, -1) == LUA_TSTRING)
           img_height(a) = dimen_to_number(L, lua_tostring(L, -1));
       else
           luaL_error(L,
                      "image.height needs integer or nil value or dimension string");
       break;
   case P_DEPTH:
       if (lua_isnil(L, -1))
           set_dp_running(a);
       else if (lua_type(L, -1) == LUA_TNUMBER)
           img_depth(a) = (int) lua_tointeger(L, -1);
       else if (lua_type(L, -1) == LUA_TSTRING)
           img_depth(a) = dimen_to_number(L, lua_tostring(L, -1));
       else
           luaL_error(L,
                      "image.depth needs integer or nil value or dimension string");
       break;
   case P_TRANSFORM:
       if (lua_isnumber(L, -1))
           img_transform(a) = (int) lua_tointeger(L, -1);
       else
           luaL_error(L, "image.transform needs integer value");
       break;
       /* now follow all image_dict entries */
   case P_FILENAME:
       if (img_state(d) >= DICT_FILESCANNED)
           luaL_error(L, "image.filename is now read-only");
       if (img_type(d) == IMG_TYPE_PDFSTREAM)
           luaL_error(L, "image.filename can't be used with image.stream");
       if (lua_isstring(L, -1)) {
           xfree(img_filename(d));
           img_filename(d) = xstrdup(lua_tostring(L, -1));
       } else
           luaL_error(L, "image.filename needs string value");
       break;
   case P_ATTR:
       if (img_state(d) >= DICT_FILESCANNED)
           luaL_error(L, "image.attr is now read-only");
       if (lua_isstring(L, -1) || lua_isnil(L, -1)) {
           xfree(img_attr(d));
           if (lua_isstring(L, -1))
               img_attr(d) = xstrdup(lua_tostring(L, -1));
       } else
           luaL_error(L, "image.attr needs string or nil value");
       break;
   case P_PAGE:
       if (img_state(d) >= DICT_FILESCANNED)
           luaL_error(L, "image.page is now read-only");
       if (lua_type(L, -1) == LUA_TSTRING) {
           xfree(img_pagename(d));
           img_pagename(d) = xstrdup(lua_tostring(L, -1));
           img_pagenum(d) = 0;
       } else if (lua_type(L, -1) == LUA_TNUMBER) {
           img_pagenum(d) = (int) lua_tointeger(L, -1);
           xfree(img_pagename(d));
       } else
           luaL_error(L, "image.page needs integer or string value");
       break;
   case P_COLORSPACE:
       if (img_state(d) >= DICT_FILESCANNED)
           luaL_error(L, "image.colorspace is now read-only");
       if (lua_isnil(L, -1))
           img_colorspace(d) = 0;
       else if (lua_isnumber(L, -1))
           img_colorspace(d) = (int) lua_tointeger(L, -1);
       else
           luaL_error(L, "image.colorspace needs integer or nil value");
       break;
   case P_PAGEBOX:
       if (img_state(d) >= DICT_FILESCANNED)
           luaL_error(L, "image.pagebox is now read-only");
       if (lua_isnil(L, -1))
           img_pagebox(d) = PDF_BOX_SPEC_NONE;
       else if (lua_isstring(L, -1))
           img_pagebox(d) = luaL_checkoption(L, -1, "none", pdfboxspec_s);
       else
           luaL_error(L, "image.pagebox needs string or nil value");
       break;
   case P_BBOX:
       if (img_state(d) >= DICT_FILESCANNED)
           luaL_error(L, "image.bbox is now read-only");
       if (!lua_istable(L, -1))
           luaL_error(L, "image.bbox needs table value");
       if (lua_objlen(L, -1) != 4)
           luaL_error(L, "image.bbox table must have exactly 4 elements");
       for (i = 1; i <= 4; i++) {      /* v k t ... */
           lua_pushinteger(L, i);      /* idx v k t ... */
           lua_gettable(L, -2);        /* int v k t ... */
           if (lua_type(L, -1) == LUA_TNUMBER)
               img_bbox(d)[i - 1] = (int) lua_tointeger(L, -1);
           else if (lua_type(L, -1) == LUA_TSTRING)
               img_bbox(d)[i - 1] = dimen_to_number(L, lua_tostring(L, -1));
           else
               luaL_error(L,
                          "image.bbox table needs integer value or dimension string elements");
           lua_pop(L, 1);      /* v k t ... */
       }
       img_set_bbox(d);
       break;
   case P_STREAM:
       if (img_filename(d) != NULL)
           luaL_error(L, "image.stream can't be used with image.filename");
       if (img_state(d) >= DICT_FILESCANNED)
           luaL_error(L, "image.stream is now read-only");
       if (img_pdfstream_ptr(d) == NULL)
           new_img_pdfstream_struct(d);
       xfree(img_pdfstream_stream(d));
       img_pdfstream_stream(d) = xstrdup(lua_tostring(L, -1));
       img_type(d) = IMG_TYPE_PDFSTREAM;
       break;
   case P_FILEPATH:
   case P_TOTALPAGES:
   case P_XSIZE:
   case P_YSIZE:
   case P_XRES:
   case P_YRES:
   case P_ROTATION:
   case P_IMAGETYPE:
   case P_OBJNUM:
   case P_INDEX:
   case P_COLORDEPTH:
       luaL_error(L, "image.%s is a read-only variable", img_parms[i].name);
       break;
   default:
       assert(0);
   }                           /* v k t ... */
}

/**********************************************************************/

static void copy_image(lua_State * L, lua_Number scale)
{
   image *a, **aa, *b, **bb;
   if (lua_gettop(L) != 1)
       luaL_error(L, "img.copy() needs exactly 1 argument");
   aa = (image **) luaL_checkudata(L, 1, TYPE_IMG);    /* a */
   lua_pop(L, 1);              /* - */
   a = *aa;
   bb = (image **) lua_newuserdata(L, sizeof(image *));        /* b */
   luaL_getmetatable(L, TYPE_IMG);     /* m b */
   lua_setmetatable(L, -2);    /* b */
   b = *bb = new_image();
   if (!is_wd_running(a))
       img_width(b) = do_zround(img_width(a) * scale);
   if (!is_ht_running(a))
       img_height(b) = do_zround(img_height(a) * scale);
   if (!is_dp_running(a))
       img_depth(b) = do_zround(img_depth(a) * scale);
   img_transform(b) = img_transform(a);
   img_dict(b) = img_dict(a);
   if (img_dictref(a) != LUA_NOREF) {
       lua_rawgeti(L, LUA_GLOBALSINDEX, img_dictref(a));       /* ad b */
       img_dictref(b) = luaL_ref(L, LUA_GLOBALSINDEX); /* b */
   } else
       assert(img_state(img_dict(a)) >= DICT_REFERED);
}

/**********************************************************************/

int l_new_image(lua_State * L)
{
   image *a, **aa;
   image_dict **add;
   if (lua_gettop(L) > 1)
       luaL_error(L, "img.new() needs maximum 1 argument");
   if (lua_gettop(L) == 1 && !lua_istable(L, -1))
       luaL_error(L, "img.new() needs table as optional argument");    /* (t) */
   aa = (image **) lua_newuserdata(L, sizeof(image *));        /* i (t) */
   luaL_getmetatable(L, TYPE_IMG);     /* m i (t) */
   lua_setmetatable(L, -2);    /* i (t) */
   a = *aa = new_image();
   add = (image_dict **) lua_newuserdata(L, sizeof(image_dict *));     /* ad i (t) */
   luaL_getmetatable(L, TYPE_IMG_DICT);        /* m ad i (t) */
   lua_setmetatable(L, -2);    /* ad i (t) */
   img_dict(a) = *add = new_image_dict();
   img_dictref(a) = luaL_ref(L, LUA_GLOBALSINDEX);     /* i (t) */
   if (lua_gettop(L) == 2) {   /* i t, else just i */
       lua_insert(L, -2);      /* t i */
       lua_pushnil(L);         /* n t i (1st key for iterator) */
       while (lua_next(L, -2) != 0) {  /* v k t i */
           lua_to_image(L, a); /* v k t i */
           lua_pop(L, 1);      /* k t i */
       }                       /* t i */
       lua_pop(L, 1);          /* i */
   }                           /* i */
   return 1;                   /* i */
}

static int l_copy_image(lua_State * L)
{
   if (lua_gettop(L) != 1)
       luaL_error(L, "img.copy() needs exactly 1 argument");
   if (lua_istable(L, 1))
       (void) l_new_image(L);  /* image --- if everything worked well */
   else
       (void) copy_image(L, 1.0);      /* image */
   return 1;                   /* image */
}

static void read_scale_img(image * a)
{
   image_dict *ad;
   assert(a != NULL);
   ad = img_dict(a);
   assert(ad != NULL);
   if (img_state(ad) == DICT_NEW) {
       if (img_type(ad) == IMG_TYPE_PDFSTREAM)
           check_pdfstream_dict(ad);
       else {
           fix_pdf_minorversion(static_pdf);
           read_img(static_pdf,
                    ad, pdf_minor_version, pdf_inclusion_errorlevel);
       }
   }
   if (is_wd_running(a) || is_ht_running(a) || is_dp_running(a))
       img_dimen(a) = scale_img(ad, img_dimen(a), img_transform(a));
}

static int l_scan_image(lua_State * L)
{
   image *a, **aa;
   if (lua_gettop(L) != 1)
       luaL_error(L, "img.scan() needs exactly 1 argument");
   if (lua_istable(L, 1))
       (void) l_new_image(L);  /* image --- if everything worked well */
   aa = (image **) luaL_checkudata(L, 1, TYPE_IMG);    /* image */
   a = *aa;
   check_o_mode(static_pdf, "img.scan()", 1 << OMODE_PDF, false);
   /* flush_str(last_tex_string); *//* ?? */
   read_scale_img(a);
   return 1;                   /* image */
}

static halfword img_to_node(image * a)
{
   image_dict *ad;
   halfword n;
   assert(a != NULL);
   ad = img_dict(a);
   assert(ad != NULL);
   assert(img_objnum(ad) != 0);
   n = new_node(whatsit_node, pdf_refximage_node);
   pdf_ximage_index(n) = img_index(ad);
   width(n) = img_width(a);
   height(n) = img_height(a);
   depth(n) = img_depth(a);
   pdf_ximage_transform(n) = img_transform(a);
   return n;
}

typedef enum { WR_WRITE, WR_IMMEDIATEWRITE, WR_NODE, WR_VF_IMG } wrtype_e;
const char *wrtype_s[] =
   { "img.write()", "img.immediatewrite()", "img.node()", "write vf image" };

static void setup_image(PDF pdf, image * a, wrtype_e writetype)
{
   image_dict *ad;
   assert(a != NULL);
   ad = img_dict(a);
   check_o_mode(pdf, wrtype_s[writetype], 1 << OMODE_PDF, false);
   /* flush_str(last_tex_string); *//* ?? */
   read_scale_img(a);
   if (img_objnum(ad) == 0) {  /* latest needed just before out_img() */
       pdf->ximage_count++;
       img_objnum(ad) =
           pdf_create_obj(pdf, obj_type_ximage, pdf->ximage_count);
       img_index(ad) = pdf->ximage_count;
       idict_to_array(ad);     /* now ad is read-only */
       obj_data_ptr(pdf, pdf->obj_ptr) = img_index(ad);
   }
}

static void write_image_or_node(lua_State * L, wrtype_e writetype)
{
   image *a, **aa;
   image_dict *ad;
   halfword n;
   if (lua_gettop(L) != 1)
       luaL_error(L, "%s needs exactly 1 argument", wrtype_s[writetype]);
   if (lua_istable(L, 1))
       (void) l_new_image(L);  /* image --- if everything worked well */
   aa = (image **) luaL_checkudata(L, 1, TYPE_IMG);    /* image */
   a = *aa;
   ad = img_dict(a);
   setup_image(static_pdf, a, writetype);
   switch (writetype) {
   case WR_WRITE:
       n = img_to_node(a);
       tail_append(n);
       break;                  /* image */
   case WR_IMMEDIATEWRITE:
       pdf_begin_dict(static_pdf, img_objnum(ad), 0);
       write_img(static_pdf, ad);
       break;                  /* image */
   case WR_NODE:              /* image */
       lua_pop(L, 1);          /* - */
       n = img_to_node(a);
       lua_nodelib_push_fast(L, n);
       break;                  /* node */
   default:
       assert(0);
   }
   if (img_state(ad) < DICT_REFERED)
       img_state(ad) = DICT_REFERED;
}

static int l_write_image(lua_State * L)
{
   write_image_or_node(L, WR_WRITE);
   return 1;                   /* image */
}

static int l_immediatewrite_image(lua_State * L)
{
   check_o_mode(static_pdf, "img.immediatewrite()", 1 << OMODE_PDF, true);
   if (global_shipping_mode != NOT_SHIPPING)
       luaL_error(L, "pdf.immediatewrite() can not be used with \\latelua");
   write_image_or_node(L, WR_IMMEDIATEWRITE);
   return 1;                   /* image */
}

static int l_image_node(lua_State * L)
{
   write_image_or_node(L, WR_NODE);
   return 1;                   /* node */
}

static int l_image_keys(lua_State * L)
{
   const parm_struct *p = img_parms + 1;
   if (lua_gettop(L) != 0)
       luaL_error(L, "img.keys() goes without argument");
   lua_newtable(L);            /* t */
   for (; p->name != NULL; p++) {
       lua_pushinteger(L, (int) p->idx);       /* k t */
       lua_pushstring(L, p->name);     /* v k t */
       lua_settable(L, -3);    /* t */
   }
   return 1;
}

static int l_image_types(lua_State * L)
{
   int i;
   const char **p;
   if (lua_gettop(L) != 0)
       luaL_error(L, "img.types() goes without argument");
   lua_newtable(L);            /* t */
   for (i = 1, p = (const char **) (imgtype_s + 1); *p != NULL; p++, i++) {
       lua_pushinteger(L, (int) i);    /* k t */
       lua_pushstring(L, *p);  /* v k t */
       lua_settable(L, -3);    /* t */
   }
   return 1;
}

static int l_image_boxes(lua_State * L)
{
   int i;
   const char **p;
   if (lua_gettop(L) != 0)
       luaL_error(L, "img.boxes() goes without argument");
   lua_newtable(L);            /* t */
   for (i = 1, p = (const char **) (pdfboxspec_s + 1); *p != NULL; p++, i++) {
       lua_pushinteger(L, (int) i);    /* k t */
       lua_pushstring(L, *p);  /* v k t */
       lua_settable(L, -3);    /* t */
   }
   return 1;
}

static const struct luaL_Reg imglib[] = {
   {"new", l_new_image},
   {"copy", l_copy_image},
   {"scan", l_scan_image},
   {"write", l_write_image},
   {"immediatewrite", l_immediatewrite_image},
   {"node", l_image_node},
   {"keys", l_image_keys},
   {"types", l_image_types},
   {"boxes", l_image_boxes},
   {NULL, NULL}                /* sentinel */
};

/**********************************************************************/

void vf_out_image(PDF pdf, unsigned i)
{
   image *a, **aa;
   image_dict *ad;
   lua_State *L = Luas;        /* ... */
   lua_rawgeti(L, LUA_GLOBALSINDEX, (int) i);  /* image ... */
   aa = (image **) luaL_checkudata(L, -1, TYPE_IMG);
   a = *aa;
   ad = img_dict(a);
   assert(ad != NULL);
   setup_image(pdf, a, WR_VF_IMG);     /* image ... */
   place_img(pdf, ad, img_dimen(a), img_transform(a));
   lua_pop(L, 1);              /* ... */
}

/**********************************************************************/
/* Metamethods for image */

static int m_img_get(lua_State * L)
{
   image **aa = (image **) luaL_checkudata(L, 1, TYPE_IMG);    /* k u */
   image_to_lua(L, *aa);       /* v u */
   return 1;
}

static int m_img_set(lua_State * L)
{
   image **aa = (image **) luaL_checkudata(L, 1, TYPE_IMG);    /* value key user */
   lua_to_image(L, *aa);       /* v k u */
   return 0;
}

static int m_img_mul(lua_State * L)
{
   image **aa;
   lua_Number scale;
   if (lua_isnumber(L, 1)) {   /* u? n */
       aa = (image **) luaL_checkudata(L, 2, TYPE_IMG);        /* u n */
       lua_insert(L, -2);      /* n a */
   } else if (lua_isnumber(L, 2)) {    /* n u? */
       aa = (image **) luaL_checkudata(L, 1, TYPE_IMG);        /* n a */
   }                           /* n a */
   scale = lua_tonumber(L, 2); /* n a */
   lua_pop(L, 1);              /* a */
   copy_image(L, scale);       /* b */
   return 1;
}

static int m_img_print(lua_State * L)
{
   image **aa;
   image_dict *d;
   aa = (image **) luaL_checkudata(L, 1, TYPE_IMG);
   d = img_dict(*aa);
   if (img_pagename(d) != NULL && strlen(img_pagename(d)) != 0)
       lua_pushfstring(L, "<img{filename=\"%s\", page=\"%s\"}>",
                       img_filename(d), img_pagename(d));
   else
       lua_pushfstring(L, "<img{filename=\"%s\", page=%d}>", img_filename(d),
                       img_pagenum(d));
   return 1;
}

static int m_img_gc(lua_State * L)
{
   image *a, **aa;
   image_dict *d;
   aa = (image **) luaL_checkudata(L, 1, TYPE_IMG);
   a = *aa;
   d = img_dict(*aa);
#ifdef DEBUG
   printf("\n===== IMG GC ===== a=%d ad=%d\n", a, img_dict(a));
#endif
   luaL_unref(L, LUA_GLOBALSINDEX, img_dictref(a));
   if (!img_is_refered(d))
       xfree(a);
   return 0;
}

static const struct luaL_Reg img_m[] = {
   {"__index", m_img_get},
   {"__newindex", m_img_set},
   {"__mul", m_img_mul},
   {"__tostring", m_img_print},
   {"__gc", m_img_gc},         /* finalizer */
   {NULL, NULL}                /* sentinel */
};

/**********************************************************************/
/* Metamethods for image_dict */

static int m_img_dict_gc(lua_State * L)
{
   image_dict *ad, **add;
   add = (image_dict **) luaL_checkudata(L, 1, TYPE_IMG_DICT);
   ad = *add;
#ifdef DEBUG
   printf("\n===== IMG_DICT GC FREE ===== ad=%d\n", ad);
#endif
   if (img_state(ad) < DICT_REFERED)
       free_image_dict(ad);
   return 0;
}

static const struct luaL_Reg img_dict_m[] = {
   {"__gc", m_img_dict_gc},    /* finalizer */
   {NULL, NULL}                /* sentinel */
};

/**********************************************************************/

int luaopen_img(lua_State * L)
{
   preset_environment(L, img_parms);
   luaL_newmetatable(L, TYPE_IMG);
   luaL_register(L, NULL, img_m);
   luaL_newmetatable(L, TYPE_IMG_DICT);
   luaL_register(L, NULL, img_dict_m);
   luaL_register(L, "img", imglib);
   return 1;
}

/**********************************************************************/