diff options
author | Tor Andersson <tor.andersson@artifex.com> | 2016-02-22 16:05:16 +0100 |
---|---|---|
committer | Tor Andersson <tor.andersson@artifex.com> | 2016-02-29 16:03:34 +0100 |
commit | 51422b7b07d708da2433f28a6c3c6547b0f5c319 (patch) | |
tree | 880ef52d1b84d0c3a84b3e1a59e1842343637e83 /source/tools | |
parent | e3829adb5b53800570ac750a9630b2f98a0d5211 (diff) | |
download | mupdf-51422b7b07d708da2433f28a6c3c6547b0f5c319.tar.xz |
js: Add "mutool run" tool to run javascript scripts.
Use an API similar to the JNI bindings.
Diffstat (limited to 'source/tools')
-rw-r--r-- | source/tools/murun.c | 1872 | ||||
-rw-r--r-- | source/tools/mutool.c | 2 |
2 files changed, 1874 insertions, 0 deletions
diff --git a/source/tools/murun.c b/source/tools/murun.c new file mode 100644 index 00000000..43c74e25 --- /dev/null +++ b/source/tools/murun.c @@ -0,0 +1,1872 @@ +#include "mupdf/fitz.h" + +#include "mujs.h" + +#define PS1 "> " + +static void rethrow(js_State *J) +{ + js_newerror(J, fz_caught_message(js_getcontext(J))); + js_throw(J); +} + +static void *alloc(void *actx, void *ptr, unsigned int n) +{ + fz_context *ctx = actx; + if (n == 0) { + fz_free(ctx, ptr); + return NULL; + } + if (ptr) + return fz_resize_array(ctx, ptr, n, 1); + return fz_malloc_array(ctx, n, 1); +} + +static void jsB_propenum(js_State *J, const char *name, int value) +{ + js_pushnumber(J, value); + js_defproperty(J, -2, name, JS_DONTENUM | JS_READONLY | JS_DONTCONF); +} + +static void jsB_propfun(js_State *J, const char *name, js_CFunction cfun, int n) +{ + const char *realname = strchr(name, '.'); + realname = realname ? realname + 1 : name; + js_newcfunction(J, cfun, name, n); + js_defproperty(J, -2, realname, JS_DONTENUM); +} + +static void jsB_propcon(js_State *J, const char *tag, const char *name, js_CFunction cfun, int n) +{ + const char *realname = strchr(name, '.'); + realname = realname ? realname + 1 : name; + js_getregistry(J, tag); + js_newcconstructor(J, cfun, cfun, name, n); + js_defproperty(J, -2, realname, JS_DONTENUM); +} + +static void jsB_gc(js_State *J) +{ + int report = js_toboolean(J, 1); + js_gc(J, report); + js_pushundefined(J); +} + +static void jsB_load(js_State *J) +{ + const char *filename = js_tostring(J, 1); + int rv = js_dofile(J, filename); + js_pushboolean(J, !rv); +} + +static void jsB_print(js_State *J) +{ + unsigned int i, top = js_gettop(J); + for (i = 1; i < top; ++i) { + const char *s = js_tostring(J, i); + if (i > 1) putchar(' '); + fputs(s, stdout); + } + putchar('\n'); + js_pushundefined(J); +} + +static void jsB_write(js_State *J) +{ + unsigned int i, top = js_gettop(J); + for (i = 1; i < top; ++i) { + const char *s = js_tostring(J, i); + if (i > 1) putchar(' '); + fputs(s, stdout); + } + js_pushundefined(J); +} + +static void jsB_read(js_State *J) +{ + const char *filename = js_tostring(J, 1); + FILE *f; + char *s; + int n, t; + + f = fopen(filename, "rb"); + if (!f) { + js_error(J, "cannot open file: '%s'", filename); + } + + if (fseek(f, 0, SEEK_END) < 0) { + fclose(f); + js_error(J, "cannot seek in file: '%s'", filename); + } + + n = ftell(f); + if (n < 0) { + fclose(f); + js_error(J, "cannot tell in file: '%s'", filename); + } + + if (fseek(f, 0, SEEK_SET) < 0) { + fclose(f); + js_error(J, "cannot seek in file: '%s'", filename); + } + + s = malloc(n + 1); + if (!s) { + fclose(f); + js_error(J, "cannot allocate storage for file contents: '%s'", filename); + } + + t = fread(s, 1, n, f); + if (t != n) { + free(s); + fclose(f); + js_error(J, "cannot read data from file: '%s'", filename); + } + s[n] = 0; + + js_pushstring(J, s); + free(s); + fclose(f); +} + +static void jsB_readline(js_State *J) +{ + char line[256]; + int n; + if (!fgets(line, sizeof line, stdin)) + js_error(J, "cannot read line from stdin"); + n = strlen(line); + if (n > 0 && line[n-1] == '\n') + line[n-1] = 0; + js_pushstring(J, line); +} + +static void jsB_quit(js_State *J) +{ + exit(js_tonumber(J, 1)); +} + +static const char *require_js = + "function require(name) {\n" + "var cache = require.cache;\n" + "if (name in cache) return cache[name];\n" + "var exports = {};\n" + "cache[name] = exports;\n" + "Function('exports', read(name+'.js'))(exports);\n" + "return exports;\n" + "}\n" + "require.cache = Object.create(null);\n" +; + +/* destructors */ + +static void ffi_gc_fz_document(js_State *J, void *doc) +{ + fz_context *ctx = js_getcontext(J); + fz_drop_document(ctx, doc); +} + +static void ffi_gc_fz_page(js_State *J, void *page) +{ + fz_context *ctx = js_getcontext(J); + fz_drop_page(ctx, page); +} + +static void ffi_gc_fz_colorspace(js_State *J, void *colorspace) +{ + fz_context *ctx = js_getcontext(J); + fz_drop_colorspace(ctx, colorspace); +} + +static void ffi_gc_fz_pixmap(js_State *J, void *pixmap) +{ + fz_context *ctx = js_getcontext(J); + fz_drop_pixmap(ctx, pixmap); +} + +static void ffi_gc_fz_path(js_State *J, void *path) +{ + fz_context *ctx = js_getcontext(J); + fz_drop_path(ctx, path); +} + +static void ffi_gc_fz_text(js_State *J, void *text) +{ + fz_context *ctx = js_getcontext(J); + fz_drop_text(ctx, text); +} + +static void ffi_gc_fz_font(js_State *J, void *font) +{ + fz_context *ctx = js_getcontext(J); + fz_drop_font(ctx, font); +} + +static void ffi_gc_fz_shade(js_State *J, void *shade) +{ + fz_context *ctx = js_getcontext(J); + fz_drop_shade(ctx, shade); +} + +static void ffi_gc_fz_image(js_State *J, void *image) +{ + fz_context *ctx = js_getcontext(J); + fz_drop_image(ctx, image); +} + +static void ffi_gc_fz_device(js_State *J, void *device) +{ + fz_context *ctx = js_getcontext(J); + fz_drop_device(ctx, device); +} + +/* type conversions */ + +struct color { + fz_colorspace *colorspace; + float color[FZ_MAX_COLORS]; + float alpha; +}; + +static fz_matrix ffi_tomatrix(js_State *J, int idx) +{ + fz_matrix matrix; + js_getindex(J, idx, 0); matrix.a = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 1); matrix.b = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 2); matrix.c = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 3); matrix.d = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 4); matrix.e = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 5); matrix.f = js_tonumber(J, -1); js_pop(J, 1); + return matrix; +} + +static void ffi_pushmatrix(js_State *J, fz_matrix matrix) +{ + js_newarray(J); + js_pushnumber(J, matrix.a); js_setindex(J, -2, 0); + js_pushnumber(J, matrix.b); js_setindex(J, -2, 1); + js_pushnumber(J, matrix.c); js_setindex(J, -2, 2); + js_pushnumber(J, matrix.d); js_setindex(J, -2, 3); + js_pushnumber(J, matrix.e); js_setindex(J, -2, 4); + js_pushnumber(J, matrix.f); js_setindex(J, -2, 5); +} + +static fz_rect ffi_torect(js_State *J, int idx) +{ + fz_rect rect; + js_getindex(J, idx, 0); rect.x0 = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 1); rect.y0 = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 2); rect.x1 = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 3); rect.y1 = js_tonumber(J, -1); js_pop(J, 1); + return rect; +} + +static void ffi_pushrect(js_State *J, fz_rect rect) +{ + js_newarray(J); + js_pushnumber(J, rect.x0); js_setindex(J, -2, 0); + js_pushnumber(J, rect.y0); js_setindex(J, -2, 1); + js_pushnumber(J, rect.x1); js_setindex(J, -2, 2); + js_pushnumber(J, rect.y1); js_setindex(J, -2, 3); +} + +static fz_irect ffi_toirect(js_State *J, int idx) +{ + fz_irect irect; + js_getindex(J, idx, 0); irect.x0 = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 1); irect.y0 = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 2); irect.x1 = js_tonumber(J, -1); js_pop(J, 1); + js_getindex(J, idx, 3); irect.y1 = js_tonumber(J, -1); js_pop(J, 1); + return irect; +} + +static void ffi_pusharray(js_State *J, const float *v, int n) +{ + int i; + js_newarray(J); + for (i = 0; i < n; ++i) { + js_pushnumber(J, v[i]); + js_setindex(J, -2, i); + } +} + +static void ffi_pushcolorspace(js_State *J, fz_context *ctx, fz_colorspace *colorspace) +{ + if (colorspace == fz_device_rgb(ctx)) + js_getregistry(J, "DeviceRGB"); + else if (colorspace == fz_device_bgr(ctx)) + js_getregistry(J, "DeviceBGR"); + else if (colorspace == fz_device_gray(ctx)) + js_getregistry(J, "DeviceGray"); + else if (colorspace == fz_device_cmyk(ctx)) + js_getregistry(J, "DeviceCMYK"); + else { + js_getregistry(J, "fz_colorspace"); + js_newuserdata(J, "fz_colorspace", fz_keep_colorspace(ctx, colorspace), ffi_gc_fz_colorspace); + } +} + +static void ffi_pushcolor(js_State *J, fz_context *ctx, fz_colorspace *colorspace, const float *color, float alpha) +{ + if (colorspace) { + ffi_pushcolorspace(J, ctx, colorspace); + ffi_pusharray(J, color, colorspace->n); + } else { + js_pushnull(J); + js_pushnull(J); + } + js_pushnumber(J, alpha); +} + +static struct color ffi_tocolor(js_State *J, int idx) +{ + struct color c; + int n, i; + c.colorspace = js_touserdata(J, idx, "fz_colorspace"); + if (c.colorspace) { + n = c.colorspace->n; + for (i=0; i < n; ++i) { + js_getindex(J, idx + 1, i); + c.color[i] = js_tonumber(J, -1); + js_pop(J, 1); + } + } + c.alpha = js_tonumber(J, idx + 2); + return c; +} + +static const char *string_from_cap(fz_linecap cap) +{ + switch (cap) { + default: + case FZ_LINECAP_BUTT: return "Butt"; + case FZ_LINECAP_ROUND: return "Round"; + case FZ_LINECAP_SQUARE: return "Square"; + case FZ_LINECAP_TRIANGLE: return "Triangle"; + } +} + +static const char *string_from_join(fz_linejoin join) +{ + switch (join) { + default: + case FZ_LINEJOIN_MITER: return "Miter"; + case FZ_LINEJOIN_ROUND: return "Round"; + case FZ_LINEJOIN_BEVEL: return "Bevel"; + case FZ_LINEJOIN_MITER_XPS: return "MiterXPS"; + } +} + +static fz_linecap cap_from_string(const char *str) +{ + if (!strcmp(str, "Round")) return FZ_LINECAP_ROUND; + if (!strcmp(str, "Square")) return FZ_LINECAP_SQUARE; + if (!strcmp(str, "Triangle")) return FZ_LINECAP_TRIANGLE; + return FZ_LINECAP_BUTT; +} + +static fz_linejoin join_from_string(const char *str) +{ + if (!strcmp(str, "Round")) return FZ_LINEJOIN_ROUND; + if (!strcmp(str, "Bevel")) return FZ_LINEJOIN_BEVEL; + if (!strcmp(str, "MiterXPS")) return FZ_LINEJOIN_MITER_XPS; + return FZ_LINEJOIN_MITER; +} + +static void ffi_pushstroke(js_State *J, const fz_stroke_state *stroke) +{ + js_newobject(J); + js_pushliteral(J, string_from_cap(stroke->start_cap)); + js_setproperty(J, -2, "startCap"); + js_pushliteral(J, string_from_cap(stroke->dash_cap)); + js_setproperty(J, -2, "dashCap"); + js_pushliteral(J, string_from_cap(stroke->end_cap)); + js_setproperty(J, -2, "endCap"); + js_pushliteral(J, string_from_join(stroke->linejoin)); + js_setproperty(J, -2, "lineJoin"); + js_pushnumber(J, stroke->linewidth); + js_setproperty(J, -2, "lineWidth"); + js_pushnumber(J, stroke->miterlimit); + js_setproperty(J, -2, "miterLimit"); + js_pushnumber(J, stroke->dash_phase); + js_setproperty(J, -2, "dashPhase"); + ffi_pusharray(J, stroke->dash_list, stroke->dash_len); + js_setproperty(J, -2, "dashes"); +} + +static fz_stroke_state ffi_tostroke(js_State *J, int idx) +{ + fz_stroke_state stroke = fz_default_stroke_state; + if (js_hasproperty(J, idx, "startCap")) { + stroke.start_cap = cap_from_string(js_tostring(J, -1)); + js_pop(J, 1); + } + if (js_hasproperty(J, idx, "dashCap")) { + stroke.dash_cap = cap_from_string(js_tostring(J, -1)); + js_pop(J, 1); + } + if (js_hasproperty(J, idx, "endCap")) { + stroke.end_cap = cap_from_string(js_tostring(J, -1)); + js_pop(J, 1); + } + if (js_hasproperty(J, idx, "lineJoin")) { + stroke.linejoin = join_from_string(js_tostring(J, -1)); + js_pop(J, 1); + } + if (js_hasproperty(J, idx, "lineWidth")) { + stroke.linewidth = js_tonumber(J, -1); + js_pop(J, 1); + } + if (js_hasproperty(J, idx, "miterLimit")) { + stroke.miterlimit = js_tonumber(J, -1); + js_pop(J, 1); + } + if (js_hasproperty(J, idx, "dashPhase")) { + stroke.dash_phase = js_tonumber(J, -1); + js_pop(J, 1); + } + if (js_hasproperty(J, idx, "dashes")) { + int i, n = js_getlength(J, -1); + if (n > nelem(stroke.dash_list)) + n = nelem(stroke.dash_list); + stroke.dash_len = n; + for (i = 0; i < n; ++i) { + js_getindex(J, -1, i); + stroke.dash_list[i] = js_tonumber(J, -1); + js_pop(J, 1); + } + } + return stroke; +} + +static void ffi_pushtext(js_State *J, fz_context *ctx, const fz_text *text) +{ + js_getregistry(J, "fz_text"); + js_newuserdata(J, "fz_text", fz_keep_text(ctx, text), ffi_gc_fz_text); +} + +static void ffi_pushpath(js_State *J, fz_context *ctx, const fz_path *path) +{ + js_getregistry(J, "fz_path"); + js_newuserdata(J, "fz_path", fz_keep_path(ctx, path), ffi_gc_fz_path); +} + +static void ffi_pushfont(js_State *J, fz_context *ctx, fz_font *font) +{ + js_getregistry(J, "fz_font"); + js_newuserdata(J, "fz_font", fz_keep_font(ctx, font), ffi_gc_fz_font); +} + +static void ffi_pushshade(js_State *J, fz_context *ctx, fz_shade *shade) +{ + js_getregistry(J, "fz_shade"); + js_newuserdata(J, "fz_shade", fz_keep_shade(ctx, shade), ffi_gc_fz_shade); +} + +static void ffi_pushimage(js_State *J, fz_context *ctx, fz_image *image) +{ + js_getregistry(J, "fz_image"); + js_newuserdata(J, "fz_image", fz_keep_image(ctx, image), ffi_gc_fz_image); +} + +/* device calling into js from c */ + +typedef struct js_device_s +{ + fz_device super; + js_State *J; +} js_device; + +static void +js_dev_begin_page(fz_context *ctx, fz_device *dev, const fz_rect *rect, const fz_matrix *ctm) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "beginPage")) { + js_copy(J, -2); /* copy the 'this' object */ + ffi_pushrect(J, *rect); + ffi_pushmatrix(J, *ctm); + if (js_pcall(J, 2)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_end_page(fz_context *ctx, fz_device *dev) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "endPage")) { + js_copy(J, -2); /* copy the 'this' object */ + if (js_pcall(J, 0)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, const fz_matrix *ctm, + fz_colorspace *colorspace, const float *color, float alpha) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "fillPath")) { + js_copy(J, -2); + ffi_pushpath(J, ctx, path); + js_pushboolean(J, even_odd); + ffi_pushmatrix(J, *ctm); + ffi_pushcolor(J, ctx, colorspace, color, alpha); + if (js_pcall(J, 6)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_clip_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, const fz_matrix *ctm, + const fz_rect *scissor) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "clipPath")) { + js_copy(J, -2); + ffi_pushpath(J, ctx, path); + js_pushboolean(J, even_odd); + ffi_pushmatrix(J, *ctm); + if (js_pcall(J, 3)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, + const fz_stroke_state *stroke, const fz_matrix *ctm, + fz_colorspace *colorspace, const float *color, float alpha) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "strokePath")) { + js_copy(J, -2); + ffi_pushpath(J, ctx, path); + ffi_pushstroke(J, stroke); + ffi_pushmatrix(J, *ctm); + ffi_pushcolor(J, ctx, colorspace, color, alpha); + if (js_pcall(J, 6)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_clip_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, + const fz_matrix *ctm, const fz_rect *scissor) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "clipStrokePath")) { + js_copy(J, -2); + ffi_pushpath(J, ctx, path); + ffi_pushstroke(J, stroke); + ffi_pushmatrix(J, *ctm); + if (js_pcall(J, 3)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_matrix *ctm, + fz_colorspace *colorspace, const float *color, float alpha) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "fillText")) { + js_copy(J, -2); + ffi_pushtext(J, ctx, text); + ffi_pushmatrix(J, *ctm); + ffi_pushcolor(J, ctx, colorspace, color, alpha); + if (js_pcall(J, 5)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, + const fz_matrix *ctm, fz_colorspace *colorspace, const float *color, float alpha) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "strokeText")) { + js_copy(J, -2); + ffi_pushtext(J, ctx, text); + ffi_pushstroke(J, stroke); + ffi_pushmatrix(J, *ctm); + ffi_pushcolor(J, ctx, colorspace, color, alpha); + if (js_pcall(J, 6)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_matrix *ctm, const fz_rect *scissor) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "clipText")) { + js_copy(J, -2); + ffi_pushtext(J, ctx, text); + ffi_pushmatrix(J, *ctm); + if (js_pcall(J, 2)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_clip_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, + const fz_matrix *ctm, const fz_rect *scissor) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "clipStrokeText")) { + js_copy(J, -2); + ffi_pushtext(J, ctx, text); + ffi_pushstroke(J, stroke); + ffi_pushmatrix(J, *ctm); + if (js_pcall(J, 3)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_matrix *ctm) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "ignoreText")) { + js_copy(J, -2); + ffi_pushtext(J, ctx, text); + ffi_pushmatrix(J, *ctm); + if (js_pcall(J, 2)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, const fz_matrix *ctm, float alpha) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "fillShade")) { + js_copy(J, -2); + ffi_pushshade(J, ctx, shade); + ffi_pushmatrix(J, *ctm); + js_pushnumber(J, alpha); + if (js_pcall(J, 3)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, const fz_matrix *ctm, float alpha) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "fillImage")) { + js_copy(J, -2); + ffi_pushimage(J, ctx, image); + ffi_pushmatrix(J, *ctm); + js_pushnumber(J, alpha); + if (js_pcall(J, 3)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, const fz_matrix *ctm, + fz_colorspace *colorspace, const float *color, float alpha) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "fillImageMask")) { + js_copy(J, -2); + ffi_pushimage(J, ctx, image); + ffi_pushmatrix(J, *ctm); + ffi_pushcolor(J, ctx, colorspace, color, alpha); + if (js_pcall(J, 5)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, const fz_matrix *ctm, const fz_rect *scissor) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "clipImageMask")) { + js_copy(J, -2); + ffi_pushimage(J, ctx, image); + ffi_pushmatrix(J, *ctm); + if (js_pcall(J, 2)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_pop_clip(fz_context *ctx, fz_device *dev) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "popClip")) { + js_copy(J, -2); + if (js_pcall(J, 0)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_begin_mask(fz_context *ctx, fz_device *dev, const fz_rect *bbox, int luminosity, + fz_colorspace *colorspace, const float *color) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "beginMask")) { + js_copy(J, -2); + ffi_pushrect(J, *bbox); + js_pushboolean(J, luminosity); + if (colorspace) { + ffi_pushcolorspace(J, ctx, colorspace); + ffi_pusharray(J, color, colorspace->n); + } else { + js_pushnull(J); + js_pushnull(J); + } + if (js_pcall(J, 4)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_end_mask(fz_context *ctx, fz_device *dev) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "endMask")) { + js_copy(J, -2); + if (js_pcall(J, 0)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_begin_group(fz_context *ctx, fz_device *dev, const fz_rect *bbox, + int isolated, int knockout, int blendmode, float alpha) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "beginGroup")) { + js_copy(J, -2); + ffi_pushrect(J, *bbox); + js_pushboolean(J, isolated); + js_pushboolean(J, knockout); + js_pushliteral(J, fz_blendmode_name(blendmode)); + js_pushnumber(J, alpha); + if (js_pcall(J, 5)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void +js_dev_end_group(fz_context *ctx, fz_device *dev) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "endGroup")) { + js_copy(J, -2); + if (js_pcall(J, 0)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static int +js_dev_begin_tile(fz_context *ctx, fz_device *dev, const fz_rect *area, const fz_rect *view, + float xstep, float ystep, const fz_matrix *ctm, int id) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "beginTile")) { + int n; + js_copy(J, -2); + ffi_pushrect(J, *area); + ffi_pushrect(J, *view); + js_pushnumber(J, xstep); + js_pushnumber(J, ystep); + ffi_pushmatrix(J, *ctm); + js_pushnumber(J, id); + if (js_pcall(J, 6)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + n = js_tointeger(J, -1); + js_pop(J, 1); + return n; + } + return 0; +} + +static void +js_dev_end_tile(fz_context *ctx, fz_device *dev) +{ + js_State *J = ((js_device*)dev)->J; + if (js_hasproperty(J, -1, "endTile")) { + js_copy(J, -2); + if (js_pcall(J, 0)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static fz_device *new_js_device(fz_context *ctx, js_State *J) +{ + js_device *dev = fz_new_device(ctx, sizeof *dev); + + dev->super.begin_page = js_dev_begin_page; + dev->super.end_page = js_dev_end_page; + + dev->super.fill_path = js_dev_fill_path; + dev->super.stroke_path = js_dev_stroke_path; + dev->super.clip_path = js_dev_clip_path; + dev->super.clip_stroke_path = js_dev_clip_stroke_path; + + dev->super.fill_text = js_dev_fill_text; + dev->super.stroke_text = js_dev_stroke_text; + dev->super.clip_text = js_dev_clip_text; + dev->super.clip_stroke_text = js_dev_clip_stroke_text; + dev->super.ignore_text = js_dev_ignore_text; + + dev->super.fill_shade = js_dev_fill_shade; + dev->super.fill_image = js_dev_fill_image; + dev->super.fill_image_mask = js_dev_fill_image_mask; + dev->super.clip_image_mask = js_dev_clip_image_mask; + + dev->super.pop_clip = js_dev_pop_clip; + + dev->super.begin_mask = js_dev_begin_mask; + dev->super.end_mask = js_dev_end_mask; + dev->super.begin_group = js_dev_begin_group; + dev->super.end_group = js_dev_end_group; + + dev->super.begin_tile = js_dev_begin_tile; + dev->super.end_tile = js_dev_end_tile; + + dev->J = J; + return (fz_device*)dev; +} + +/* device calling into c from js */ + +static void ffi_Device_beginPage(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_rect rect = ffi_torect(J, 1); + fz_matrix ctm = ffi_tomatrix(J, 2); + fz_try(ctx) + fz_begin_page(ctx, dev, &rect, &ctm); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_endPage(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_try(ctx) + fz_end_page(ctx, dev); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_fillPath(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_path *path = js_touserdata(J, 1, "fz_path"); + int even_odd = js_toboolean(J, 2); + fz_matrix ctm = ffi_tomatrix(J, 3); + struct color c = ffi_tocolor(J, 4); + fz_try(ctx) + fz_fill_path(ctx, dev, path, even_odd, &ctm, c.colorspace, c.color, c.alpha); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_strokePath(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_path *path = js_touserdata(J, 1, "fz_path"); + fz_stroke_state stroke = ffi_tostroke(J, 2); + fz_matrix ctm = ffi_tomatrix(J, 3); + struct color c = ffi_tocolor(J, 4); + fz_try(ctx) + fz_stroke_path(ctx, dev, path, &stroke, &ctm, c.colorspace, c.color, c.alpha); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_clipPath(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_path *path = js_touserdata(J, 1, "fz_path"); + int even_odd = js_toboolean(J, 2); + fz_matrix ctm = ffi_tomatrix(J, 3); + fz_try(ctx) + fz_clip_path(ctx, dev, path, even_odd, &ctm, NULL); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_clipStrokePath(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_path *path = js_touserdata(J, 1, "fz_path"); + fz_stroke_state stroke = ffi_tostroke(J, 2); + fz_matrix ctm = ffi_tomatrix(J, 3); + fz_try(ctx) + fz_clip_stroke_path(ctx, dev, path, &stroke, &ctm, NULL); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_fillText(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_text *text = js_touserdata(J, 1, "fz_text"); + fz_matrix ctm = ffi_tomatrix(J, 2); + struct color c = ffi_tocolor(J, 3); + fz_try(ctx) + fz_fill_text(ctx, dev, text, &ctm, c.colorspace, c.color, c.alpha); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_strokeText(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_text *text = js_touserdata(J, 1, "fz_text"); + fz_stroke_state stroke = ffi_tostroke(J, 2); + fz_matrix ctm = ffi_tomatrix(J, 3); + struct color c = ffi_tocolor(J, 4); + fz_try(ctx) + fz_stroke_text(ctx, dev, text, &stroke, &ctm, c.colorspace, c.color, c.alpha); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_clipText(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_text *text = js_touserdata(J, 1, "fz_text"); + fz_matrix ctm = ffi_tomatrix(J, 2); + fz_try(ctx) + fz_clip_text(ctx, dev, text, &ctm, NULL); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_clipStrokeText(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_text *text = js_touserdata(J, 1, "fz_text"); + fz_stroke_state stroke = ffi_tostroke(J, 2); + fz_matrix ctm = ffi_tomatrix(J, 3); + fz_try(ctx) + fz_clip_stroke_text(ctx, dev, text, &stroke, &ctm, NULL); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_ignoreText(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_text *text = js_touserdata(J, 1, "fz_text"); + fz_matrix ctm = ffi_tomatrix(J, 2); + fz_try(ctx) + fz_ignore_text(ctx, dev, text, &ctm); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_fillShade(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_shade *shade = js_touserdata(J, 1, "fz_shade"); + fz_matrix ctm = ffi_tomatrix(J, 2); + float alpha = js_tonumber(J, 3); + fz_try(ctx) + fz_fill_shade(ctx, dev, shade, &ctm, alpha); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_fillImage(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_image *image = js_touserdata(J, 1, "fz_image"); + fz_matrix ctm = ffi_tomatrix(J, 2); + float alpha = js_tonumber(J, 3); + fz_try(ctx) + fz_fill_image(ctx, dev, image, &ctm, alpha); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_fillImageMask(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_image *image = js_touserdata(J, 1, "fz_image"); + fz_matrix ctm = ffi_tomatrix(J, 2); + struct color c = ffi_tocolor(J, 3); + fz_try(ctx) + fz_fill_image_mask(ctx, dev, image, &ctm, c.colorspace, c.color, c.alpha); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_clipImageMask(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_image *image = js_touserdata(J, 1, "fz_image"); + fz_matrix ctm = ffi_tomatrix(J, 2); + fz_try(ctx) + fz_clip_image_mask(ctx, dev, image, &ctm, NULL); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_popClip(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_try(ctx) + fz_pop_clip(ctx, dev); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_beginMask(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_rect area = ffi_torect(J, 1); + int luminosity = js_toboolean(J, 2); + struct color c = ffi_tocolor(J, 3); + fz_try(ctx) + fz_begin_mask(ctx, dev, &area, luminosity, c.colorspace, c.color); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_endMask(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_try(ctx) + fz_end_mask(ctx, dev); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_beginGroup(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_rect area = ffi_torect(J, 1); + int isolated = js_toboolean(J, 2); + int knockout = js_toboolean(J, 3); + int blendmode = fz_lookup_blendmode(js_tostring(J, 4)); + float alpha = js_tonumber(J, 5); + fz_try(ctx) + fz_begin_group(ctx, dev, &area, isolated, knockout, blendmode, alpha); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_endGroup(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_try(ctx) + fz_end_group(ctx, dev); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Device_beginTile(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_rect area = ffi_torect(J, 1); + fz_rect view = ffi_torect(J, 2); + float xstep = js_tonumber(J, 3); + float ystep = js_tonumber(J, 4); + fz_matrix ctm = ffi_tomatrix(J, 5); + int id = js_tonumber(J, 6); + int n = 0; + fz_try(ctx) + n = fz_begin_tile_id(ctx, dev, &area, &view, xstep, ystep, &ctm, id); + fz_catch(ctx) + rethrow(J); + js_pushnumber(J, n); +} + +static void ffi_Device_endTile(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_device *dev = js_touserdata(J, 0, "fz_device"); + fz_try(ctx) + fz_end_tile(ctx, dev); + fz_catch(ctx) + rethrow(J); +} + +/* mupdf module */ + +static void ffi_new_Document(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + const char *filename = js_tostring(J, 1); + fz_document *doc; + + fz_try(ctx) + doc = fz_open_document(ctx, filename); + fz_catch(ctx) + rethrow(J); + + js_getregistry(J, "fz_document"); + js_newuserdata(J, "fz_document", doc, ffi_gc_fz_document); +} + +static void ffi_Document_countPages(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_document *doc = js_touserdata(J, 0, "fz_document"); + int count; + + fz_try(ctx) + count = fz_count_pages(ctx, doc); + fz_catch(ctx) + rethrow(J); + + js_pushnumber(J, count); +} + +static void ffi_Document_loadPage(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_document *doc = js_touserdata(J, 0, "fz_document"); + int number = js_tointeger(J, 1); + fz_page *page; + + fz_try(ctx) + page = fz_load_page(ctx, doc, number); + fz_catch(ctx) + rethrow(J); + + js_getregistry(J, "fz_page"); + js_newuserdata(J, "fz_page", page, ffi_gc_fz_page); +} + +static void ffi_Page_toPixmap(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_page *page = js_touserdata(J, 0, "fz_page"); + fz_matrix ctm = ffi_tomatrix(J, 1); + fz_colorspace *colorspace = js_touserdata(J, 2, "fz_colorspace"); + fz_pixmap *pixmap; + + fz_try(ctx) + pixmap = fz_new_pixmap_from_page(ctx, page, &ctm, colorspace); + fz_catch(ctx) + rethrow(J); + + js_getregistry(J, "fz_pixmap"); + js_newuserdata(J, "fz_pixmap", pixmap, ffi_gc_fz_pixmap); +} + +static void ffi_Page_search(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_page *page = js_touserdata(J, 0, "fz_page"); + const char *needle = js_tostring(J, 1); + fz_rect hits[256]; + int i, n; + + fz_try(ctx) + n = fz_search_page(ctx, page, needle, hits, nelem(hits)); + fz_catch(ctx) + rethrow(J); + + js_newarray(J); + for (i = 0; i < n; ++i) { + ffi_pushrect(J, hits[i]); + js_setindex(J, -2, i); + } +} + +static void ffi_Page_bound(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_page *page = js_touserdata(J, 0, "fz_page"); + fz_rect bounds; + + fz_try(ctx) + fz_bound_page(ctx, page, &bounds); + fz_catch(ctx) + rethrow(J); + + ffi_pushrect(J, bounds); +} + +static void ffi_Page_run(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_page *page = js_touserdata(J, 0, "fz_page"); + fz_device *device = NULL; + fz_matrix ctm = ffi_tomatrix(J, 2); + + if (js_isuserdata(J, 1, "fz_device")) { + device = js_touserdata(J, 1, "fz_device"); + fz_try(ctx) + fz_run_page(ctx, page, device, &ctm, NULL); + fz_catch(ctx) + rethrow(J); + } else { + device = new_js_device(ctx, J); + js_copy(J, 1); /* put the js device on the top so the callbacks know where to get it */ + fz_try(ctx) + fz_run_page(ctx, page, device, &ctm, NULL); + fz_always(ctx) + fz_drop_device(ctx, device); + fz_catch(ctx) + rethrow(J); + } +} + +static void ffi_new_Pixmap(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_colorspace *colorspace = js_touserdata(J, 1, "fz_colorspace"); + fz_irect bounds = ffi_toirect(J, 2); + fz_pixmap *pixmap; + + fz_try(ctx) + pixmap = fz_new_pixmap_with_bbox(ctx, colorspace, &bounds); + fz_catch(ctx) + rethrow(J); + + js_getregistry(J, "fz_pixmap"); + js_newuserdata(J, "fz_pixmap", pixmap, ffi_gc_fz_pixmap); +} + +static void ffi_Pixmap_saveAsPNG(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_pixmap *pixmap = js_touserdata(J, 0, "fz_pixmap"); + const char *filename = js_tostring(J, 1); + int savealpha = js_toboolean(J, 2); + + fz_try(ctx) + fz_save_pixmap_as_png(ctx, pixmap, filename, savealpha); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Pixmap_bound(js_State *J) +{ + fz_pixmap *pixmap = js_touserdata(J, 0, "fz_pixmap"); + fz_rect bounds; + + // fz_irect and fz_pixmap_bbox instead + bounds.x0 = pixmap->x; + bounds.y0 = pixmap->y; + bounds.x1 = pixmap->x + pixmap->w; + bounds.y1 = pixmap->y + pixmap->h; + + ffi_pushrect(J, bounds); +} + +static void ffi_Pixmap_clear(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_pixmap *pixmap = js_touserdata(J, 0, "fz_pixmap"); + if (js_isdefined(J, 1)) { + int value = js_tonumber(J, 1); + fz_try(ctx) + fz_clear_pixmap_with_value(ctx, pixmap, value); + fz_catch(ctx) + rethrow(J); + } else { + fz_try(ctx) + fz_clear_pixmap(ctx, pixmap); + fz_catch(ctx) + rethrow(J); + } +} + +static void ffi_ColorSpace_toString(js_State *J) +{ + fz_colorspace *colorspace = js_touserdata(J, 0, "fz_colorspace"); + js_pushstring(J, colorspace->name); +} + +static void ffi_new_Image(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + const char *name = js_tostring(J, 1); + fz_image *image = NULL; + + fz_try(ctx) + image = fz_new_image_from_file(ctx, name); + fz_catch(ctx) + rethrow(J); + + js_getregistry(J, "fz_image"); + js_newuserdata(J, "fz_image", image, ffi_gc_fz_image); +} + +static void ffi_new_Font(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + const char *name = js_tostring(J, 1); + int index = js_isnumber(J, 2) ? js_tonumber(J, 2) : 0; + unsigned char *data; + unsigned int size; + fz_font *font = NULL; + + fz_try(ctx) { + data = fz_lookup_base14_font(ctx, name, &size); + if (data) + font = fz_new_font_from_memory(ctx, name, data, size, index, 0); + else + font = fz_new_font_from_file(ctx, name, name, index, 0); + } + fz_catch(ctx) + rethrow(J); + + js_getregistry(J, "fz_font"); + js_newuserdata(J, "fz_font", font, ffi_gc_fz_font); +} + +static void ffi_Font_encodeCharacter(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_font *font = js_touserdata(J, 0, "fz_font"); + int unicode = js_tonumber(J, 1); + int glyph = 0; + fz_try(ctx) + glyph = fz_encode_character(ctx, font, unicode); + fz_catch(ctx) + rethrow(J); + js_pushnumber(J, glyph); +} + +static void ffi_Font_advanceGlyph(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_font *font = js_touserdata(J, 0, "fz_font"); + int glyph = js_tonumber(J, 1); + int wmode = js_isdefined(J, 2) ? js_toboolean(J, 2) : 0; + + float advance = 0; + fz_try(ctx) + advance = fz_advance_glyph(ctx, font, glyph, wmode); + fz_catch(ctx) + rethrow(J); + js_pushnumber(J, advance); +} + +static void ffi_new_Text(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_text *text; + + fz_try(ctx) + text = fz_new_text(ctx); + fz_catch(ctx) + rethrow(J); + + js_getregistry(J, "fz_text"); + js_newuserdata(J, "fz_text", text, ffi_gc_fz_text); +} + +static void ffi_Text_walk(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_text *text = js_touserdata(J, 0, "fz_text"); + fz_text_span *span; + fz_matrix trm; + int i; + + js_getproperty(J, 1, "showGlyph"); + for (span = text->head; span; span = span->next) { + ffi_pushfont(J, ctx, span->font); + trm = span->trm; + for (i = 0; i < span->len; ++i) { + trm.e = span->items[i].x; + trm.f = span->items[i].y; + js_copy(J, -2); /* showGlyph function */ + js_copy(J, 1); /* object for this binding */ + js_copy(J, -3); /* font */ + ffi_pushmatrix(J, trm); + js_pushnumber(J, span->items[i].gid); + js_pushnumber(J, span->items[i].ucs); + js_call(J, 4); + js_pop(J, 1); + } + js_pop(J, 1); /* pop font object */ + } + js_pop(J, 1); /* pop showGlyph function */ +} + +static void ffi_Text_showGlyph(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_text *text = js_touserdata(J, 0, "fz_text"); + fz_font *font = js_touserdata(J, 1, "fz_font"); + fz_matrix trm = ffi_tomatrix(J, 2); + int glyph = js_tointeger(J, 3); + int unicode = js_tointeger(J, 4); + int wmode = js_isdefined(J, 5) ? js_toboolean(J, 5) : 0; + + fz_try(ctx) + fz_show_glyph(ctx, text, font, &trm, glyph, unicode, wmode); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Text_showString(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_text *text = js_touserdata(J, 0, "fz_text"); + fz_font *font = js_touserdata(J, 1, "fz_font"); + fz_matrix trm = ffi_tomatrix(J, 2); + const char *s = js_tostring(J, 3); + int wmode = js_isdefined(J, 4) ? js_toboolean(J, 4) : 0; + + fz_try(ctx) + fz_show_string(ctx, text, font, &trm, s, wmode); + fz_catch(ctx) + rethrow(J); + + /* update matrix with new pen position */ + js_pushnumber(J, trm.e); + js_setindex(J, 2, 4); + js_pushnumber(J, trm.f); + js_setindex(J, 2, 5); +} + +static void ffi_new_Path(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_path *path; + + fz_try(ctx) + path = fz_new_path(ctx); + fz_catch(ctx) + rethrow(J); + + js_getregistry(J, "fz_path"); + js_newuserdata(J, "fz_path", path, ffi_gc_fz_path); +} + +static void ffi_Path_walk_moveTo(fz_context *ctx, void *arg, float x, float y) +{ + js_State *J = arg; + if (js_hasproperty(J, 1, "moveTo")) { + js_copy(J, 1); + js_pushnumber(J, x); + js_pushnumber(J, y); + if (js_pcall(J, 2)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void ffi_Path_walk_lineTo(fz_context *ctx, void *arg, float x, float y) +{ + js_State *J = arg; + if (js_hasproperty(J, 1, "lineTo")) { + js_copy(J, 1); + js_pushnumber(J, x); + js_pushnumber(J, y); + if (js_pcall(J, 2)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void ffi_Path_walk_curveTo(fz_context *ctx, void *arg, + float x1, float y1, float x2, float y2, float x3, float y3) +{ + js_State *J = arg; + if (js_hasproperty(J, 1, "curveTo")) { + js_copy(J, 1); + js_pushnumber(J, x1); + js_pushnumber(J, y1); + js_pushnumber(J, x2); + js_pushnumber(J, y2); + js_pushnumber(J, x3); + js_pushnumber(J, y3); + if (js_pcall(J, 6)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void ffi_Path_walk_closePath(fz_context *ctx, void *arg) +{ + js_State *J = arg; + if (js_hasproperty(J, 1, "closePath")) { + js_copy(J, 1); + if (js_pcall(J, 0)) + fz_warn(ctx, "%s", js_tostring(J, -1)); + js_pop(J, 1); + } +} + +static void ffi_Path_walk(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_path *path = js_touserdata(J, 0, "fz_path"); + fz_path_walker walker = { + ffi_Path_walk_moveTo, + ffi_Path_walk_lineTo, + ffi_Path_walk_curveTo, + ffi_Path_walk_closePath, + }; + + fz_walk_path(ctx, path, &walker, J); +} + +static void ffi_Path_moveTo(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_path *path = js_touserdata(J, 0, "fz_path"); + float x = js_tonumber(J, 1); + float y = js_tonumber(J, 2); + + fz_try(ctx) + fz_moveto(ctx, path, x, y); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Path_lineTo(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_path *path = js_touserdata(J, 0, "fz_path"); + float x = js_tonumber(J, 1); + float y = js_tonumber(J, 2); + + fz_try(ctx) + fz_lineto(ctx, path, x, y); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Path_curveTo(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_path *path = js_touserdata(J, 0, "fz_path"); + float x1 = js_tonumber(J, 1); + float y1 = js_tonumber(J, 2); + float x2 = js_tonumber(J, 3); + float y2 = js_tonumber(J, 4); + float x3 = js_tonumber(J, 5); + float y3 = js_tonumber(J, 6); + + fz_try(ctx) + fz_curveto(ctx, path, x1, y1, x2, y2, x3, y3); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Path_closePath(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_path *path = js_touserdata(J, 0, "fz_path"); + + fz_try(ctx) + fz_closepath(ctx, path); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_Path_rect(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_path *path = js_touserdata(J, 0, "fz_path"); + float x1 = js_tonumber(J, 1); + float y1 = js_tonumber(J, 2); + float x2 = js_tonumber(J, 3); + float y2 = js_tonumber(J, 4); + + fz_try(ctx) + fz_rectto(ctx, path, x1, y1, x2, y2); + fz_catch(ctx) + rethrow(J); +} + +static void ffi_new_DrawDevice(js_State *J) +{ + fz_context *ctx = js_getcontext(J); + fz_pixmap *pixmap = js_touserdata(J, 1, "fz_pixmap"); + fz_device *device; + + fz_try(ctx) + device = fz_new_draw_device(ctx, pixmap); + fz_catch(ctx) + rethrow(J); + + js_getregistry(J, "fz_device"); + js_newuserdata(J, "fz_device", device, ffi_gc_fz_device); +} + +int murun_main(int argc, char **argv) +{ + fz_context *ctx; + js_State *J; + int i; + + ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED); + fz_register_document_handlers(ctx); + + J = js_newstate(alloc, ctx, JS_STRICT); + js_setcontext(J, ctx); + + /* standard command line javascript functions */ + + js_newcfunction(J, jsB_gc, "gc", 0); + js_setglobal(J, "gc"); + + js_newcfunction(J, jsB_load, "load", 1); + js_setglobal(J, "load"); + + js_newcfunction(J, jsB_print, "print", 1); + js_setglobal(J, "print"); + + js_newcfunction(J, jsB_write, "write", 0); + js_setglobal(J, "write"); + + js_newcfunction(J, jsB_read, "read", 1); + js_setglobal(J, "read"); + + js_newcfunction(J, jsB_readline, "readline", 0); + js_setglobal(J, "readline"); + + js_newcfunction(J, jsB_quit, "quit", 1); + js_setglobal(J, "quit"); + + js_dostring(J, require_js); + + /* mupdf module */ + + js_newobject(J); + { + jsB_propfun(J, "Document.countPages", ffi_Document_countPages, 0); + jsB_propfun(J, "Document.loadPage", ffi_Document_loadPage, 1); + // Document.layout + // Document.needsPassword + // Document.authenticatePassword + // Document.lookupMetadata + + // Document.hasPermission + jsB_propenum(J, "PERMISSION_PRINT", FZ_PERMISSION_PRINT); + jsB_propenum(J, "PERMISSION_COPY", FZ_PERMISSION_COPY); + jsB_propenum(J, "PERMISSION_EDIT", FZ_PERMISSION_EDIT); + jsB_propenum(J, "PERMISSION_ANNOTATE", FZ_PERMISSION_ANNOTATE); + } + js_setregistry(J, "fz_document"); + + js_newobject(J); + { + jsB_propfun(J, "Page.bound", ffi_Page_bound, 0); + jsB_propfun(J, "Page.run", ffi_Page_run, 2); + jsB_propfun(J, "Page.toPixmap", ffi_Page_toPixmap, 1); + jsB_propfun(J, "Page.search", ffi_Page_search, 0); + } + js_setregistry(J, "fz_page"); + + js_newobject(J); + { + jsB_propfun(J, "Device.beginPage", ffi_Device_beginPage, 2); + jsB_propfun(J, "Device.endPage", ffi_Device_endPage, 0); + + jsB_propfun(J, "Device.fillPath", ffi_Device_fillPath, 6); + jsB_propfun(J, "Device.strokePath", ffi_Device_strokePath, 6); + jsB_propfun(J, "Device.clipPath", ffi_Device_clipPath, 3); + jsB_propfun(J, "Device.clipStrokePath", ffi_Device_clipStrokePath, 3); + + jsB_propfun(J, "Device.fillText", ffi_Device_fillText, 5); + jsB_propfun(J, "Device.strokeText", ffi_Device_strokeText, 6); + jsB_propfun(J, "Device.clipText", ffi_Device_clipText, 2); + jsB_propfun(J, "Device.clipStrokeText", ffi_Device_clipStrokeText, 3); + jsB_propfun(J, "Device.ignoreText", ffi_Device_ignoreText, 2); + + jsB_propfun(J, "Device.fillShade", ffi_Device_fillShade, 3); + jsB_propfun(J, "Device.fillImage", ffi_Device_fillImage, 3); + jsB_propfun(J, "Device.fillImageMask", ffi_Device_fillImageMask, 5); + jsB_propfun(J, "Device.clipImageMask", ffi_Device_clipImageMask, 2); + + jsB_propfun(J, "Device.popClip", ffi_Device_popClip, 0); + + jsB_propfun(J, "Device.beginMask", ffi_Device_beginMask, 5); /* should be 4 */ + jsB_propfun(J, "Device.endMask", ffi_Device_endMask, 0); + jsB_propfun(J, "Device.beginGroup", ffi_Device_beginGroup, 5); + jsB_propfun(J, "Device.endGroup", ffi_Device_endGroup, 0); + jsB_propfun(J, "Device.beginTile", ffi_Device_beginTile, 6); + jsB_propfun(J, "Device.endTile", ffi_Device_endTile, 0); + } + js_setregistry(J, "fz_device"); + + js_newobject(J); + { + jsB_propfun(J, "ColorSpace.toString", ffi_ColorSpace_toString, 0); + } + js_setregistry(J, "fz_colorspace"); + { + js_getregistry(J, "fz_colorspace"); + js_newuserdata(J, "fz_colorspace", fz_device_gray(ctx), ffi_gc_fz_colorspace); + js_setregistry(J, "DeviceGray"); + + js_getregistry(J, "fz_colorspace"); + js_newuserdata(J, "fz_colorspace", fz_device_rgb(ctx), ffi_gc_fz_colorspace); + js_setregistry(J, "DeviceRGB"); + + js_getregistry(J, "fz_colorspace"); + js_newuserdata(J, "fz_colorspace", fz_device_bgr(ctx), ffi_gc_fz_colorspace); + js_setregistry(J, "DeviceBGR"); + + js_getregistry(J, "fz_colorspace"); + js_newuserdata(J, "fz_colorspace", fz_device_cmyk(ctx), ffi_gc_fz_colorspace); + js_setregistry(J, "DeviceCMYK"); + } + + js_newobject(J); + { + } + js_setregistry(J, "fz_shade"); + + js_newobject(J); + { + } + js_setregistry(J, "fz_image"); + + js_newobject(J); + { + jsB_propfun(J, "Font.encodeCharacter", ffi_Font_encodeCharacter, 1); + jsB_propfun(J, "Font.advanceGlyph", ffi_Font_advanceGlyph, 2); + } + js_setregistry(J, "fz_font"); + + js_newobject(J); + { + jsB_propfun(J, "Text.walk", ffi_Text_walk, 1); + jsB_propfun(J, "Text.showGlyph", ffi_Text_showGlyph, 5); + jsB_propfun(J, "Text.showString", ffi_Text_showString, 4); + } + js_setregistry(J, "fz_text"); + + js_newobject(J); + { + jsB_propfun(J, "Path.walk", ffi_Path_walk, 1); + jsB_propfun(J, "Path.moveTo", ffi_Path_moveTo, 2); + jsB_propfun(J, "Path.lineTo", ffi_Path_lineTo, 2); + jsB_propfun(J, "Path.curveTo", ffi_Path_curveTo, 6); + jsB_propfun(J, "Path.closePath", ffi_Path_closePath, 0); + jsB_propfun(J, "Path.rect", ffi_Path_rect, 4); + } + js_setregistry(J, "fz_path"); + + js_newobject(J); + { + jsB_propfun(J, "Pixmap.bound", ffi_Pixmap_bound, 0); + jsB_propfun(J, "Pixmap.clear", ffi_Pixmap_clear, 1); + // Pixmap.resolution() + // Pixmap.colorspace() + // Pixmap.components() + // Pixmap.samples() + // Pixmap.invert + // Pixmap.tint + // Pixmap.gamma + // Pixmap.scale() + + jsB_propfun(J, "Pixmap.saveAsPNG", ffi_Pixmap_saveAsPNG, 2); + // Pixmap.saveAsPNM, PAM, TGA, PWG, PCL + + // Pixmap.halftone() -> Bitmap + // Pixmap.md5() + } + js_setregistry(J, "fz_pixmap"); + + js_newobject(J); + { + jsB_propcon(J, "fz_document", "Document", ffi_new_Document, 1); + jsB_propcon(J, "fz_pixmap", "Pixmap", ffi_new_Pixmap, 2); + jsB_propcon(J, "fz_image", "Image", ffi_new_Image, 1); + jsB_propcon(J, "fz_font", "Font", ffi_new_Font, 2); + jsB_propcon(J, "fz_text", "Text", ffi_new_Text, 0); + jsB_propcon(J, "fz_path", "Path", ffi_new_Path, 0); + jsB_propcon(J, "fz_device", "DrawDevice", ffi_new_DrawDevice, 1); + + js_getregistry(J, "DeviceGray"); + js_defproperty(J, -2, "DeviceGray", JS_DONTENUM | JS_READONLY | JS_DONTCONF); + + js_getregistry(J, "DeviceRGB"); + js_defproperty(J, -2, "DeviceRGB", JS_DONTENUM | JS_READONLY | JS_DONTCONF); + + js_getregistry(J, "DeviceBGR"); + js_defproperty(J, -2, "DeviceBGR", JS_DONTENUM | JS_READONLY | JS_DONTCONF); + + js_getregistry(J, "DeviceCMYK"); + js_defproperty(J, -2, "DeviceCMYK", JS_DONTENUM | JS_READONLY | JS_DONTCONF); + + // Set user CSS + } + js_setglobal(J, "mupdf"); + + /* re-implement matrix math in javascript */ + js_dostring(J, "mupdf.Identity = [1,0,0,1,0,0];"); + js_dostring(J, "mupdf.Scale = function(sx,sy) { return [sx,0,0,sy,0,0]; };"); + js_dostring(J, "mupdf.Translate = function(tx,ty) { return [1,0,0,1,tx,ty]; };"); + js_dostring(J, "mupdf.Concat = function(a,b) { return [" + "a[0] * b[0] + a[1] * b[2]," + "a[0] * b[1] + a[1] * b[3]," + "a[2] * b[0] + a[3] * b[2]," + "a[2] * b[1] + a[3] * b[3]," + "a[4] * b[0] + a[5] * b[2] + b[4]," + "a[4] * b[1] + a[5] * b[3] + b[5]];}"); + + if (argc > 1) { + for (i = 1; i < argc; ++i) + if (js_dofile(J, argv[i])) + return 1; + } else { + char line[256]; + fputs(PS1, stdout); + while (fgets(line, sizeof line, stdin)) { + js_dostring(J, line); + fputs(PS1, stdout); + } + putchar('\n'); + } + + js_freestate(J); + fz_drop_context(ctx); + return 0; +} diff --git a/source/tools/mutool.c b/source/tools/mutool.c index c662ffef..6d2cfa40 100644 --- a/source/tools/mutool.c +++ b/source/tools/mutool.c @@ -9,6 +9,7 @@ #endif int mudraw_main(int argc, char *argv[]); +int murun_main(int argc, char *argv[]); int pdfclean_main(int argc, char *argv[]); int pdfextract_main(int argc, char *argv[]); int pdfinfo_main(int argc, char *argv[]); @@ -23,6 +24,7 @@ static struct { char *desc; } tools[] = { { mudraw_main, "draw", "convert document" }, + { murun_main, "run", "run javascript" }, { pdfclean_main, "clean", "rewrite pdf file" }, { pdfextract_main, "extract", "extract font and image resources" }, { pdfinfo_main, "info", "show information about pdf resources" }, |