comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 1:1d09e1dec1d9
1 %{
2 /*
3 # ------------------------------------------------------------------------
4 # Copyright 2020-2022, Harald Lieder, mailto:harald.lieder@outlook.com
5 # License: GNU AFFERO GPL 3.0, https://www.gnu.org/licenses/agpl-3.0.html
6 #
7 # Part of "PyMuPDF", a Python binding for "MuPDF" (http://mupdf.com), a
8 # lightweight PDF, XPS, and E-book viewer, renderer and toolkit which is
9 # maintained and developed by Artifex Software, Inc. https://artifex.com.
10 # ------------------------------------------------------------------------
11 */
12 typedef struct
13 {
14 fz_device super;
15 PyObject *out;
16 size_t seqno;
17 long depth;
18 int clips;
19 PyObject *method;
20 } jm_lineart_device;
21
22 static PyObject *dev_pathdict = NULL;
23 static PyObject *scissors = NULL;
24 static float dev_linewidth = 0; // border width if present
25 static fz_matrix trace_device_ptm; // page transformation matrix
26 static fz_matrix trace_device_ctm; // trace device matrix
27 static fz_matrix trace_device_rot;
28 static fz_point dev_lastpoint = {0, 0};
29 static fz_point dev_firstpoint = {0, 0};
30 static int dev_havemove = 0;
31 static fz_rect dev_pathrect;
32 static float dev_pathfactor = 0;
33 static int dev_linecount = 0;
34 static char *layer_name=NULL; // optional content name
35 static int path_type = 0; // one of the following values:
36 #define FILL_PATH 1
37 #define STROKE_PATH 2
38 #define CLIP_PATH 3
39 #define CLIP_STROKE_PATH 4
40
41 static void trace_device_reset()
42 {
43 Py_CLEAR(dev_pathdict);
44 Py_CLEAR(scissors);
45 layer_name = NULL;
46 dev_linewidth = 0;
47 trace_device_ptm = fz_identity;
48 trace_device_ctm = fz_identity;
49 trace_device_rot = fz_identity;
50 dev_lastpoint.x = 0;
51 dev_lastpoint.y = 0;
52 dev_firstpoint.x = 0;
53 dev_firstpoint.y = 0;
54 dev_pathrect.x0 = 0;
55 dev_pathrect.y0 = 0;
56 dev_pathrect.x1 = 0;
57 dev_pathrect.y1 = 0;
58 dev_pathfactor = 0;
59 dev_linecount = 0;
60 path_type = 0;
61 }
62
63 // Every scissor of a clip is a sub rectangle of the preceeding clip
64 // scissor if the clip level is larger.
65 static fz_rect compute_scissor()
66 {
67 PyObject *last_scissor = NULL;
68 fz_rect scissor;
69 if (!scissors) {
70 scissors = PyList_New(0);
71 }
72 Py_ssize_t num_scissors = PyList_Size(scissors);
73 if (num_scissors > 0) {
74 last_scissor = PyList_GET_ITEM(scissors, num_scissors-1);
75 scissor = JM_rect_from_py(last_scissor);
76 scissor = fz_intersect_rect(scissor, dev_pathrect);
77 } else {
78 scissor = dev_pathrect;
79 }
80 LIST_APPEND_DROP(scissors, JM_py_from_rect(scissor));
81 return scissor;
82 }
83
84
85 static void
86 jm_increase_seqno(fz_context *ctx, fz_device *dev_, ...)
87 {
88 jm_lineart_device *dev = (jm_lineart_device *) dev_;
89 dev->seqno += 1;
90 }
91
92 /*
93 --------------------------------------------------------------------------
94 Check whether the last 4 lines represent a quad.
95 Because of how we count, the lines are a polyline already, i.e. last point
96 of a line equals 1st point of next line.
97 So we check for a polygon (last line's end point equals start point).
98 If not true we return 0.
99 --------------------------------------------------------------------------
100 */
101 static int
102 jm_checkquad()
103 {
104 PyObject *items = PyDict_GetItem(dev_pathdict, dictkey_items);
105 Py_ssize_t i, len = PyList_Size(items);
106 float f[8]; // coordinates of the 4 corners
107 fz_point temp, lp; // line = (temp, lp)
108 PyObject *rect;
109 PyObject *line;
110 // fill the 8 floats in f, start from items[-4:]
111 for (i = 0; i < 4; i++) { // store line start points
112 line = PyList_GET_ITEM(items, len - 4 + i);
113 temp = JM_point_from_py(PyTuple_GET_ITEM(line, 1));
114 f[i * 2] = temp.x;
115 f[i * 2 + 1] = temp.y;
116 lp = JM_point_from_py(PyTuple_GET_ITEM(line, 2));
117 }
118 if (lp.x != f[0] || lp.y != f[1]) {
119 // not a polygon!
120 //dev_linecount -= 1;
121 return 0;
122 }
123
124 // we have detected a quad
125 dev_linecount = 0; // reset this
126 // a quad item is ("qu", (ul, ur, ll, lr)), where the tuple items
127 // are pairs of floats representing a quad corner each.
128 rect = PyTuple_New(2);
129 PyTuple_SET_ITEM(rect, 0, PyUnicode_FromString("qu"));
130 /* ----------------------------------------------------
131 * relationship of float array to quad points:
132 * (0, 1) = ul, (2, 3) = ll, (6, 7) = ur, (4, 5) = lr
133 ---------------------------------------------------- */
134 fz_quad q = fz_make_quad(f[0], f[1], f[6], f[7], f[2], f[3], f[4], f[5]);
135 PyTuple_SET_ITEM(rect, 1, JM_py_from_quad(q));
136 PyList_SetItem(items, len - 4, rect); // replace item -4 by rect
137 PyList_SetSlice(items, len - 3, len, NULL); // delete remaining 3 items
138 return 1;
139 }
140
141
142 /*
143 --------------------------------------------------------------------------
144 Check whether the last 3 path items represent a rectangle.
145 Line 1 and 3 must be horizontal, line 2 must be vertical.
146 Returns 1 if we have modified the path, otherwise 0.
147 --------------------------------------------------------------------------
148 */
149 static int
150 jm_checkrect()
151 {
152 dev_linecount = 0; // reset line count
153 long orientation = 0; // area orientation of rectangle
154 fz_point ll, lr, ur, ul;
155 fz_rect r;
156 PyObject *rect;
157 PyObject *line0, *line2;
158 PyObject *items = PyDict_GetItem(dev_pathdict, dictkey_items);
159 Py_ssize_t len = PyList_Size(items);
160
161 line0 = PyList_GET_ITEM(items, len - 3);
162 ll = JM_point_from_py(PyTuple_GET_ITEM(line0, 1));
163 lr = JM_point_from_py(PyTuple_GET_ITEM(line0, 2));
164 // no need to extract "line1"!
165 line2 = PyList_GET_ITEM(items, len - 1);
166 ur = JM_point_from_py(PyTuple_GET_ITEM(line2, 1));
167 ul = JM_point_from_py(PyTuple_GET_ITEM(line2, 2));
168
169 /*
170 ---------------------------------------------------------------------
171 Assumption:
172 When decomposing rects, MuPDF always starts with a horizontal line,
173 followed by a vertical line, followed by a horizontal line.
174 First line: (ll, lr), third line: (ul, ur).
175 If 1st line is below 3rd line, we record anti-clockwise (+1), else
176 clockwise (-1) orientation.
177 ---------------------------------------------------------------------
178 */
179 if (ll.y != lr.y ||
180 ll.x != ul.x ||
181 ur.y != ul.y ||
182 ur.x != lr.x) {
183 goto drop_out; // not a rectangle
184 }
185
186 // we have a rect, replace last 3 "l" items by one "re" item.
187 if (ul.y < lr.y) {
188 r = fz_make_rect(ul.x, ul.y, lr.x, lr.y);
189 orientation = 1;
190 } else {
191 r = fz_make_rect(ll.x, ll.y, ur.x, ur.y);
192 orientation = -1;
193 }
194 rect = PyTuple_New(3);
195 PyTuple_SET_ITEM(rect, 0, PyUnicode_FromString("re"));
196 PyTuple_SET_ITEM(rect, 1, JM_py_from_rect(r));
197 PyTuple_SET_ITEM(rect, 2, PyLong_FromLong(orientation));
198 PyList_SetItem(items, len - 3, rect); // replace item -3 by rect
199 PyList_SetSlice(items, len - 2, len, NULL); // delete remaining 2 items
200 return 1;
201 drop_out:;
202 return 0;
203 }
204
205 static PyObject *
206 jm_lineart_color(fz_context *ctx, fz_colorspace *colorspace, const float *color)
207 {
208 float rgb[3];
209 if (colorspace) {
210 fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx),
211 rgb, NULL, fz_default_color_params);
212 return Py_BuildValue("fff", rgb[0], rgb[1], rgb[2]);
213 }
214 return PyTuple_New(0);
215 }
216
217 static void
218 trace_moveto(fz_context *ctx, void *dev_, float x, float y)
219 {
220 dev_lastpoint = fz_transform_point(fz_make_point(x, y), trace_device_ctm);
221 if (fz_is_infinite_rect(dev_pathrect)) {
222 dev_pathrect = fz_make_rect(dev_lastpoint.x, dev_lastpoint.y,
223 dev_lastpoint.x, dev_lastpoint.y);
224 }
225 dev_firstpoint = dev_lastpoint;
226 dev_havemove = 1;
227 dev_linecount = 0; // reset # of consec. lines
228 }
229
230 static void
231 trace_lineto(fz_context *ctx, void *dev_, float x, float y)
232 {
233 fz_point p1 = fz_transform_point(fz_make_point(x, y), trace_device_ctm);
234 dev_pathrect = fz_include_point_in_rect(dev_pathrect, p1);
235 PyObject *list = PyTuple_New(3);
236 PyTuple_SET_ITEM(list, 0, PyUnicode_FromString("l"));
237 PyTuple_SET_ITEM(list, 1, JM_py_from_point(dev_lastpoint));
238 PyTuple_SET_ITEM(list, 2, JM_py_from_point(p1));
239 dev_lastpoint = p1;
240 PyObject *items = PyDict_GetItem(dev_pathdict, dictkey_items);
241 LIST_APPEND_DROP(items, list);
242 dev_linecount += 1; // counts consecutive lines
243 if (dev_linecount == 4 && path_type != FILL_PATH) { // shrink to "re" or "qu" item
244 jm_checkquad();
245 }
246 }
247
248 static void
249 trace_curveto(fz_context *ctx, void *dev_, float x1, float y1, float x2, float y2, float x3, float y3)
250 {
251 dev_linecount = 0; // reset # of consec. lines
252 fz_point p1 = fz_make_point(x1, y1);
253 fz_point p2 = fz_make_point(x2, y2);
254 fz_point p3 = fz_make_point(x3, y3);
255 p1 = fz_transform_point(p1, trace_device_ctm);
256 p2 = fz_transform_point(p2, trace_device_ctm);
257 p3 = fz_transform_point(p3, trace_device_ctm);
258 dev_pathrect = fz_include_point_in_rect(dev_pathrect, p1);
259 dev_pathrect = fz_include_point_in_rect(dev_pathrect, p2);
260 dev_pathrect = fz_include_point_in_rect(dev_pathrect, p3);
261
262 PyObject *list = PyTuple_New(5);
263 PyTuple_SET_ITEM(list, 0, PyUnicode_FromString("c"));
264 PyTuple_SET_ITEM(list, 1, JM_py_from_point(dev_lastpoint));
265 PyTuple_SET_ITEM(list, 2, JM_py_from_point(p1));
266 PyTuple_SET_ITEM(list, 3, JM_py_from_point(p2));
267 PyTuple_SET_ITEM(list, 4, JM_py_from_point(p3));
268 dev_lastpoint = p3;
269 PyObject *items = PyDict_GetItem(dev_pathdict, dictkey_items);
270 LIST_APPEND_DROP(items, list);
271 }
272
273 static void
274 trace_close(fz_context *ctx, void *dev_)
275 {
276 if (dev_linecount == 3) {
277 if (jm_checkrect()) {
278 return;
279 }
280 }
281 dev_linecount = 0; // reset # of consec. lines
282 if (dev_havemove) {
283 if (dev_firstpoint.x != dev_lastpoint.x || dev_firstpoint.y != dev_lastpoint.y) {
284 PyObject *list = PyTuple_New(3);
285 PyTuple_SET_ITEM(list, 0, PyUnicode_FromString("l"));
286 PyTuple_SET_ITEM(list, 1, JM_py_from_point(dev_lastpoint));
287 PyTuple_SET_ITEM(list, 2, JM_py_from_point(dev_firstpoint));
288 dev_lastpoint = dev_firstpoint;
289 PyObject *items = PyDict_GetItem(dev_pathdict, dictkey_items);
290 LIST_APPEND_DROP(items, list);
291 }
292 dev_havemove = 0;
293 DICT_SETITEMSTR_DROP(dev_pathdict, "closePath", JM_BOOL(0));
294 } else {
295 DICT_SETITEMSTR_DROP(dev_pathdict, "closePath", JM_BOOL(1));
296 }
297 }
298
299 static const fz_path_walker trace_path_walker =
300 {
301 trace_moveto,
302 trace_lineto,
303 trace_curveto,
304 trace_close
305 };
306
307 /*
308 ---------------------------------------------------------------------
309 Create the "items" list of the path dictionary
310 * either create or empty the path dictionary
311 * reset the end point of the path
312 * reset count of consecutive lines
313 * invoke fz_walk_path(), which create the single items
314 * if no items detected, empty path dict again
315 ---------------------------------------------------------------------
316 */
317 static void
318 jm_lineart_path(fz_context *ctx, jm_lineart_device *dev, const fz_path *path)
319 {
320 dev_pathrect = fz_infinite_rect;
321 dev_linecount = 0;
322 dev_lastpoint = fz_make_point(0, 0);
323 if (dev_pathdict) {
324 Py_CLEAR(dev_pathdict);
325 }
326 dev_pathdict = PyDict_New();
327 DICT_SETITEM_DROP(dev_pathdict, dictkey_items, PyList_New(0));
328 fz_walk_path(ctx, path, &trace_path_walker, dev);
329 // Check if any items were added ...
330 if (!PyDict_GetItem(dev_pathdict, dictkey_items) || !PyList_Size(PyDict_GetItem(dev_pathdict, dictkey_items))) {
331 Py_CLEAR(dev_pathdict);
332 }
333 }
334
335 //---------------------------------------------------------------------------
336 // Append current path to list or merge into last path of the list.
337 // (1) Append if first path, different item lists or not a 'stroke' version
338 // of previous path
339 // (2) If new path has the same items, merge its content into previous path
340 // and change path["type"] to "fs".
341 // (3) If "out" is callable, skip the previous and pass dictionary to it.
342 //---------------------------------------------------------------------------
343 static void
344 jm_append_merge(PyObject *out, PyObject *method)
345 {
346 if (PyCallable_Check(out) || method != Py_None) { // function or method
347 goto callback;
348 }
349 Py_ssize_t len = PyList_Size(out); // len of output list so far
350 if (len == 0) { // always append first path
351 goto append;
352 }
353 const char *thistype = PyUnicode_AsUTF8(PyDict_GetItem(dev_pathdict, dictkey_type));
354 if (strcmp(thistype, "s") != 0) { // if not stroke, then append
355 goto append;
356 }
357 PyObject *prev = PyList_GET_ITEM(out, len - 1); // get prev path
358 const char *prevtype = PyUnicode_AsUTF8(PyDict_GetItem(prev, dictkey_type));
359 if (strcmp(prevtype, "f") != 0) { // if previous not fill, append
360 goto append;
361 }
362 // last check: there must be the same list of items for "f" and "s".
363 PyObject *previtems = PyDict_GetItem(prev, dictkey_items);
364 PyObject *thisitems = PyDict_GetItem(dev_pathdict, dictkey_items);
365 if (PyObject_RichCompareBool(previtems, thisitems, Py_NE)) {
366 goto append;
367 }
368 int rc = PyDict_Merge(prev, dev_pathdict, 0); // merge with no override
369 if (rc == 0) {
370 DICT_SETITEM_DROP(prev, dictkey_type, PyUnicode_FromString("fs"));
371 goto postappend;
372 } else {
373 PySys_WriteStderr("could not merge stroke and fill path");
374 goto append;
375 }
376 append:;
377 PyList_Append(out, dev_pathdict);
378 postappend:;
379 Py_CLEAR(dev_pathdict);
380 return;
381
382 callback:; // callback function or method
383 PyObject *resp = NULL;
384 if (method == Py_None) {
385 resp = PyObject_CallFunctionObjArgs(out, dev_pathdict, NULL);
386 } else {
387 resp = PyObject_CallMethodObjArgs(out, method, dev_pathdict, NULL);
388 }
389 if (resp) {
390 Py_DECREF(resp);
391 } else {
392 PySys_WriteStderr("calling cdrawings callback function/method failed!");
393 PyErr_Clear();
394 }
395 Py_CLEAR(dev_pathdict);
396 return;
397 }
398
399
400 static void
401 jm_lineart_fill_path(fz_context *ctx, fz_device *dev_, const fz_path *path,
402 int even_odd, fz_matrix ctm, fz_colorspace *colorspace,
403 const float *color, float alpha, fz_color_params color_params)
404 {
405 jm_lineart_device *dev = (jm_lineart_device *) dev_;
406 PyObject *out = dev->out;
407 trace_device_ctm = ctm; //fz_concat(ctm, trace_device_ptm);
408 path_type = FILL_PATH;
409 jm_lineart_path(ctx, dev, path);
410 if (!dev_pathdict) {
411 return;
412 }
413 DICT_SETITEM_DROP(dev_pathdict, dictkey_type, PyUnicode_FromString("f"));
414 DICT_SETITEMSTR_DROP(dev_pathdict, "even_odd", JM_BOOL(even_odd));
415 DICT_SETITEMSTR_DROP(dev_pathdict, "fill_opacity", Py_BuildValue("f", alpha));
416 DICT_SETITEMSTR_DROP(dev_pathdict, "fill", jm_lineart_color(ctx, colorspace, color));
417 DICT_SETITEM_DROP(dev_pathdict, dictkey_rect, JM_py_from_rect(dev_pathrect));
418 DICT_SETITEMSTR_DROP(dev_pathdict, "seqno", PyLong_FromSize_t(dev->seqno));
419 DICT_SETITEMSTR_DROP(dev_pathdict, "layer", JM_UnicodeFromStr(layer_name));
420 if (dev->clips) {
421 DICT_SETITEMSTR_DROP(dev_pathdict, "level", PyLong_FromLong(dev->depth));
422 }
423 jm_append_merge(out, dev->method);
424 dev->seqno += 1;
425 }
426
427 static void
428 jm_lineart_stroke_path(fz_context *ctx, fz_device *dev_, const fz_path *path,
429 const fz_stroke_state *stroke, fz_matrix ctm,
430 fz_colorspace *colorspace, const float *color, float alpha,
431 fz_color_params color_params)
432 {
433 jm_lineart_device *dev = (jm_lineart_device *)dev_;
434 PyObject *out = dev->out;
435 int i;
436 dev_pathfactor = 1;
437 if (fz_abs(ctm.a) == fz_abs(ctm.d)) {
438 dev_pathfactor = fz_abs(ctm.a);
439 }
440 trace_device_ctm = ctm; // fz_concat(ctm, trace_device_ptm);
441 path_type = STROKE_PATH;
442
443 jm_lineart_path(ctx, dev, path);
444 if (!dev_pathdict) {
445 return;
446 }
447 DICT_SETITEM_DROP(dev_pathdict, dictkey_type, PyUnicode_FromString("s"));
448 DICT_SETITEMSTR_DROP(dev_pathdict, "stroke_opacity", Py_BuildValue("f", alpha));
449 DICT_SETITEMSTR_DROP(dev_pathdict, "color", jm_lineart_color(ctx, colorspace, color));
450 DICT_SETITEM_DROP(dev_pathdict, dictkey_width, Py_BuildValue("f", dev_pathfactor * stroke->linewidth));
451 DICT_SETITEMSTR_DROP(dev_pathdict, "lineCap", Py_BuildValue("iii", stroke->start_cap, stroke->dash_cap, stroke->end_cap));
452 DICT_SETITEMSTR_DROP(dev_pathdict, "lineJoin", Py_BuildValue("f", dev_pathfactor * stroke->linejoin));
453 if (!PyDict_GetItemString(dev_pathdict, "closePath")) {
454 DICT_SETITEMSTR_DROP(dev_pathdict, "closePath", JM_BOOL(0));
455 }
456
457 // output the "dashes" string
458 if (stroke->dash_len) {
459 fz_buffer *buff = fz_new_buffer(ctx, 256);
460 fz_append_string(ctx, buff, "[ "); // left bracket
461 for (i = 0; i < stroke->dash_len; i++) {
462 fz_append_printf(ctx, buff, "%g ", dev_pathfactor * stroke->dash_list[i]);
463 }
464 fz_append_printf(ctx, buff, "] %g", dev_pathfactor * stroke->dash_phase);
465 DICT_SETITEMSTR_DROP(dev_pathdict, "dashes", JM_EscapeStrFromBuffer(ctx, buff));
466 fz_drop_buffer(ctx, buff);
467 } else {
468 DICT_SETITEMSTR_DROP(dev_pathdict, "dashes", PyUnicode_FromString("[] 0"));
469 }
470
471 DICT_SETITEM_DROP(dev_pathdict, dictkey_rect, JM_py_from_rect(dev_pathrect));
472 DICT_SETITEMSTR_DROP(dev_pathdict, "layer", JM_UnicodeFromStr(layer_name));
473 DICT_SETITEMSTR_DROP(dev_pathdict, "seqno", PyLong_FromSize_t(dev->seqno));
474 if (dev->clips) {
475 DICT_SETITEMSTR_DROP(dev_pathdict, "level", PyLong_FromLong(dev->depth));
476 }
477 // output the dict - potentially merging it with a previous fill_path twin
478 jm_append_merge(out, dev->method);
479 dev->seqno += 1;
480 }
481
482 static void
483 jm_lineart_clip_path(fz_context *ctx, fz_device *dev_, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor)
484 {
485 jm_lineart_device *dev = (jm_lineart_device *)dev_;
486 if (!dev->clips) return;
487 PyObject *out = dev->out;
488 trace_device_ctm = ctm; //fz_concat(ctm, trace_device_ptm);
489 path_type = CLIP_PATH;
490 jm_lineart_path(ctx, dev, path);
491 if (!dev_pathdict) {
492 return;
493 }
494 DICT_SETITEM_DROP(dev_pathdict, dictkey_type, PyUnicode_FromString("clip"));
495 DICT_SETITEMSTR_DROP(dev_pathdict, "even_odd", JM_BOOL(even_odd));
496 if (!PyDict_GetItemString(dev_pathdict, "closePath")) {
497 DICT_SETITEMSTR_DROP(dev_pathdict, "closePath", JM_BOOL(0));
498 }
499 DICT_SETITEMSTR_DROP(dev_pathdict, "scissor", JM_py_from_rect(compute_scissor()));
500 DICT_SETITEMSTR_DROP(dev_pathdict, "level", PyLong_FromLong(dev->depth));
501 DICT_SETITEMSTR_DROP(dev_pathdict, "layer", JM_UnicodeFromStr(layer_name));
502 jm_append_merge(out, dev->method);
503 dev->depth++;
504 }
505
506 static void
507 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)
508 {
509 jm_lineart_device *dev = (jm_lineart_device *)dev_;
510 if (!dev->clips) return;
511 PyObject *out = dev->out;
512 trace_device_ctm = ctm; //fz_concat(ctm, trace_device_ptm);
513 path_type = CLIP_STROKE_PATH;
514 jm_lineart_path(ctx, dev, path);
515 if (!dev_pathdict) {
516 return;
517 }
518 DICT_SETITEM_DROP(dev_pathdict, dictkey_type, PyUnicode_FromString("clip"));
519 DICT_SETITEMSTR_DROP(dev_pathdict, "even_odd", Py_BuildValue("s", NULL));
520 if (!PyDict_GetItemString(dev_pathdict, "closePath")) {
521 DICT_SETITEMSTR_DROP(dev_pathdict, "closePath", JM_BOOL(0));
522 }
523 DICT_SETITEMSTR_DROP(dev_pathdict, "scissor", JM_py_from_rect(compute_scissor()));
524 DICT_SETITEMSTR_DROP(dev_pathdict, "level", PyLong_FromLong(dev->depth));
525 DICT_SETITEMSTR_DROP(dev_pathdict, "layer", JM_UnicodeFromStr(layer_name));
526 jm_append_merge(out, dev->method);
527 dev->depth++;
528 }
529
530 static void
531 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)
532 {
533 jm_lineart_device *dev = (jm_lineart_device *)dev_;
534 if (!dev->clips) return;
535 PyObject *out = dev->out;
536 compute_scissor();
537 dev->depth++;
538 }
539
540 static void
541 jm_lineart_clip_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm, fz_rect scissor)
542 {
543 jm_lineart_device *dev = (jm_lineart_device *)dev_;
544 if (!dev->clips) return;
545 PyObject *out = dev->out;
546 compute_scissor();
547 dev->depth++;
548 }
549
550 static void
551 jm_lineart_clip_image_mask(fz_context *ctx, fz_device *dev_, fz_image *image, fz_matrix ctm, fz_rect scissor)
552 {
553 jm_lineart_device *dev = (jm_lineart_device *)dev_;
554 if (!dev->clips) return;
555 PyObject *out = dev->out;
556 compute_scissor();
557 dev->depth++;
558 }
559
560 static void
561 jm_lineart_pop_clip(fz_context *ctx, fz_device *dev_)
562 {
563 jm_lineart_device *dev = (jm_lineart_device *)dev_;
564 if (!dev->clips) return;
565 if (!scissors) return;
566 Py_ssize_t len = PyList_Size(scissors);
567 if (len < 1) return;
568 PyList_SetSlice(scissors, len - 1, len, NULL);
569 dev->depth--;
570 }
571
572
573 static void
574 jm_lineart_begin_layer(fz_context *ctx, fz_device *dev_, const char *name)
575 {
576 layer_name = fz_strdup(ctx, name);
577 }
578
579 static void
580 jm_lineart_end_layer(fz_context *ctx, fz_device *dev_)
581 {
582 fz_free(ctx, layer_name);
583 layer_name = NULL;
584 }
585
586 static void
587 jm_lineart_begin_group(fz_context *ctx, fz_device *dev_, fz_rect bbox, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha)
588 {
589 jm_lineart_device *dev = (jm_lineart_device *)dev_;
590 if (!dev->clips) return;
591 PyObject *out = dev->out;
592 dev_pathdict = Py_BuildValue("{s:s,s:N,s:N,s:N,s:s,s:f,s:i,s:N}",
593 "type", "group",
594 "rect", JM_py_from_rect(bbox),
595 "isolated", JM_BOOL(isolated),
596 "knockout", JM_BOOL(knockout),
597 "blendmode", fz_blendmode_name(blendmode),
598 "opacity", alpha,
599 "level", dev->depth,
600 "layer", JM_UnicodeFromStr(layer_name)
601 );
602 jm_append_merge(out, dev->method);
603 dev->depth++;
604 }
605
606 static void
607 jm_lineart_end_group(fz_context *ctx, fz_device *dev_)
608 {
609 jm_lineart_device *dev = (jm_lineart_device *)dev_;
610 if (!dev->clips) return;
611 dev->depth--;
612 }
613
614
615 static void
616 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)
617 {
618 dev_linewidth = stroke->linewidth;
619 jm_increase_seqno(ctx, dev_);
620 }
621
622
623 static void
624 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)
625 {
626 fz_font *out_font = NULL;
627 int i;
628 const char *fontname = JM_font_name(ctx, span->font);
629 float rgb[3];
630 PyObject *chars = PyTuple_New(span->len);
631 fz_matrix mat = fz_concat(span->trm, ctm); // text transformation matrix
632 fz_point dir = fz_transform_vector(fz_make_point(1, 0), mat); // writing direction
633 double fsize = sqrt(dir.x * dir.x + dir.y * dir.y);
634
635 dir = fz_normalize_vector(dir);
636 double linewidth, adv, asc, dsc;
637 double space_adv = 0;
638 float x0, y0, x1, y1;
639 asc = (double) JM_font_ascender(ctx, span->font);
640 dsc = (double) JM_font_descender(ctx, span->font);
641 if (asc < 1e-3) { // probably Tesseract font
642 dsc = -0.1;
643 asc = 0.9;
644 }
645 // compute effective ascender / descender
646 double ascsize = asc * fsize / (asc - dsc);
647 double dscsize = dsc * fsize / (asc - dsc);
648
649 int fflags = 0; // font flags
650 int mono = fz_font_is_monospaced(ctx, span->font);
651 fflags += mono * TEXT_FONT_MONOSPACED;
652 fflags += fz_font_is_italic(ctx, span->font) * TEXT_FONT_ITALIC;
653 fflags += fz_font_is_serif(ctx, span->font) * TEXT_FONT_SERIFED;
654 fflags += fz_font_is_bold(ctx, span->font) * TEXT_FONT_BOLD;
655
656 if (dev_linewidth > 0) { // width of character border
657 linewidth = (double) dev_linewidth;
658 } else {
659 linewidth = fsize * 0.05; // default: 5% of font size
660 }
661 fz_point char_orig;
662 double last_adv = 0;
663
664 // walk through characters of span
665 fz_rect span_bbox;
666 fz_matrix rot = fz_make_matrix(dir.x, dir.y, -dir.y, dir.x, 0, 0);
667 if (dir.x == -1) { // left-right flip
668 rot.d = 1;
669 }
670
671 //PySys_WriteStdout("mat: (%g, %g, %g, %g)\n", mat.a, mat.b, mat.c, mat.d);
672 //PySys_WriteStdout("rot: (%g, %g, %g, %g)\n", rot.a, rot.b, rot.c, rot.d);
673
674 for (i = 0; i < span->len; i++) {
675 adv = 0;
676 if (span->items[i].gid >= 0) {
677 adv = (double) fz_advance_glyph(ctx, span->font, span->items[i].gid, span->wmode);
678 }
679 adv *= fsize;
680 last_adv = adv;
681 if (span->items[i].ucs == 32) {
682 space_adv = adv;
683 }
684 char_orig = fz_make_point(span->items[i].x, span->items[i].y);
685 char_orig = fz_transform_point(char_orig, ctm);
686 fz_matrix m1 = fz_make_matrix(1, 0, 0, 1, -char_orig.x, -char_orig.y);
687 m1 = fz_concat(m1, rot);
688 m1 = fz_concat(m1, fz_make_matrix(1, 0, 0, 1, char_orig.x, char_orig.y));
689 x0 = char_orig.x;
690 x1 = x0 + adv;
691 if (mat.d > 0 && (dir.x == 1 || dir.x == -1) ||
692 mat.b !=0 && mat.b == -mat.c) { // up-down flip
693 y0 = char_orig.y + dscsize;
694 y1 = char_orig.y + ascsize;
695 } else {
696 y0 = char_orig.y - ascsize;
697 y1 = char_orig.y - dscsize;
698 }
699 fz_rect char_bbox = fz_make_rect(x0, y0, x1, y1);
700 char_bbox = fz_transform_rect(char_bbox, m1);
701 PyTuple_SET_ITEM(chars, (Py_ssize_t) i, Py_BuildValue("ii(ff)(ffff)",
702 span->items[i].ucs, span->items[i].gid,
703 char_orig.x, char_orig.y, char_bbox.x0, char_bbox.y0, char_bbox.x1, char_bbox.y1));
704 if (i > 0) {
705 span_bbox = fz_union_rect(span_bbox, char_bbox);
706 } else {
707 span_bbox = char_bbox;
708 }
709 }
710 if (!space_adv) {
711 if (!mono) {
712 space_adv = fz_advance_glyph(ctx, span->font,
713 fz_encode_character_with_fallback(ctx, span->font, 32, 0, 0, &out_font),
714 span->wmode);
715 space_adv *= fsize;
716 if (!space_adv) {
717 space_adv = last_adv;
718 }
719 } else {
720 space_adv = last_adv; // for mono, any char width suffices
721 }
722 }
723 // make the span dictionary
724 PyObject *span_dict = PyDict_New();
725 DICT_SETITEMSTR_DROP(span_dict, "dir", JM_py_from_point(dir));
726 DICT_SETITEM_DROP(span_dict, dictkey_font, JM_EscapeStrFromStr(fontname));
727 DICT_SETITEM_DROP(span_dict, dictkey_wmode, PyLong_FromLong((long) span->wmode));
728 DICT_SETITEM_DROP(span_dict, dictkey_flags, PyLong_FromLong((long) fflags));
729 DICT_SETITEMSTR_DROP(span_dict, "bidi_lvl", PyLong_FromLong((long) span->bidi_level));
730 DICT_SETITEMSTR_DROP(span_dict, "bidi_dir", PyLong_FromLong((long) span->markup_dir));
731 DICT_SETITEM_DROP(span_dict, dictkey_ascender, PyFloat_FromDouble(asc));
732 DICT_SETITEM_DROP(span_dict, dictkey_descender, PyFloat_FromDouble(dsc));
733 DICT_SETITEM_DROP(span_dict, dictkey_colorspace, PyLong_FromLong(3));
734
735 if (colorspace) {
736 fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx),
737 rgb, NULL, fz_default_color_params);
738 } else {
739 rgb[0] = rgb[1] = rgb[2] = 0;
740 }
741
742 DICT_SETITEM_DROP(span_dict, dictkey_color, Py_BuildValue("fff", rgb[0], rgb[1], rgb[2]));
743 DICT_SETITEM_DROP(span_dict, dictkey_size, PyFloat_FromDouble(fsize));
744 DICT_SETITEMSTR_DROP(span_dict, "opacity", PyFloat_FromDouble((double) alpha));
745 DICT_SETITEMSTR_DROP(span_dict, "linewidth", PyFloat_FromDouble((double) linewidth));
746 DICT_SETITEMSTR_DROP(span_dict, "spacewidth", PyFloat_FromDouble(space_adv));
747 DICT_SETITEM_DROP(span_dict, dictkey_type, PyLong_FromLong((long) type));
748 DICT_SETITEM_DROP(span_dict, dictkey_bbox, JM_py_from_rect(span_bbox));
749 DICT_SETITEMSTR_DROP(span_dict, "layer", JM_UnicodeFromStr(layer_name));
750 DICT_SETITEMSTR_DROP(span_dict, "seqno", PyLong_FromSize_t(seqno));
751 DICT_SETITEM_DROP(span_dict, dictkey_chars, chars);
752 LIST_APPEND_DROP(out, span_dict);
753 }
754
755 static void
756 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)
757 {
758 fz_text_span *span;
759 for (span = text->head; span; span = span->next)
760 jm_trace_text_span(ctx, out, span, type, ctm, colorspace, color, alpha, seqno);
761 }
762
763 /*---------------------------------------------------------
764 There are 3 text trace types:
765 0 - fill text (PDF Tr 0)
766 1 - stroke text (PDF Tr 1)
767 3 - ignore text (PDF Tr 3)
768 ---------------------------------------------------------*/
769 static void
770 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)
771 {
772 jm_lineart_device *dev = (jm_lineart_device *)dev_;
773 PyObject *out = dev->out;
774 jm_trace_text(ctx, out, text, 0, ctm, colorspace, color, alpha, dev->seqno);
775 dev->seqno += 1;
776 }
777
778 static void
779 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)
780 {
781 jm_lineart_device *dev = (jm_lineart_device *)dev_;
782 PyObject *out = dev->out;
783 jm_trace_text(ctx, out, text, 1, ctm, colorspace, color, alpha, dev->seqno);
784 dev->seqno += 1;
785 }
786
787
788 static void
789 jm_lineart_ignore_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm)
790 {
791 jm_lineart_device *dev = (jm_lineart_device *)dev_;
792 PyObject *out = dev->out;
793 jm_trace_text(ctx, out, text, 3, ctm, NULL, NULL, 1, dev->seqno);
794 dev->seqno += 1;
795 }
796
797 static void jm_lineart_drop_device(fz_context *ctx, fz_device *dev_)
798 {
799 jm_lineart_device *dev = (jm_lineart_device *)dev_;
800 if (PyList_Check(dev->out)) {
801 Py_CLEAR(dev->out);
802 }
803 Py_CLEAR(dev->method);
804 Py_CLEAR(scissors);
805 }
806
807 //-------------------------------------------------------------------
808 // LINEART device for Python method Page.get_cdrawings()
809 //-------------------------------------------------------------------
810 fz_device *JM_new_lineart_device(fz_context *ctx, PyObject *out, int clips, PyObject *method)
811 {
812 jm_lineart_device *dev = fz_new_derived_device(ctx, jm_lineart_device);
813
814 dev->super.close_device = NULL;
815 dev->super.drop_device = jm_lineart_drop_device;
816 dev->super.fill_path = jm_lineart_fill_path;
817 dev->super.stroke_path = jm_lineart_stroke_path;
818 dev->super.clip_path = jm_lineart_clip_path;
819 dev->super.clip_stroke_path = jm_lineart_clip_stroke_path;
820
821 dev->super.fill_text = jm_increase_seqno;
822 dev->super.stroke_text = jm_increase_seqno;
823 dev->super.clip_text = jm_lineart_clip_text;
824 dev->super.clip_stroke_text = jm_lineart_clip_stroke_text;
825 dev->super.ignore_text = jm_increase_seqno;
826
827 dev->super.fill_shade = jm_increase_seqno;
828 dev->super.fill_image = jm_increase_seqno;
829 dev->super.fill_image_mask = jm_increase_seqno;
830 dev->super.clip_image_mask = jm_lineart_clip_image_mask;
831
832 dev->super.pop_clip = jm_lineart_pop_clip;
833
834 dev->super.begin_mask = NULL;
835 dev->super.end_mask = NULL;
836 dev->super.begin_group = jm_lineart_begin_group;
837 dev->super.end_group = jm_lineart_end_group;
838
839 dev->super.begin_tile = NULL;
840 dev->super.end_tile = NULL;
841
842 dev->super.begin_layer = jm_lineart_begin_layer;
843 dev->super.end_layer = jm_lineart_end_layer;
844
845 dev->super.begin_structure = NULL;
846 dev->super.end_structure = NULL;
847
848 dev->super.begin_metatext = NULL;
849 dev->super.end_metatext = NULL;
850
851 dev->super.render_flags = NULL;
852 dev->super.set_default_colorspaces = NULL;
853
854 if (PyList_Check(out)) {
855 Py_INCREF(out);
856 }
857 Py_INCREF(method);
858 dev->out = out;
859 dev->seqno = 0;
860 dev->depth = 0;
861 dev->clips = clips;
862 dev->method = method;
863 trace_device_reset();
864 return (fz_device *)dev;
865 }
866
867 //-------------------------------------------------------------------
868 // Trace TEXT device for Python method Page.get_texttrace()
869 //-------------------------------------------------------------------
870 fz_device *JM_new_texttrace_device(fz_context *ctx, PyObject *out)
871 {
872 jm_lineart_device *dev = fz_new_derived_device(ctx, jm_lineart_device);
873
874 dev->super.close_device = NULL;
875 dev->super.drop_device = jm_lineart_drop_device;
876 dev->super.fill_path = jm_increase_seqno;
877 dev->super.stroke_path = jm_dev_linewidth;
878 dev->super.clip_path = NULL;
879 dev->super.clip_stroke_path = NULL;
880
881 dev->super.fill_text = jm_lineart_fill_text;
882 dev->super.stroke_text = jm_lineart_stroke_text;
883 dev->super.clip_text = NULL;
884 dev->super.clip_stroke_text = NULL;
885 dev->super.ignore_text = jm_lineart_ignore_text;
886
887 dev->super.fill_shade = jm_increase_seqno;
888 dev->super.fill_image = jm_increase_seqno;
889 dev->super.fill_image_mask = jm_increase_seqno;
890 dev->super.clip_image_mask = NULL;
891
892 dev->super.pop_clip = NULL;
893
894 dev->super.begin_mask = NULL;
895 dev->super.end_mask = NULL;
896 dev->super.begin_group = NULL;
897 dev->super.end_group = NULL;
898
899 dev->super.begin_tile = NULL;
900 dev->super.end_tile = NULL;
901
902 dev->super.begin_layer = jm_lineart_begin_layer;
903 dev->super.end_layer = jm_lineart_end_layer;
904
905 dev->super.begin_structure = NULL;
906 dev->super.end_structure = NULL;
907
908 dev->super.begin_metatext = NULL;
909 dev->super.end_metatext = NULL;
910
911 dev->super.render_flags = NULL;
912 dev->super.set_default_colorspaces = NULL;
913
914 if (PyList_Check(out)) {
915 Py_XINCREF(out);
916 }
917 dev->out = out;
918 dev->seqno = 0;
919 dev->depth = 0;
920 dev->clips = 0;
921 dev->method = NULL;
922 trace_device_reset();
923
924 return (fz_device *)dev;
925 }
926
927 //-------------------------------------------------------------------
928 // BBOX device
929 //-------------------------------------------------------------------
930 typedef struct jm_bbox_device_s
931 {
932 fz_device super;
933 PyObject *result;
934 int layers;
935 } jm_bbox_device;
936
937 static void
938 jm_bbox_add_rect(fz_context *ctx, fz_device *dev, fz_rect rect, char *code)
939 {
940 jm_bbox_device *bdev = (jm_bbox_device *)dev;
941 if (!bdev->layers) {
942 LIST_APPEND_DROP(bdev->result, Py_BuildValue("sN", code, JM_py_from_rect(rect)));
943 } else {
944 LIST_APPEND_DROP(bdev->result, Py_BuildValue("sNN", code, JM_py_from_rect(rect), JM_UnicodeFromStr(layer_name)));
945 }
946 }
947
948 static void
949 jm_bbox_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm,
950 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
951 {
952 jm_bbox_add_rect(ctx, dev, fz_bound_path(ctx, path, NULL, ctm), "fill-path");
953 }
954
955 static void
956 jm_bbox_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke,
957 fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
958 {
959 jm_bbox_add_rect(ctx, dev, fz_bound_path(ctx, path, stroke, ctm), "stroke-path");
960 }
961
962 static void
963 jm_bbox_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, ...)
964 {
965 jm_bbox_add_rect(ctx, dev, fz_bound_text(ctx, text, NULL, ctm), "fill-text");
966 }
967
968 static void
969 jm_bbox_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm)
970 {
971 jm_bbox_add_rect(ctx, dev, fz_bound_text(ctx, text, NULL, ctm), "ignore-text");
972 }
973
974 static void
975 jm_bbox_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, ...)
976 {
977 jm_bbox_add_rect(ctx, dev, fz_bound_text(ctx, text, stroke, ctm), "stroke-text");
978 }
979
980 static void
981 jm_bbox_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params)
982 {
983 jm_bbox_add_rect(ctx, dev, fz_bound_shade(ctx, shade, ctm), "fill-shade");
984 }
985
986 static void
987 jm_bbox_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params)
988 {
989 jm_bbox_add_rect(ctx, dev, fz_transform_rect(fz_unit_rect, ctm), "fill-image");
990 }
991
992 static void
993 jm_bbox_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm,
994 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
995 {
996 jm_bbox_add_rect(ctx, dev, fz_transform_rect(fz_unit_rect, ctm), "fill-imgmask");
997 }
998
999 fz_device *
1000 JM_new_bbox_device(fz_context *ctx, PyObject *result, int layers)
1001 {
1002 jm_bbox_device *dev = fz_new_derived_device(ctx, jm_bbox_device);
1003
1004 dev->super.fill_path = jm_bbox_fill_path;
1005 dev->super.stroke_path = jm_bbox_stroke_path;
1006 dev->super.clip_path = NULL;
1007 dev->super.clip_stroke_path = NULL;
1008
1009 dev->super.fill_text = jm_bbox_fill_text;
1010 dev->super.stroke_text = jm_bbox_stroke_text;
1011 dev->super.clip_text = NULL;
1012 dev->super.clip_stroke_text = NULL;
1013 dev->super.ignore_text = jm_bbox_ignore_text;
1014
1015 dev->super.fill_shade = jm_bbox_fill_shade;
1016 dev->super.fill_image = jm_bbox_fill_image;
1017 dev->super.fill_image_mask = jm_bbox_fill_image_mask;
1018 dev->super.clip_image_mask = NULL;
1019
1020 dev->super.pop_clip = NULL;
1021
1022 dev->super.begin_mask = NULL;
1023 dev->super.end_mask = NULL;
1024 dev->super.begin_group = NULL;
1025 dev->super.end_group = NULL;
1026
1027 dev->super.begin_tile = NULL;
1028 dev->super.end_tile = NULL;
1029
1030 dev->super.begin_layer = jm_lineart_begin_layer;
1031 dev->super.end_layer = jm_lineart_end_layer;
1032
1033 dev->super.begin_structure = NULL;
1034 dev->super.end_structure = NULL;
1035
1036 dev->super.begin_metatext = NULL;
1037 dev->super.end_metatext = NULL;
1038
1039 dev->super.render_flags = NULL;
1040 dev->super.set_default_colorspaces = NULL;
1041
1042 dev->result = result;
1043 dev->layers = layers;
1044 trace_device_reset();
1045
1046 return (fz_device *)dev;
1047 }
1048
1049 %}