Mercurial > hgrepos > Python2 > PyMuPDF
diff src_classic/helper-devices.i @ 1:1d09e1dec1d9 upstream
ADD: PyMuPDF v1.26.4: the original sdist.
It does not yet contain MuPDF. This normally will be downloaded when
building PyMuPDF.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:37:51 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src_classic/helper-devices.i Mon Sep 15 11:37:51 2025 +0200 @@ -0,0 +1,1049 @@ +%{ +/* +# ------------------------------------------------------------------------ +# Copyright 2020-2022, Harald Lieder, mailto:harald.lieder@outlook.com +# License: GNU AFFERO GPL 3.0, https://www.gnu.org/licenses/agpl-3.0.html +# +# Part of "PyMuPDF", a Python binding for "MuPDF" (http://mupdf.com), a +# lightweight PDF, XPS, and E-book viewer, renderer and toolkit which is +# maintained and developed by Artifex Software, Inc. https://artifex.com. +# ------------------------------------------------------------------------ +*/ +typedef struct +{ + fz_device super; + PyObject *out; + size_t seqno; + long depth; + int clips; + PyObject *method; +} jm_lineart_device; + +static PyObject *dev_pathdict = NULL; +static PyObject *scissors = NULL; +static float dev_linewidth = 0; // border width if present +static fz_matrix trace_device_ptm; // page transformation matrix +static fz_matrix trace_device_ctm; // trace device matrix +static fz_matrix trace_device_rot; +static fz_point dev_lastpoint = {0, 0}; +static fz_point dev_firstpoint = {0, 0}; +static int dev_havemove = 0; +static fz_rect dev_pathrect; +static float dev_pathfactor = 0; +static int dev_linecount = 0; +static char *layer_name=NULL; // optional content name +static int path_type = 0; // one of the following values: +#define FILL_PATH 1 +#define STROKE_PATH 2 +#define CLIP_PATH 3 +#define CLIP_STROKE_PATH 4 + +static void trace_device_reset() +{ + Py_CLEAR(dev_pathdict); + Py_CLEAR(scissors); + layer_name = NULL; + dev_linewidth = 0; + trace_device_ptm = fz_identity; + trace_device_ctm = fz_identity; + trace_device_rot = fz_identity; + dev_lastpoint.x = 0; + dev_lastpoint.y = 0; + dev_firstpoint.x = 0; + dev_firstpoint.y = 0; + dev_pathrect.x0 = 0; + dev_pathrect.y0 = 0; + dev_pathrect.x1 = 0; + dev_pathrect.y1 = 0; + dev_pathfactor = 0; + dev_linecount = 0; + path_type = 0; +} + +// Every scissor of a clip is a sub rectangle of the preceeding clip +// scissor if the clip level is larger. +static fz_rect compute_scissor() +{ + PyObject *last_scissor = NULL; + fz_rect scissor; + if (!scissors) { + scissors = PyList_New(0); + } + Py_ssize_t num_scissors = PyList_Size(scissors); + if (num_scissors > 0) { + last_scissor = PyList_GET_ITEM(scissors, num_scissors-1); + scissor = JM_rect_from_py(last_scissor); + scissor = fz_intersect_rect(scissor, dev_pathrect); + } else { + scissor = dev_pathrect; + } + LIST_APPEND_DROP(scissors, JM_py_from_rect(scissor)); + return scissor; +} + + +static void +jm_increase_seqno(fz_context *ctx, fz_device *dev_, ...) +{ + jm_lineart_device *dev = (jm_lineart_device *) dev_; + dev->seqno += 1; +} + +/* +-------------------------------------------------------------------------- +Check whether the last 4 lines represent a quad. +Because of how we count, the lines are a polyline already, i.e. last point +of a line equals 1st point of next line. +So we check for a polygon (last line's end point equals start point). +If not true we return 0. +-------------------------------------------------------------------------- +*/ +static int +jm_checkquad() +{ + PyObject *items = PyDict_GetItem(dev_pathdict, dictkey_items); + Py_ssize_t i, len = PyList_Size(items); + float f[8]; // coordinates of the 4 corners + fz_point temp, lp; // line = (temp, lp) + PyObject *rect; + PyObject *line; + // fill the 8 floats in f, start from items[-4:] + for (i = 0; i < 4; i++) { // store line start points + line = PyList_GET_ITEM(items, len - 4 + i); + temp = JM_point_from_py(PyTuple_GET_ITEM(line, 1)); + f[i * 2] = temp.x; + f[i * 2 + 1] = temp.y; + lp = JM_point_from_py(PyTuple_GET_ITEM(line, 2)); + } + if (lp.x != f[0] || lp.y != f[1]) { + // not a polygon! + //dev_linecount -= 1; + return 0; + } + + // we have detected a quad + dev_linecount = 0; // reset this + // a quad item is ("qu", (ul, ur, ll, lr)), where the tuple items + // are pairs of floats representing a quad corner each. + rect = PyTuple_New(2); + PyTuple_SET_ITEM(rect, 0, PyUnicode_FromString("qu")); + /* ---------------------------------------------------- + * relationship of float array to quad points: + * (0, 1) = ul, (2, 3) = ll, (6, 7) = ur, (4, 5) = lr + ---------------------------------------------------- */ + fz_quad q = fz_make_quad(f[0], f[1], f[6], f[7], f[2], f[3], f[4], f[5]); + PyTuple_SET_ITEM(rect, 1, JM_py_from_quad(q)); + PyList_SetItem(items, len - 4, rect); // replace item -4 by rect + PyList_SetSlice(items, len - 3, len, NULL); // delete remaining 3 items + return 1; +} + + +/* +-------------------------------------------------------------------------- +Check whether the last 3 path items represent a rectangle. +Line 1 and 3 must be horizontal, line 2 must be vertical. +Returns 1 if we have modified the path, otherwise 0. +-------------------------------------------------------------------------- +*/ +static int +jm_checkrect() +{ + dev_linecount = 0; // reset line count + long orientation = 0; // area orientation of rectangle + fz_point ll, lr, ur, ul; + fz_rect r; + PyObject *rect; + PyObject *line0, *line2; + PyObject *items = PyDict_GetItem(dev_pathdict, dictkey_items); + Py_ssize_t len = PyList_Size(items); + + line0 = PyList_GET_ITEM(items, len - 3); + ll = JM_point_from_py(PyTuple_GET_ITEM(line0, 1)); + lr = JM_point_from_py(PyTuple_GET_ITEM(line0, 2)); + // no need to extract "line1"! + line2 = PyList_GET_ITEM(items, len - 1); + ur = JM_point_from_py(PyTuple_GET_ITEM(line2, 1)); + ul = JM_point_from_py(PyTuple_GET_ITEM(line2, 2)); + + /* + --------------------------------------------------------------------- + Assumption: + When decomposing rects, MuPDF always starts with a horizontal line, + followed by a vertical line, followed by a horizontal line. + First line: (ll, lr), third line: (ul, ur). + If 1st line is below 3rd line, we record anti-clockwise (+1), else + clockwise (-1) orientation. + --------------------------------------------------------------------- + */ + if (ll.y != lr.y || + ll.x != ul.x || + ur.y != ul.y || + ur.x != lr.x) { + goto drop_out; // not a rectangle + } + + // we have a rect, replace last 3 "l" items by one "re" item. + if (ul.y < lr.y) { + r = fz_make_rect(ul.x, ul.y, lr.x, lr.y); + orientation = 1; + } else { + r = fz_make_rect(ll.x, ll.y, ur.x, ur.y); + orientation = -1; + } + rect = PyTuple_New(3); + PyTuple_SET_ITEM(rect, 0, PyUnicode_FromString("re")); + PyTuple_SET_ITEM(rect, 1, JM_py_from_rect(r)); + PyTuple_SET_ITEM(rect, 2, PyLong_FromLong(orientation)); + PyList_SetItem(items, len - 3, rect); // replace item -3 by rect + PyList_SetSlice(items, len - 2, len, NULL); // delete remaining 2 items + return 1; + drop_out:; + return 0; +} + +static PyObject * +jm_lineart_color(fz_context *ctx, fz_colorspace *colorspace, const float *color) +{ + float rgb[3]; + if (colorspace) { + fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), + rgb, NULL, fz_default_color_params); + return Py_BuildValue("fff", rgb[0], rgb[1], rgb[2]); + } + return PyTuple_New(0); +} + +static void +trace_moveto(fz_context *ctx, void *dev_, float x, float y) +{ + dev_lastpoint = fz_transform_point(fz_make_point(x, y), trace_device_ctm); + if (fz_is_infinite_rect(dev_pathrect)) { + dev_pathrect = fz_make_rect(dev_lastpoint.x, dev_lastpoint.y, + dev_lastpoint.x, dev_lastpoint.y); + } + dev_firstpoint = dev_lastpoint; + dev_havemove = 1; + dev_linecount = 0; // reset # of consec. lines +} + +static void +trace_lineto(fz_context *ctx, void *dev_, float x, float y) +{ + fz_point p1 = fz_transform_point(fz_make_point(x, y), trace_device_ctm); + dev_pathrect = fz_include_point_in_rect(dev_pathrect, p1); + PyObject *list = PyTuple_New(3); + PyTuple_SET_ITEM(list, 0, PyUnicode_FromString("l")); + PyTuple_SET_ITEM(list, 1, JM_py_from_point(dev_lastpoint)); + PyTuple_SET_ITEM(list, 2, JM_py_from_point(p1)); + dev_lastpoint = p1; + PyObject *items = PyDict_GetItem(dev_pathdict, dictkey_items); + LIST_APPEND_DROP(items, list); + dev_linecount += 1; // counts consecutive lines + if (dev_linecount == 4 && path_type != FILL_PATH) { // shrink to "re" or "qu" item + jm_checkquad(); + } +} + +static void +trace_curveto(fz_context *ctx, void *dev_, float x1, float y1, float x2, float y2, float x3, float y3) +{ + dev_linecount = 0; // reset # of consec. lines + fz_point p1 = fz_make_point(x1, y1); + fz_point p2 = fz_make_point(x2, y2); + fz_point p3 = fz_make_point(x3, y3); + p1 = fz_transform_point(p1, trace_device_ctm); + p2 = fz_transform_point(p2, trace_device_ctm); + p3 = fz_transform_point(p3, trace_device_ctm); + dev_pathrect = fz_include_point_in_rect(dev_pathrect, p1); + dev_pathrect = fz_include_point_in_rect(dev_pathrect, p2); + dev_pathrect = fz_include_point_in_rect(dev_pathrect, p3); + + PyObject *list = PyTuple_New(5); + PyTuple_SET_ITEM(list, 0, PyUnicode_FromString("c")); + PyTuple_SET_ITEM(list, 1, JM_py_from_point(dev_lastpoint)); + PyTuple_SET_ITEM(list, 2, JM_py_from_point(p1)); + PyTuple_SET_ITEM(list, 3, JM_py_from_point(p2)); + PyTuple_SET_ITEM(list, 4, JM_py_from_point(p3)); + dev_lastpoint = p3; + PyObject *items = PyDict_GetItem(dev_pathdict, dictkey_items); + LIST_APPEND_DROP(items, list); +} + +static void +trace_close(fz_context *ctx, void *dev_) +{ + if (dev_linecount == 3) { + if (jm_checkrect()) { + return; + } + } + dev_linecount = 0; // reset # of consec. lines + if (dev_havemove) { + if (dev_firstpoint.x != dev_lastpoint.x || dev_firstpoint.y != dev_lastpoint.y) { + PyObject *list = PyTuple_New(3); + PyTuple_SET_ITEM(list, 0, PyUnicode_FromString("l")); + PyTuple_SET_ITEM(list, 1, JM_py_from_point(dev_lastpoint)); + PyTuple_SET_ITEM(list, 2, JM_py_from_point(dev_firstpoint)); + dev_lastpoint = dev_firstpoint; + PyObject *items = PyDict_GetItem(dev_pathdict, dictkey_items); + LIST_APPEND_DROP(items, list); + } + dev_havemove = 0; + DICT_SETITEMSTR_DROP(dev_pathdict, "closePath", JM_BOOL(0)); + } else { + DICT_SETITEMSTR_DROP(dev_pathdict, "closePath", JM_BOOL(1)); + } +} + +static const fz_path_walker trace_path_walker = + { + trace_moveto, + trace_lineto, + trace_curveto, + trace_close + }; + +/* +--------------------------------------------------------------------- +Create the "items" list of the path dictionary +* either create or empty the path dictionary +* reset the end point of the path +* reset count of consecutive lines +* invoke fz_walk_path(), which create the single items +* if no items detected, empty path dict again +--------------------------------------------------------------------- +*/ +static void +jm_lineart_path(fz_context *ctx, jm_lineart_device *dev, const fz_path *path) +{ + dev_pathrect = fz_infinite_rect; + dev_linecount = 0; + dev_lastpoint = fz_make_point(0, 0); + if (dev_pathdict) { + Py_CLEAR(dev_pathdict); + } + dev_pathdict = PyDict_New(); + DICT_SETITEM_DROP(dev_pathdict, dictkey_items, PyList_New(0)); + fz_walk_path(ctx, path, &trace_path_walker, dev); + // Check if any items were added ... + if (!PyDict_GetItem(dev_pathdict, dictkey_items) || !PyList_Size(PyDict_GetItem(dev_pathdict, dictkey_items))) { + Py_CLEAR(dev_pathdict); + } +} + +//--------------------------------------------------------------------------- +// Append current path to list or merge into last path of the list. +// (1) Append if first path, different item lists or not a 'stroke' version +// of previous path +// (2) If new path has the same items, merge its content into previous path +// and change path["type"] to "fs". +// (3) If "out" is callable, skip the previous and pass dictionary to it. +//--------------------------------------------------------------------------- +static void +jm_append_merge(PyObject *out, PyObject *method) +{ + if (PyCallable_Check(out) || method != Py_None) { // function or method + goto callback; + } + Py_ssize_t len = PyList_Size(out); // len of output list so far + if (len == 0) { // always append first path + goto append; + } + const char *thistype = PyUnicode_AsUTF8(PyDict_GetItem(dev_pathdict, dictkey_type)); + if (strcmp(thistype, "s") != 0) { // if not stroke, then append + goto append; + } + PyObject *prev = PyList_GET_ITEM(out, len - 1); // get prev path + const char *prevtype = PyUnicode_AsUTF8(PyDict_GetItem(prev, dictkey_type)); + if (strcmp(prevtype, "f") != 0) { // if previous not fill, append + goto append; + } + // last check: there must be the same list of items for "f" and "s". + PyObject *previtems = PyDict_GetItem(prev, dictkey_items); + PyObject *thisitems = PyDict_GetItem(dev_pathdict, dictkey_items); + if (PyObject_RichCompareBool(previtems, thisitems, Py_NE)) { + goto append; + } + int rc = PyDict_Merge(prev, dev_pathdict, 0); // merge with no override + if (rc == 0) { + DICT_SETITEM_DROP(prev, dictkey_type, PyUnicode_FromString("fs")); + goto postappend; + } else { + PySys_WriteStderr("could not merge stroke and fill path"); + goto append; + } + append:; + PyList_Append(out, dev_pathdict); + postappend:; + Py_CLEAR(dev_pathdict); + return; + + callback:; // callback function or method + PyObject *resp = NULL; + if (method == Py_None) { + resp = PyObject_CallFunctionObjArgs(out, dev_pathdict, NULL); + } else { + resp = PyObject_CallMethodObjArgs(out, method, dev_pathdict, NULL); + } + if (resp) { + Py_DECREF(resp); + } else { + PySys_WriteStderr("calling cdrawings callback function/method failed!"); + PyErr_Clear(); + } + Py_CLEAR(dev_pathdict); + return; +} + + +static void +jm_lineart_fill_path(fz_context *ctx, fz_device *dev_, const fz_path *path, + int even_odd, fz_matrix ctm, fz_colorspace *colorspace, + const float *color, float alpha, fz_color_params color_params) +{ + jm_lineart_device *dev = (jm_lineart_device *) dev_; + PyObject *out = dev->out; + trace_device_ctm = ctm; //fz_concat(ctm, trace_device_ptm); + path_type = FILL_PATH; + jm_lineart_path(ctx, dev, path); + if (!dev_pathdict) { + return; + } + DICT_SETITEM_DROP(dev_pathdict, dictkey_type, PyUnicode_FromString("f")); + DICT_SETITEMSTR_DROP(dev_pathdict, "even_odd", JM_BOOL(even_odd)); + DICT_SETITEMSTR_DROP(dev_pathdict, "fill_opacity", Py_BuildValue("f", alpha)); + DICT_SETITEMSTR_DROP(dev_pathdict, "fill", jm_lineart_color(ctx, colorspace, color)); + DICT_SETITEM_DROP(dev_pathdict, dictkey_rect, JM_py_from_rect(dev_pathrect)); + DICT_SETITEMSTR_DROP(dev_pathdict, "seqno", PyLong_FromSize_t(dev->seqno)); + DICT_SETITEMSTR_DROP(dev_pathdict, "layer", JM_UnicodeFromStr(layer_name)); + if (dev->clips) { + DICT_SETITEMSTR_DROP(dev_pathdict, "level", PyLong_FromLong(dev->depth)); + } + jm_append_merge(out, dev->method); + dev->seqno += 1; +} + +static void +jm_lineart_stroke_path(fz_context *ctx, fz_device *dev_, const fz_path *path, + const fz_stroke_state *stroke, fz_matrix ctm, + fz_colorspace *colorspace, const float *color, float alpha, + fz_color_params color_params) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + PyObject *out = dev->out; + int i; + dev_pathfactor = 1; + if (fz_abs(ctm.a) == fz_abs(ctm.d)) { + dev_pathfactor = fz_abs(ctm.a); + } + trace_device_ctm = ctm; // fz_concat(ctm, trace_device_ptm); + path_type = STROKE_PATH; + + jm_lineart_path(ctx, dev, path); + if (!dev_pathdict) { + return; + } + DICT_SETITEM_DROP(dev_pathdict, dictkey_type, PyUnicode_FromString("s")); + DICT_SETITEMSTR_DROP(dev_pathdict, "stroke_opacity", Py_BuildValue("f", alpha)); + DICT_SETITEMSTR_DROP(dev_pathdict, "color", jm_lineart_color(ctx, colorspace, color)); + DICT_SETITEM_DROP(dev_pathdict, dictkey_width, Py_BuildValue("f", dev_pathfactor * stroke->linewidth)); + DICT_SETITEMSTR_DROP(dev_pathdict, "lineCap", Py_BuildValue("iii", stroke->start_cap, stroke->dash_cap, stroke->end_cap)); + DICT_SETITEMSTR_DROP(dev_pathdict, "lineJoin", Py_BuildValue("f", dev_pathfactor * stroke->linejoin)); + if (!PyDict_GetItemString(dev_pathdict, "closePath")) { + DICT_SETITEMSTR_DROP(dev_pathdict, "closePath", JM_BOOL(0)); + } + + // output the "dashes" string + if (stroke->dash_len) { + fz_buffer *buff = fz_new_buffer(ctx, 256); + fz_append_string(ctx, buff, "[ "); // left bracket + for (i = 0; i < stroke->dash_len; i++) { + fz_append_printf(ctx, buff, "%g ", dev_pathfactor * stroke->dash_list[i]); + } + fz_append_printf(ctx, buff, "] %g", dev_pathfactor * stroke->dash_phase); + DICT_SETITEMSTR_DROP(dev_pathdict, "dashes", JM_EscapeStrFromBuffer(ctx, buff)); + fz_drop_buffer(ctx, buff); + } else { + DICT_SETITEMSTR_DROP(dev_pathdict, "dashes", PyUnicode_FromString("[] 0")); + } + + DICT_SETITEM_DROP(dev_pathdict, dictkey_rect, JM_py_from_rect(dev_pathrect)); + DICT_SETITEMSTR_DROP(dev_pathdict, "layer", JM_UnicodeFromStr(layer_name)); + DICT_SETITEMSTR_DROP(dev_pathdict, "seqno", PyLong_FromSize_t(dev->seqno)); + if (dev->clips) { + DICT_SETITEMSTR_DROP(dev_pathdict, "level", PyLong_FromLong(dev->depth)); + } + // output the dict - potentially merging it with a previous fill_path twin + jm_append_merge(out, dev->method); + dev->seqno += 1; +} + +static void +jm_lineart_clip_path(fz_context *ctx, fz_device *dev_, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + if (!dev->clips) return; + PyObject *out = dev->out; + trace_device_ctm = ctm; //fz_concat(ctm, trace_device_ptm); + path_type = CLIP_PATH; + jm_lineart_path(ctx, dev, path); + if (!dev_pathdict) { + return; + } + DICT_SETITEM_DROP(dev_pathdict, dictkey_type, PyUnicode_FromString("clip")); + DICT_SETITEMSTR_DROP(dev_pathdict, "even_odd", JM_BOOL(even_odd)); + if (!PyDict_GetItemString(dev_pathdict, "closePath")) { + DICT_SETITEMSTR_DROP(dev_pathdict, "closePath", JM_BOOL(0)); + } + DICT_SETITEMSTR_DROP(dev_pathdict, "scissor", JM_py_from_rect(compute_scissor())); + DICT_SETITEMSTR_DROP(dev_pathdict, "level", PyLong_FromLong(dev->depth)); + DICT_SETITEMSTR_DROP(dev_pathdict, "layer", JM_UnicodeFromStr(layer_name)); + jm_append_merge(out, dev->method); + dev->depth++; +} + +static void +jm_lineart_clip_stroke_path(fz_context *ctx, fz_device *dev_, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + if (!dev->clips) return; + PyObject *out = dev->out; + trace_device_ctm = ctm; //fz_concat(ctm, trace_device_ptm); + path_type = CLIP_STROKE_PATH; + jm_lineart_path(ctx, dev, path); + if (!dev_pathdict) { + return; + } + DICT_SETITEM_DROP(dev_pathdict, dictkey_type, PyUnicode_FromString("clip")); + DICT_SETITEMSTR_DROP(dev_pathdict, "even_odd", Py_BuildValue("s", NULL)); + if (!PyDict_GetItemString(dev_pathdict, "closePath")) { + DICT_SETITEMSTR_DROP(dev_pathdict, "closePath", JM_BOOL(0)); + } + DICT_SETITEMSTR_DROP(dev_pathdict, "scissor", JM_py_from_rect(compute_scissor())); + DICT_SETITEMSTR_DROP(dev_pathdict, "level", PyLong_FromLong(dev->depth)); + DICT_SETITEMSTR_DROP(dev_pathdict, "layer", JM_UnicodeFromStr(layer_name)); + jm_append_merge(out, dev->method); + dev->depth++; +} + +static void +jm_lineart_clip_stroke_text(fz_context *ctx, fz_device *dev_, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + if (!dev->clips) return; + PyObject *out = dev->out; + compute_scissor(); + dev->depth++; +} + +static void +jm_lineart_clip_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm, fz_rect scissor) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + if (!dev->clips) return; + PyObject *out = dev->out; + compute_scissor(); + dev->depth++; +} + +static void +jm_lineart_clip_image_mask(fz_context *ctx, fz_device *dev_, fz_image *image, fz_matrix ctm, fz_rect scissor) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + if (!dev->clips) return; + PyObject *out = dev->out; + compute_scissor(); + dev->depth++; +} + +static void +jm_lineart_pop_clip(fz_context *ctx, fz_device *dev_) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + if (!dev->clips) return; + if (!scissors) return; + Py_ssize_t len = PyList_Size(scissors); + if (len < 1) return; + PyList_SetSlice(scissors, len - 1, len, NULL); + dev->depth--; +} + + +static void +jm_lineart_begin_layer(fz_context *ctx, fz_device *dev_, const char *name) +{ + layer_name = fz_strdup(ctx, name); +} + +static void +jm_lineart_end_layer(fz_context *ctx, fz_device *dev_) +{ + fz_free(ctx, layer_name); + layer_name = NULL; +} + +static void +jm_lineart_begin_group(fz_context *ctx, fz_device *dev_, fz_rect bbox, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + if (!dev->clips) return; + PyObject *out = dev->out; + dev_pathdict = Py_BuildValue("{s:s,s:N,s:N,s:N,s:s,s:f,s:i,s:N}", + "type", "group", + "rect", JM_py_from_rect(bbox), + "isolated", JM_BOOL(isolated), + "knockout", JM_BOOL(knockout), + "blendmode", fz_blendmode_name(blendmode), + "opacity", alpha, + "level", dev->depth, + "layer", JM_UnicodeFromStr(layer_name) + ); + jm_append_merge(out, dev->method); + dev->depth++; +} + +static void +jm_lineart_end_group(fz_context *ctx, fz_device *dev_) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + if (!dev->clips) return; + dev->depth--; +} + + +static void +jm_dev_linewidth(fz_context *ctx, fz_device *dev_, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) +{ + dev_linewidth = stroke->linewidth; + jm_increase_seqno(ctx, dev_); +} + + +static void +jm_trace_text_span(fz_context *ctx, PyObject *out, fz_text_span *span, int type, fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, size_t seqno) +{ + fz_font *out_font = NULL; + int i; + const char *fontname = JM_font_name(ctx, span->font); + float rgb[3]; + PyObject *chars = PyTuple_New(span->len); + fz_matrix mat = fz_concat(span->trm, ctm); // text transformation matrix + fz_point dir = fz_transform_vector(fz_make_point(1, 0), mat); // writing direction + double fsize = sqrt(dir.x * dir.x + dir.y * dir.y); + + dir = fz_normalize_vector(dir); + double linewidth, adv, asc, dsc; + double space_adv = 0; + float x0, y0, x1, y1; + asc = (double) JM_font_ascender(ctx, span->font); + dsc = (double) JM_font_descender(ctx, span->font); + if (asc < 1e-3) { // probably Tesseract font + dsc = -0.1; + asc = 0.9; + } + // compute effective ascender / descender + double ascsize = asc * fsize / (asc - dsc); + double dscsize = dsc * fsize / (asc - dsc); + + int fflags = 0; // font flags + int mono = fz_font_is_monospaced(ctx, span->font); + fflags += mono * TEXT_FONT_MONOSPACED; + fflags += fz_font_is_italic(ctx, span->font) * TEXT_FONT_ITALIC; + fflags += fz_font_is_serif(ctx, span->font) * TEXT_FONT_SERIFED; + fflags += fz_font_is_bold(ctx, span->font) * TEXT_FONT_BOLD; + + if (dev_linewidth > 0) { // width of character border + linewidth = (double) dev_linewidth; + } else { + linewidth = fsize * 0.05; // default: 5% of font size + } + fz_point char_orig; + double last_adv = 0; + + // walk through characters of span + fz_rect span_bbox; + fz_matrix rot = fz_make_matrix(dir.x, dir.y, -dir.y, dir.x, 0, 0); + if (dir.x == -1) { // left-right flip + rot.d = 1; + } + + //PySys_WriteStdout("mat: (%g, %g, %g, %g)\n", mat.a, mat.b, mat.c, mat.d); + //PySys_WriteStdout("rot: (%g, %g, %g, %g)\n", rot.a, rot.b, rot.c, rot.d); + + for (i = 0; i < span->len; i++) { + adv = 0; + if (span->items[i].gid >= 0) { + adv = (double) fz_advance_glyph(ctx, span->font, span->items[i].gid, span->wmode); + } + adv *= fsize; + last_adv = adv; + if (span->items[i].ucs == 32) { + space_adv = adv; + } + char_orig = fz_make_point(span->items[i].x, span->items[i].y); + char_orig = fz_transform_point(char_orig, ctm); + fz_matrix m1 = fz_make_matrix(1, 0, 0, 1, -char_orig.x, -char_orig.y); + m1 = fz_concat(m1, rot); + m1 = fz_concat(m1, fz_make_matrix(1, 0, 0, 1, char_orig.x, char_orig.y)); + x0 = char_orig.x; + x1 = x0 + adv; + if (mat.d > 0 && (dir.x == 1 || dir.x == -1) || + mat.b !=0 && mat.b == -mat.c) { // up-down flip + y0 = char_orig.y + dscsize; + y1 = char_orig.y + ascsize; + } else { + y0 = char_orig.y - ascsize; + y1 = char_orig.y - dscsize; + } + fz_rect char_bbox = fz_make_rect(x0, y0, x1, y1); + char_bbox = fz_transform_rect(char_bbox, m1); + PyTuple_SET_ITEM(chars, (Py_ssize_t) i, Py_BuildValue("ii(ff)(ffff)", + span->items[i].ucs, span->items[i].gid, + char_orig.x, char_orig.y, char_bbox.x0, char_bbox.y0, char_bbox.x1, char_bbox.y1)); + if (i > 0) { + span_bbox = fz_union_rect(span_bbox, char_bbox); + } else { + span_bbox = char_bbox; + } + } + if (!space_adv) { + if (!mono) { + space_adv = fz_advance_glyph(ctx, span->font, + fz_encode_character_with_fallback(ctx, span->font, 32, 0, 0, &out_font), + span->wmode); + space_adv *= fsize; + if (!space_adv) { + space_adv = last_adv; + } + } else { + space_adv = last_adv; // for mono, any char width suffices + } + } + // make the span dictionary + PyObject *span_dict = PyDict_New(); + DICT_SETITEMSTR_DROP(span_dict, "dir", JM_py_from_point(dir)); + DICT_SETITEM_DROP(span_dict, dictkey_font, JM_EscapeStrFromStr(fontname)); + DICT_SETITEM_DROP(span_dict, dictkey_wmode, PyLong_FromLong((long) span->wmode)); + DICT_SETITEM_DROP(span_dict, dictkey_flags, PyLong_FromLong((long) fflags)); + DICT_SETITEMSTR_DROP(span_dict, "bidi_lvl", PyLong_FromLong((long) span->bidi_level)); + DICT_SETITEMSTR_DROP(span_dict, "bidi_dir", PyLong_FromLong((long) span->markup_dir)); + DICT_SETITEM_DROP(span_dict, dictkey_ascender, PyFloat_FromDouble(asc)); + DICT_SETITEM_DROP(span_dict, dictkey_descender, PyFloat_FromDouble(dsc)); + DICT_SETITEM_DROP(span_dict, dictkey_colorspace, PyLong_FromLong(3)); + + if (colorspace) { + fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), + rgb, NULL, fz_default_color_params); + } else { + rgb[0] = rgb[1] = rgb[2] = 0; + } + + DICT_SETITEM_DROP(span_dict, dictkey_color, Py_BuildValue("fff", rgb[0], rgb[1], rgb[2])); + DICT_SETITEM_DROP(span_dict, dictkey_size, PyFloat_FromDouble(fsize)); + DICT_SETITEMSTR_DROP(span_dict, "opacity", PyFloat_FromDouble((double) alpha)); + DICT_SETITEMSTR_DROP(span_dict, "linewidth", PyFloat_FromDouble((double) linewidth)); + DICT_SETITEMSTR_DROP(span_dict, "spacewidth", PyFloat_FromDouble(space_adv)); + DICT_SETITEM_DROP(span_dict, dictkey_type, PyLong_FromLong((long) type)); + DICT_SETITEM_DROP(span_dict, dictkey_bbox, JM_py_from_rect(span_bbox)); + DICT_SETITEMSTR_DROP(span_dict, "layer", JM_UnicodeFromStr(layer_name)); + DICT_SETITEMSTR_DROP(span_dict, "seqno", PyLong_FromSize_t(seqno)); + DICT_SETITEM_DROP(span_dict, dictkey_chars, chars); + LIST_APPEND_DROP(out, span_dict); +} + +static void +jm_trace_text(fz_context *ctx, PyObject *out, const fz_text *text, int type, fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, size_t seqno) +{ + fz_text_span *span; + for (span = text->head; span; span = span->next) + jm_trace_text_span(ctx, out, span, type, ctm, colorspace, color, alpha, seqno); +} + +/*--------------------------------------------------------- +There are 3 text trace types: +0 - fill text (PDF Tr 0) +1 - stroke text (PDF Tr 1) +3 - ignore text (PDF Tr 3) +---------------------------------------------------------*/ +static void +jm_lineart_fill_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + PyObject *out = dev->out; + jm_trace_text(ctx, out, text, 0, ctm, colorspace, color, alpha, dev->seqno); + dev->seqno += 1; +} + +static void +jm_lineart_stroke_text(fz_context *ctx, fz_device *dev_, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + PyObject *out = dev->out; + jm_trace_text(ctx, out, text, 1, ctm, colorspace, color, alpha, dev->seqno); + dev->seqno += 1; +} + + +static void +jm_lineart_ignore_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + PyObject *out = dev->out; + jm_trace_text(ctx, out, text, 3, ctm, NULL, NULL, 1, dev->seqno); + dev->seqno += 1; +} + +static void jm_lineart_drop_device(fz_context *ctx, fz_device *dev_) +{ + jm_lineart_device *dev = (jm_lineart_device *)dev_; + if (PyList_Check(dev->out)) { + Py_CLEAR(dev->out); + } + Py_CLEAR(dev->method); + Py_CLEAR(scissors); +} + +//------------------------------------------------------------------- +// LINEART device for Python method Page.get_cdrawings() +//------------------------------------------------------------------- +fz_device *JM_new_lineart_device(fz_context *ctx, PyObject *out, int clips, PyObject *method) +{ + jm_lineart_device *dev = fz_new_derived_device(ctx, jm_lineart_device); + + dev->super.close_device = NULL; + dev->super.drop_device = jm_lineart_drop_device; + dev->super.fill_path = jm_lineart_fill_path; + dev->super.stroke_path = jm_lineart_stroke_path; + dev->super.clip_path = jm_lineart_clip_path; + dev->super.clip_stroke_path = jm_lineart_clip_stroke_path; + + dev->super.fill_text = jm_increase_seqno; + dev->super.stroke_text = jm_increase_seqno; + dev->super.clip_text = jm_lineart_clip_text; + dev->super.clip_stroke_text = jm_lineart_clip_stroke_text; + dev->super.ignore_text = jm_increase_seqno; + + dev->super.fill_shade = jm_increase_seqno; + dev->super.fill_image = jm_increase_seqno; + dev->super.fill_image_mask = jm_increase_seqno; + dev->super.clip_image_mask = jm_lineart_clip_image_mask; + + dev->super.pop_clip = jm_lineart_pop_clip; + + dev->super.begin_mask = NULL; + dev->super.end_mask = NULL; + dev->super.begin_group = jm_lineart_begin_group; + dev->super.end_group = jm_lineart_end_group; + + dev->super.begin_tile = NULL; + dev->super.end_tile = NULL; + + dev->super.begin_layer = jm_lineart_begin_layer; + dev->super.end_layer = jm_lineart_end_layer; + + dev->super.begin_structure = NULL; + dev->super.end_structure = NULL; + + dev->super.begin_metatext = NULL; + dev->super.end_metatext = NULL; + + dev->super.render_flags = NULL; + dev->super.set_default_colorspaces = NULL; + + if (PyList_Check(out)) { + Py_INCREF(out); + } + Py_INCREF(method); + dev->out = out; + dev->seqno = 0; + dev->depth = 0; + dev->clips = clips; + dev->method = method; + trace_device_reset(); + return (fz_device *)dev; +} + +//------------------------------------------------------------------- +// Trace TEXT device for Python method Page.get_texttrace() +//------------------------------------------------------------------- +fz_device *JM_new_texttrace_device(fz_context *ctx, PyObject *out) +{ + jm_lineart_device *dev = fz_new_derived_device(ctx, jm_lineart_device); + + dev->super.close_device = NULL; + dev->super.drop_device = jm_lineart_drop_device; + dev->super.fill_path = jm_increase_seqno; + dev->super.stroke_path = jm_dev_linewidth; + dev->super.clip_path = NULL; + dev->super.clip_stroke_path = NULL; + + dev->super.fill_text = jm_lineart_fill_text; + dev->super.stroke_text = jm_lineart_stroke_text; + dev->super.clip_text = NULL; + dev->super.clip_stroke_text = NULL; + dev->super.ignore_text = jm_lineart_ignore_text; + + dev->super.fill_shade = jm_increase_seqno; + dev->super.fill_image = jm_increase_seqno; + dev->super.fill_image_mask = jm_increase_seqno; + dev->super.clip_image_mask = NULL; + + dev->super.pop_clip = NULL; + + dev->super.begin_mask = NULL; + dev->super.end_mask = NULL; + dev->super.begin_group = NULL; + dev->super.end_group = NULL; + + dev->super.begin_tile = NULL; + dev->super.end_tile = NULL; + + dev->super.begin_layer = jm_lineart_begin_layer; + dev->super.end_layer = jm_lineart_end_layer; + + dev->super.begin_structure = NULL; + dev->super.end_structure = NULL; + + dev->super.begin_metatext = NULL; + dev->super.end_metatext = NULL; + + dev->super.render_flags = NULL; + dev->super.set_default_colorspaces = NULL; + + if (PyList_Check(out)) { + Py_XINCREF(out); + } + dev->out = out; + dev->seqno = 0; + dev->depth = 0; + dev->clips = 0; + dev->method = NULL; + trace_device_reset(); + + return (fz_device *)dev; +} + +//------------------------------------------------------------------- +// BBOX device +//------------------------------------------------------------------- +typedef struct jm_bbox_device_s +{ + fz_device super; + PyObject *result; + int layers; +} jm_bbox_device; + +static void +jm_bbox_add_rect(fz_context *ctx, fz_device *dev, fz_rect rect, char *code) +{ + jm_bbox_device *bdev = (jm_bbox_device *)dev; + if (!bdev->layers) { + LIST_APPEND_DROP(bdev->result, Py_BuildValue("sN", code, JM_py_from_rect(rect))); + } else { + LIST_APPEND_DROP(bdev->result, Py_BuildValue("sNN", code, JM_py_from_rect(rect), JM_UnicodeFromStr(layer_name))); + } +} + +static void +jm_bbox_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, + fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) +{ + jm_bbox_add_rect(ctx, dev, fz_bound_path(ctx, path, NULL, ctm), "fill-path"); +} + +static void +jm_bbox_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, + fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) +{ + jm_bbox_add_rect(ctx, dev, fz_bound_path(ctx, path, stroke, ctm), "stroke-path"); +} + +static void +jm_bbox_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, ...) +{ + jm_bbox_add_rect(ctx, dev, fz_bound_text(ctx, text, NULL, ctm), "fill-text"); +} + +static void +jm_bbox_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm) +{ + jm_bbox_add_rect(ctx, dev, fz_bound_text(ctx, text, NULL, ctm), "ignore-text"); +} + +static void +jm_bbox_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, ...) +{ + jm_bbox_add_rect(ctx, dev, fz_bound_text(ctx, text, stroke, ctm), "stroke-text"); +} + +static void +jm_bbox_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params) +{ + jm_bbox_add_rect(ctx, dev, fz_bound_shade(ctx, shade, ctm), "fill-shade"); +} + +static void +jm_bbox_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params) +{ + jm_bbox_add_rect(ctx, dev, fz_transform_rect(fz_unit_rect, ctm), "fill-image"); +} + +static void +jm_bbox_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, + fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) +{ + jm_bbox_add_rect(ctx, dev, fz_transform_rect(fz_unit_rect, ctm), "fill-imgmask"); +} + +fz_device * +JM_new_bbox_device(fz_context *ctx, PyObject *result, int layers) +{ + jm_bbox_device *dev = fz_new_derived_device(ctx, jm_bbox_device); + + dev->super.fill_path = jm_bbox_fill_path; + dev->super.stroke_path = jm_bbox_stroke_path; + dev->super.clip_path = NULL; + dev->super.clip_stroke_path = NULL; + + dev->super.fill_text = jm_bbox_fill_text; + dev->super.stroke_text = jm_bbox_stroke_text; + dev->super.clip_text = NULL; + dev->super.clip_stroke_text = NULL; + dev->super.ignore_text = jm_bbox_ignore_text; + + dev->super.fill_shade = jm_bbox_fill_shade; + dev->super.fill_image = jm_bbox_fill_image; + dev->super.fill_image_mask = jm_bbox_fill_image_mask; + dev->super.clip_image_mask = NULL; + + dev->super.pop_clip = NULL; + + dev->super.begin_mask = NULL; + dev->super.end_mask = NULL; + dev->super.begin_group = NULL; + dev->super.end_group = NULL; + + dev->super.begin_tile = NULL; + dev->super.end_tile = NULL; + + dev->super.begin_layer = jm_lineart_begin_layer; + dev->super.end_layer = jm_lineart_end_layer; + + dev->super.begin_structure = NULL; + dev->super.end_structure = NULL; + + dev->super.begin_metatext = NULL; + dev->super.end_metatext = NULL; + + dev->super.render_flags = NULL; + dev->super.set_default_colorspaces = NULL; + + dev->result = result; + dev->layers = layers; + trace_device_reset(); + + return (fz_device *)dev; +} + +%}
