comparison src_classic/fitz_old.i @ 3:2c135c81b16c

MERGE: upstream PyMuPDF 1.26.4 with MuPDF 1.26.7
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:44:09 +0200
parents 1d09e1dec1d9
children
comparison
equal deleted inserted replaced
0:6015a75abc2d 3:2c135c81b16c
1 %module fitz
2 %pythonbegin %{
3 %}
4 //------------------------------------------------------------------------
5 // SWIG macros: handle fitz exceptions
6 //------------------------------------------------------------------------
7 %define FITZEXCEPTION(meth, cond)
8 %exception meth
9 {
10 $action
11 if (cond) {
12 return JM_ReturnException(gctx);
13 }
14 }
15 %enddef
16
17
18 %define FITZEXCEPTION2(meth, cond)
19 %exception meth
20 {
21 $action
22 if (cond) {
23 const char *msg = fz_caught_message(gctx);
24 if (strcmp(msg, MSG_BAD_FILETYPE) == 0) {
25 PyErr_SetString(PyExc_ValueError, msg);
26 } else {
27 PyErr_SetString(JM_Exc_FileDataError, MSG_BAD_DOCUMENT);
28 }
29 return NULL;
30 }
31 }
32 %enddef
33
34 //------------------------------------------------------------------------
35 // SWIG macro: check that a document is not closed / encrypted
36 //------------------------------------------------------------------------
37 %define CLOSECHECK(meth, doc)
38 %pythonprepend meth %{doc
39 if self.is_closed or self.is_encrypted:
40 raise ValueError("document closed or encrypted")%}
41 %enddef
42
43 %define CLOSECHECK0(meth, doc)
44 %pythonprepend meth%{doc
45 if self.is_closed:
46 raise ValueError("document closed")%}
47 %enddef
48
49 //------------------------------------------------------------------------
50 // SWIG macro: check if object has a valid parent
51 //------------------------------------------------------------------------
52 %define PARENTCHECK(meth, doc)
53 %pythonprepend meth %{doc
54 CheckParent(self)%}
55 %enddef
56
57
58 //------------------------------------------------------------------------
59 // SWIG macro: ensure object still exists
60 //------------------------------------------------------------------------
61 %define ENSURE_OWNERSHIP(meth, doc)
62 %pythonprepend meth %{doc
63 EnsureOwnership(self)%}
64 %enddef
65
66 %include "mupdf/fitz/version.h"
67
68 %{
69 #define MEMDEBUG 0
70 #if MEMDEBUG == 1
71 #define DEBUGMSG1(x) PySys_WriteStderr("[DEBUG] free %s ", x)
72 #define DEBUGMSG2 PySys_WriteStderr("... done!\n")
73 #else
74 #define DEBUGMSG1(x)
75 #define DEBUGMSG2
76 #endif
77
78 #ifndef FLT_EPSILON
79 #define FLT_EPSILON 1e-5
80 #endif
81
82 #define SWIG_FILE_WITH_INIT
83
84 // JM_MEMORY controls what allocators we tell MuPDF to use when we call
85 // fz_new_context():
86 //
87 // JM_MEMORY=0: MuPDF uses malloc()/free().
88 // JM_MEMORY=1: MuPDF uses PyMem_Malloc()/PyMem_Free().
89 //
90 // There are also a small number of places where we call malloc() or
91 // PyMem_Malloc() ourselves, depending on JM_MEMORY.
92 //
93 #define JM_MEMORY 0
94
95 #if JM_MEMORY == 1
96 #define JM_Alloc(type, len) PyMem_New(type, len)
97 #define JM_Free(x) PyMem_Del(x)
98 #else
99 #define JM_Alloc(type, len) (type *) malloc(sizeof(type)*len)
100 #define JM_Free(x) free(x)
101 #endif
102
103 #define EMPTY_STRING PyUnicode_FromString("")
104 #define EXISTS(x) (x != NULL && PyObject_IsTrue(x)==1)
105 #define RAISEPY(context, msg, exc) {JM_Exc_CurrentException=exc; fz_throw(context, FZ_ERROR_GENERIC, msg);}
106 #define ASSERT_PDF(cond) if (cond == NULL) RAISEPY(gctx, MSG_IS_NO_PDF, PyExc_RuntimeError)
107 #define ENSURE_OPERATION(ctx, pdf) if (!JM_have_operation(ctx, pdf)) RAISEPY(ctx, "No journalling operation started", PyExc_RuntimeError)
108 #define INRANGE(v, low, high) ((low) <= v && v <= (high))
109 #define JM_BOOL(x) PyBool_FromLong((long) (x))
110 #define JM_PyErr_Clear if (PyErr_Occurred()) PyErr_Clear()
111
112 #define JM_StrAsChar(x) (char *)PyUnicode_AsUTF8(x)
113 #define JM_BinFromChar(x) PyBytes_FromString(x)
114 #define JM_BinFromCharSize(x, y) PyBytes_FromStringAndSize(x, (Py_ssize_t) y)
115
116 #include <mupdf/fitz.h>
117 #include <mupdf/pdf.h>
118 #include <time.h>
119 // freetype includes >> --------------------------------------------------
120 #include <ft2build.h>
121 #include FT_FREETYPE_H
122 #ifdef FT_FONT_FORMATS_H
123 #include FT_FONT_FORMATS_H
124 #else
125 #include FT_XFREE86_H
126 #endif
127 #include FT_TRUETYPE_TABLES_H
128
129 #ifndef FT_SFNT_HEAD
130 #define FT_SFNT_HEAD ft_sfnt_head
131 #endif
132 // << freetype includes --------------------------------------------------
133
134 void JM_delete_widget(fz_context *ctx, pdf_page *page, pdf_annot *annot);
135 static void JM_get_page_labels(fz_context *ctx, PyObject *liste, pdf_obj *nums);
136 static int DICT_SETITEMSTR_DROP(PyObject *dict, const char *key, PyObject *value);
137 static int LIST_APPEND_DROP(PyObject *list, PyObject *item);
138 static int LIST_APPEND_DROP(PyObject *list, PyObject *item);
139 static fz_irect JM_irect_from_py(PyObject *r);
140 static fz_matrix JM_matrix_from_py(PyObject *m);
141 static fz_point JM_normalize_vector(float x, float y);
142 static fz_point JM_point_from_py(PyObject *p);
143 static fz_quad JM_quad_from_py(PyObject *r);
144 static fz_rect JM_rect_from_py(PyObject *r);
145 static int JM_FLOAT_ITEM(PyObject *obj, Py_ssize_t idx, double *result);
146 static int JM_INT_ITEM(PyObject *obj, Py_ssize_t idx, int *result);
147 static PyObject *JM_py_from_irect(fz_irect r);
148 static PyObject *JM_py_from_matrix(fz_matrix m);
149 static PyObject *JM_py_from_point(fz_point p);
150 static PyObject *JM_py_from_quad(fz_quad q);
151 static PyObject *JM_py_from_rect(fz_rect r);
152 static void show(const char* prefix, PyObject* obj);
153
154
155 // additional headers ----------------------------------------------
156 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR == 23 && FZ_VERSION_PATCH < 8
157 pdf_obj *pdf_lookup_page_loc(fz_context *ctx, pdf_document *doc, int needle, pdf_obj **parentp, int *indexp);
158 fz_pixmap *fz_scale_pixmap(fz_context *ctx, fz_pixmap *src, float x, float y, float w, float h, const fz_irect *clip);
159 int fz_pixmap_size(fz_context *ctx, fz_pixmap *src);
160 void fz_subsample_pixmap(fz_context *ctx, fz_pixmap *tile, int factor);
161 void fz_copy_pixmap_rect(fz_context *ctx, fz_pixmap *dest, fz_pixmap *src, fz_irect b, const fz_default_colorspaces *default_cs);
162 void fz_write_pixmap_as_jpeg(fz_context *ctx, fz_output *out, fz_pixmap *pix, int jpg_quality);
163 #endif
164 static const float JM_font_ascender(fz_context *ctx, fz_font *font);
165 static const float JM_font_descender(fz_context *ctx, fz_font *font);
166 // end of additional headers --------------------------------------------
167
168 static PyObject *JM_mupdf_warnings_store;
169 static int JM_mupdf_show_errors;
170 static int JM_mupdf_show_warnings;
171 static PyObject *JM_Exc_FileDataError;
172 static PyObject *JM_Exc_CurrentException;
173 %}
174
175 //------------------------------------------------------------------------
176 // global context
177 //------------------------------------------------------------------------
178 %init %{
179 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR >= 22
180 /* Stop Memento backtraces if we reach the Python interpreter.
181 `cfunction_call()` isn't the only way that Python calls C though, so we
182 might need extra calls to Memento_addBacktraceLimitFnname().
183
184 We put this inside `#ifdef MEMENTO` because memento.h's disabling macro
185 causes "warning: statement with no effect" from cc. */
186 #ifdef MEMENTO
187 Memento_addBacktraceLimitFnname("cfunction_call");
188 #endif
189 #endif
190
191 /*
192 We end up with Memento leaks from fz_new_context()'s allocs even when our
193 atexit handler calls fz_drop_context(), so remove these from Memento's
194 accounting.
195 */
196 Memento_startLeaking();
197 #if JM_MEMORY == 1
198 gctx = fz_new_context(&JM_Alloc_Context, NULL, FZ_STORE_DEFAULT);
199 #else
200 gctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT);
201 #endif
202 Memento_stopLeaking();
203 if(!gctx)
204 {
205 PyErr_SetString(PyExc_RuntimeError, "Fatal error: cannot create global context.");
206 return NULL;
207 }
208 fz_register_document_handlers(gctx);
209
210 //------------------------------------------------------------------------
211 // START redirect stdout/stderr
212 //------------------------------------------------------------------------
213 JM_mupdf_warnings_store = PyList_New(0);
214 JM_mupdf_show_errors = 1;
215 JM_mupdf_show_warnings = 0;
216 char user[] = "PyMuPDF";
217 fz_set_warning_callback(gctx, JM_mupdf_warning, &user);
218 fz_set_error_callback(gctx, JM_mupdf_error, &user);
219 JM_Exc_FileDataError = NULL;
220 JM_Exc_CurrentException = PyExc_RuntimeError;
221 //------------------------------------------------------------------------
222 // STOP redirect stdout/stderr
223 //------------------------------------------------------------------------
224 // init global constants
225 //------------------------------------------------------------------------
226 dictkey_align = PyUnicode_InternFromString("align");
227 dictkey_ascender = PyUnicode_InternFromString("ascender");
228 dictkey_bbox = PyUnicode_InternFromString("bbox");
229 dictkey_blocks = PyUnicode_InternFromString("blocks");
230 dictkey_bpc = PyUnicode_InternFromString("bpc");
231 dictkey_c = PyUnicode_InternFromString("c");
232 dictkey_chars = PyUnicode_InternFromString("chars");
233 dictkey_color = PyUnicode_InternFromString("color");
234 dictkey_colorspace = PyUnicode_InternFromString("colorspace");
235 dictkey_content = PyUnicode_InternFromString("content");
236 dictkey_creationDate = PyUnicode_InternFromString("creationDate");
237 dictkey_cs_name = PyUnicode_InternFromString("cs-name");
238 dictkey_da = PyUnicode_InternFromString("da");
239 dictkey_dashes = PyUnicode_InternFromString("dashes");
240 dictkey_desc = PyUnicode_InternFromString("desc");
241 dictkey_desc = PyUnicode_InternFromString("descender");
242 dictkey_descender = PyUnicode_InternFromString("descender");
243 dictkey_dir = PyUnicode_InternFromString("dir");
244 dictkey_effect = PyUnicode_InternFromString("effect");
245 dictkey_ext = PyUnicode_InternFromString("ext");
246 dictkey_filename = PyUnicode_InternFromString("filename");
247 dictkey_fill = PyUnicode_InternFromString("fill");
248 dictkey_flags = PyUnicode_InternFromString("flags");
249 dictkey_font = PyUnicode_InternFromString("font");
250 dictkey_glyph = PyUnicode_InternFromString("glyph");
251 dictkey_height = PyUnicode_InternFromString("height");
252 dictkey_id = PyUnicode_InternFromString("id");
253 dictkey_image = PyUnicode_InternFromString("image");
254 dictkey_items = PyUnicode_InternFromString("items");
255 dictkey_length = PyUnicode_InternFromString("length");
256 dictkey_lines = PyUnicode_InternFromString("lines");
257 dictkey_matrix = PyUnicode_InternFromString("transform");
258 dictkey_modDate = PyUnicode_InternFromString("modDate");
259 dictkey_name = PyUnicode_InternFromString("name");
260 dictkey_number = PyUnicode_InternFromString("number");
261 dictkey_origin = PyUnicode_InternFromString("origin");
262 dictkey_rect = PyUnicode_InternFromString("rect");
263 dictkey_size = PyUnicode_InternFromString("size");
264 dictkey_smask = PyUnicode_InternFromString("smask");
265 dictkey_spans = PyUnicode_InternFromString("spans");
266 dictkey_stroke = PyUnicode_InternFromString("stroke");
267 dictkey_style = PyUnicode_InternFromString("style");
268 dictkey_subject = PyUnicode_InternFromString("subject");
269 dictkey_text = PyUnicode_InternFromString("text");
270 dictkey_title = PyUnicode_InternFromString("title");
271 dictkey_type = PyUnicode_InternFromString("type");
272 dictkey_ufilename = PyUnicode_InternFromString("ufilename");
273 dictkey_width = PyUnicode_InternFromString("width");
274 dictkey_wmode = PyUnicode_InternFromString("wmode");
275 dictkey_xref = PyUnicode_InternFromString("xref");
276 dictkey_xres = PyUnicode_InternFromString("xres");
277 dictkey_yres = PyUnicode_InternFromString("yres");
278
279 atexit( cleanup);
280 %}
281
282 %header %{
283 fz_context *gctx;
284
285 static void cleanup()
286 {
287 fz_drop_context( gctx);
288 }
289
290 static int JM_UNIQUE_ID = 0;
291
292 struct DeviceWrapper {
293 fz_device *device;
294 fz_display_list *list;
295 };
296 %}
297
298 //------------------------------------------------------------------------
299 // include version information and several other helpers
300 //------------------------------------------------------------------------
301 %pythoncode %{
302 import sys
303 import io
304 import math
305 import os
306 import weakref
307 import hashlib
308 import typing
309 import binascii
310 import re
311 import tarfile
312 import zipfile
313 import pathlib
314 import string
315
316 # PDF names must not contain these characters:
317 INVALID_NAME_CHARS = set(string.whitespace + "()<>[]{}/%" + chr(0))
318
319 TESSDATA_PREFIX = os.getenv("TESSDATA_PREFIX")
320 point_like = "point_like"
321 rect_like = "rect_like"
322 matrix_like = "matrix_like"
323 quad_like = "quad_like"
324
325 # ByteString is gone from typing in 3.14.
326 # collections.abc.Buffer available from 3.12 only
327 try:
328 ByteString = typing.ByteString
329 except AttributeError:
330 ByteString = bytes | bytearray | memoryview
331
332 AnyType = typing.Any
333 OptInt = typing.Union[int, None]
334 OptFloat = typing.Optional[float]
335 OptStr = typing.Optional[str]
336 OptDict = typing.Optional[dict]
337 OptBytes = typing.Optional[ByteString]
338 OptSeq = typing.Optional[typing.Sequence]
339
340 try:
341 from pymupdf_fonts import fontdescriptors, fontbuffers
342
343 fitz_fontdescriptors = fontdescriptors.copy()
344 for k in fitz_fontdescriptors.keys():
345 fitz_fontdescriptors[k]["loader"] = fontbuffers[k]
346 del fontdescriptors, fontbuffers
347 except ImportError:
348 fitz_fontdescriptors = {}
349 %}
350 %include version.i
351 %include helper-git-versions.i
352 %include helper-defines.i
353 %include helper-globals.i
354 %include helper-geo-c.i
355 %include helper-other.i
356 %include helper-pixmap.i
357 %include helper-geo-py.i
358 %include helper-annot.i
359 %include helper-fields.i
360 %include helper-python.i
361 %include helper-portfolio.i
362 %include helper-select.i
363 %include helper-stext.i
364 %include helper-xobject.i
365 %include helper-pdfinfo.i
366 %include helper-convert.i
367 %include helper-fileobj.i
368 %include helper-devices.i
369
370 %{
371 // Declaring these structs here prevents gcc from generating warnings like:
372 //
373 // warning: 'struct Document' declared inside parameter list will not be visible outside of this definition or declaration
374 //
375 struct Colorspace;
376 struct Document;
377 struct Font;
378 struct Graftmap;
379 struct TextPage;
380 struct TextWriter;
381 struct DocumentWriter;
382 struct Xml;
383 struct Archive;
384 struct Story;
385 %}
386
387 //------------------------------------------------------------------------
388 // fz_document
389 //------------------------------------------------------------------------
390 struct Document
391 {
392 %extend
393 {
394 ~Document()
395 {
396 DEBUGMSG1("Document");
397 fz_document *this_doc = (fz_document *) $self;
398 fz_drop_document(gctx, this_doc);
399 DEBUGMSG2;
400 }
401 FITZEXCEPTION2(Document, !result)
402
403 %pythonprepend Document %{
404 """Creates a document. Use 'open' as a synonym.
405
406 Notes:
407 Basic usages:
408 open() - new PDF document
409 open(filename) - string, pathlib.Path, or file object.
410 open(filename, fileype=type) - overwrite filename extension.
411 open(type, buffer) - type: extension, buffer: bytes object.
412 open(stream=buffer, filetype=type) - keyword version of previous.
413 Parameters rect, width, height, fontsize: layout reflowable
414 document on open (e.g. EPUB). Ignored if n/a.
415 """
416 self.is_closed = False
417 self.is_encrypted = False
418 self.isEncrypted = False
419 self.metadata = None
420 self.FontInfos = []
421 self.Graftmaps = {}
422 self.ShownPages = {}
423 self.InsertedImages = {}
424 self._page_refs = weakref.WeakValueDictionary()
425
426 if not filename or type(filename) is str:
427 pass
428 elif hasattr(filename, "absolute"):
429 filename = str(filename)
430 elif hasattr(filename, "name"):
431 filename = filename.name
432 else:
433 msg = "bad filename"
434 raise TypeError(msg)
435
436 if stream != None:
437 if type(stream) is bytes:
438 self.stream = stream
439 elif type(stream) is bytearray:
440 self.stream = bytes(stream)
441 elif type(stream) is io.BytesIO:
442 self.stream = stream.getvalue()
443 else:
444 msg = "bad type: 'stream'"
445 raise TypeError(msg)
446 stream = self.stream
447 if not (filename or filetype):
448 filename = "pdf"
449 else:
450 self.stream = None
451
452 if filename and self.stream == None:
453 self.name = filename
454 from_file = True
455 else:
456 from_file = False
457 self.name = ""
458
459 if from_file:
460 if not os.path.exists(filename):
461 msg = f"no such file: '{filename}'"
462 raise FileNotFoundError(msg)
463 elif not os.path.isfile(filename):
464 msg = f"'{filename}' is no file"
465 raise FileDataError(msg)
466 if from_file and os.path.getsize(filename) == 0 or type(self.stream) is bytes and len(self.stream) == 0:
467 msg = "cannot open empty document"
468 raise EmptyFileError(msg)
469 %}
470 %pythonappend Document %{
471 if self.thisown:
472 self._graft_id = TOOLS.gen_id()
473 if self.needs_pass is True:
474 self.is_encrypted = True
475 self.isEncrypted = True
476 else: # we won't init until doc is decrypted
477 self.init_doc()
478 # the following hack detects invalid/empty SVG files, which else may lead
479 # to interpreter crashes
480 if filename and filename.lower().endswith("svg") or filetype and "svg" in filetype.lower():
481 try:
482 _ = self.convert_to_pdf() # this seems to always work
483 except:
484 raise FileDataError("cannot open broken document") from None
485 %}
486
487 Document(const char *filename=NULL, PyObject *stream=NULL,
488 const char *filetype=NULL, PyObject *rect=NULL,
489 float width=0, float height=0,
490 float fontsize=11)
491 {
492 int old_msg_option = JM_mupdf_show_errors;
493 JM_mupdf_show_errors = 0;
494 fz_document *doc = NULL;
495 const fz_document_handler *handler;
496 char *c = NULL;
497 char *magic = NULL;
498 size_t len = 0;
499 fz_stream *data = NULL;
500 float w = width, h = height;
501 fz_rect r = JM_rect_from_py(rect);
502 if (!fz_is_infinite_rect(r)) {
503 w = r.x1 - r.x0;
504 h = r.y1 - r.y0;
505 }
506
507 fz_try(gctx) {
508 if (stream != Py_None) { // stream given, **MUST** be bytes!
509 c = PyBytes_AS_STRING(stream); // just a pointer, no new obj
510 len = (size_t) PyBytes_Size(stream);
511 data = fz_open_memory(gctx, (const unsigned char *) c, len);
512 magic = (char *)filename;
513 if (!magic) magic = (char *)filetype;
514 handler = fz_recognize_document(gctx, magic);
515 if (!handler) {
516 RAISEPY(gctx, MSG_BAD_FILETYPE, PyExc_ValueError);
517 }
518 doc = fz_open_document_with_stream(gctx, magic, data);
519 } else {
520 if (filename && strlen(filename)) {
521 if (!filetype || strlen(filetype) == 0) {
522 doc = fz_open_document(gctx, filename);
523 } else {
524 handler = fz_recognize_document(gctx, filetype);
525 if (!handler) {
526 RAISEPY(gctx, MSG_BAD_FILETYPE, PyExc_ValueError);
527 }
528 #if FZ_VERSION_MINOR >= 24
529 if (handler->open)
530 {
531 fz_stream* filename_stream = fz_open_file(gctx, filename);
532 fz_try(gctx)
533 {
534 doc = handler->open(gctx, filename_stream, NULL, NULL);
535 }
536 fz_always(gctx)
537 {
538 fz_drop_stream(gctx, filename_stream);
539 }
540 fz_catch(gctx)
541 {
542 fz_rethrow(gctx);
543 }
544 }
545 #else
546 if (handler->open) {
547 doc = handler->open(gctx, filename);
548 } else if (handler->open_with_stream) {
549 data = fz_open_file(gctx, filename);
550 doc = handler->open_with_stream(gctx, data);
551 }
552 #endif
553 }
554 } else {
555 pdf_document *pdf = pdf_create_document(gctx);
556 doc = (fz_document *) pdf;
557 }
558 }
559 }
560 fz_always(gctx) {
561 fz_drop_stream(gctx, data);
562 }
563 fz_catch(gctx) {
564 JM_mupdf_show_errors = old_msg_option;
565 return NULL;
566 }
567 if (w > 0 && h > 0) {
568 fz_layout_document(gctx, doc, w, h, fontsize);
569 } else if (fz_is_document_reflowable(gctx, doc)) {
570 fz_layout_document(gctx, doc, 400, 600, 11);
571 }
572 return (struct Document *) doc;
573 }
574
575
576 FITZEXCEPTION(load_page, !result)
577 %pythonprepend load_page %{
578 """Load a page.
579
580 'page_id' is either a 0-based page number or a tuple (chapter, pno),
581 with chapter number and page number within that chapter.
582 """
583
584 if self.is_closed or self.is_encrypted:
585 raise ValueError("document closed or encrypted")
586 if page_id is None:
587 page_id = 0
588 if page_id not in self:
589 raise ValueError("page not in document")
590 if type(page_id) is int and page_id < 0:
591 np = self.page_count
592 while page_id < 0:
593 page_id += np
594 %}
595 %pythonappend load_page %{
596 val.thisown = True
597 val.parent = weakref.proxy(self)
598 self._page_refs[id(val)] = val
599 val._annot_refs = weakref.WeakValueDictionary()
600 val.number = page_id
601 %}
602 struct Page *
603 load_page(PyObject *page_id)
604 {
605 fz_page *page = NULL;
606 fz_document *doc = (fz_document *) $self;
607 int pno = 0, chapter = 0;
608 fz_try(gctx) {
609 if (PySequence_Check(page_id)) {
610 if (JM_INT_ITEM(page_id, 0, &chapter) == 1) {
611 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
612 }
613 if (JM_INT_ITEM(page_id, 1, &pno) == 1) {
614 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
615 }
616 page = fz_load_chapter_page(gctx, doc, chapter, pno);
617 } else {
618 pno = (int) PyLong_AsLong(page_id);
619 if (PyErr_Occurred()) {
620 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
621 }
622 page = fz_load_page(gctx, doc, pno);
623 }
624 }
625 fz_catch(gctx) {
626 PyErr_Clear();
627 return NULL;
628 }
629 PyErr_Clear();
630 return (struct Page *) page;
631 }
632
633
634 FITZEXCEPTION(_remove_links_to, !result)
635 PyObject *_remove_links_to(PyObject *numbers)
636 {
637 fz_try(gctx) {
638 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
639 remove_dest_range(gctx, pdf, numbers);
640 }
641 fz_catch(gctx) {
642 return NULL;
643 }
644 Py_RETURN_NONE;
645 }
646
647
648 CLOSECHECK0(_loadOutline, """Load first outline.""")
649 struct Outline *_loadOutline()
650 {
651 fz_outline *ol = NULL;
652 fz_document *doc = (fz_document *) $self;
653 fz_try(gctx) {
654 ol = fz_load_outline(gctx, doc);
655 }
656 fz_catch(gctx) {
657 return NULL;
658 }
659 return (struct Outline *) ol;
660 }
661
662 void _dropOutline(struct Outline *ol) {
663 DEBUGMSG1("Outline");
664 fz_outline *this_ol = (fz_outline *) ol;
665 fz_drop_outline(gctx, this_ol);
666 DEBUGMSG2;
667 }
668
669 FITZEXCEPTION(_insert_font, !result)
670 CLOSECHECK0(_insert_font, """Utility: insert font from file or binary.""")
671 PyObject *
672 _insert_font(char *fontfile=NULL, PyObject *fontbuffer=NULL)
673 {
674 PyObject *value=NULL;
675 pdf_document *pdf = pdf_specifics(gctx, (fz_document *)$self);
676
677 fz_try(gctx) {
678 ASSERT_PDF(pdf);
679 if (!fontfile && !EXISTS(fontbuffer)) {
680 RAISEPY(gctx, MSG_FILE_OR_BUFFER, PyExc_ValueError);
681 }
682 value = JM_insert_font(gctx, pdf, NULL, fontfile, fontbuffer,
683 0, 0, 0, 0, 0, -1);
684 }
685 fz_catch(gctx) {
686 return NULL;
687 }
688 return value;
689 }
690
691
692 FITZEXCEPTION(get_outline_xrefs, !result)
693 CLOSECHECK0(get_outline_xrefs, """Get list of outline xref numbers.""")
694 PyObject *
695 get_outline_xrefs()
696 {
697 PyObject *xrefs = PyList_New(0);
698 pdf_document *pdf = pdf_specifics(gctx, (fz_document *)$self);
699 if (!pdf) {
700 return xrefs;
701 }
702 fz_try(gctx) {
703 pdf_obj *root = pdf_dict_get(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root));
704 if (!root) goto finished;
705 pdf_obj *olroot = pdf_dict_get(gctx, root, PDF_NAME(Outlines));
706 if (!olroot) goto finished;
707 pdf_obj *first = pdf_dict_get(gctx, olroot, PDF_NAME(First));
708 if (!first) goto finished;
709 xrefs = JM_outline_xrefs(gctx, first, xrefs);
710 finished:;
711 }
712 fz_catch(gctx) {
713 Py_DECREF(xrefs);
714 return NULL;
715 }
716 return xrefs;
717 }
718
719
720 FITZEXCEPTION(xref_get_keys, !result)
721 CLOSECHECK0(xref_get_keys, """Get the keys of PDF dict object at 'xref'. Use -1 for the PDF trailer.""")
722 PyObject *
723 xref_get_keys(int xref)
724 {
725 pdf_document *pdf = pdf_specifics(gctx, (fz_document *)$self);
726 pdf_obj *obj=NULL;
727 PyObject *rc = NULL;
728 int i, n;
729 fz_try(gctx) {
730 ASSERT_PDF(pdf);
731 int xreflen = pdf_xref_len(gctx, pdf);
732 if (!INRANGE(xref, 1, xreflen-1) && xref != -1) {
733 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
734 }
735 if (xref > 0) {
736 obj = pdf_load_object(gctx, pdf, xref);
737 } else {
738 obj = pdf_trailer(gctx, pdf);
739 }
740 n = pdf_dict_len(gctx, obj);
741 rc = PyTuple_New(n);
742 if (!n) goto finished;
743 for (i = 0; i < n; i++) {
744 const char *key = pdf_to_name(gctx, pdf_dict_get_key(gctx, obj, i));
745 PyTuple_SET_ITEM(rc, i, Py_BuildValue("s", key));
746 }
747 finished:;
748 }
749 fz_always(gctx) {
750 if (xref > 0) {
751 pdf_drop_obj(gctx, obj);
752 }
753 }
754 fz_catch(gctx) {
755 return NULL;
756 }
757 return rc;
758 }
759
760
761 FITZEXCEPTION(xref_get_key, !result)
762 CLOSECHECK0(xref_get_key, """Get PDF dict key value of object at 'xref'.""")
763 PyObject *
764 xref_get_key(int xref, const char *key)
765 {
766 pdf_document *pdf = pdf_specifics(gctx, (fz_document *)$self);
767 pdf_obj *obj=NULL, *subobj=NULL;
768 PyObject *rc = NULL;
769 fz_buffer *res = NULL;
770 PyObject *text = NULL;
771 fz_try(gctx) {
772 ASSERT_PDF(pdf);
773 int xreflen = pdf_xref_len(gctx, pdf);
774 if (!INRANGE(xref, 1, xreflen-1) && xref != -1) {
775 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
776 }
777 if (xref > 0) {
778 obj = pdf_load_object(gctx, pdf, xref);
779 } else {
780 obj = pdf_trailer(gctx, pdf);
781 }
782 if (!obj) {
783 goto not_found;
784 }
785 subobj = pdf_dict_getp(gctx, obj, key);
786 if (!subobj) {
787 goto not_found;
788 }
789 char *type;
790 if (pdf_is_indirect(gctx, subobj)) {
791 type = "xref";
792 text = PyUnicode_FromFormat("%i 0 R", pdf_to_num(gctx, subobj));
793 } else if (pdf_is_array(gctx, subobj)) {
794 type = "array";
795 } else if (pdf_is_dict(gctx, subobj)) {
796 type = "dict";
797 } else if (pdf_is_int(gctx, subobj)) {
798 type = "int";
799 text = PyUnicode_FromFormat("%i", pdf_to_int(gctx, subobj));
800 } else if (pdf_is_real(gctx, subobj)) {
801 type = "float";
802 } else if (pdf_is_null(gctx, subobj)) {
803 type = "null";
804 text = PyUnicode_FromString("null");
805 } else if (pdf_is_bool(gctx, subobj)) {
806 type = "bool";
807 if (pdf_to_bool(gctx, subobj)) {
808 text = PyUnicode_FromString("true");
809 } else {
810 text = PyUnicode_FromString("false");
811 }
812 } else if (pdf_is_name(gctx, subobj)) {
813 type = "name";
814 text = PyUnicode_FromFormat("/%s", pdf_to_name(gctx, subobj));
815 } else if (pdf_is_string(gctx, subobj)) {
816 type = "string";
817 text = JM_UnicodeFromStr(pdf_to_text_string(gctx, subobj));
818 } else {
819 type = "unknown";
820 }
821 if (!text) {
822 res = JM_object_to_buffer(gctx, subobj, 1, 0);
823 text = JM_UnicodeFromBuffer(gctx, res);
824 }
825 rc = Py_BuildValue("sO", type, text);
826 Py_DECREF(text);
827 goto finished;
828
829 not_found:;
830 rc = Py_BuildValue("ss", "null", "null");
831 finished:;
832 }
833 fz_always(gctx) {
834 if (xref > 0) {
835 pdf_drop_obj(gctx, obj);
836 }
837 fz_drop_buffer(gctx, res);
838 }
839 fz_catch(gctx) {
840 return NULL;
841 }
842 return rc;
843 }
844
845
846 FITZEXCEPTION(xref_set_key, !result)
847 %pythonprepend xref_set_key %{
848 """Set the value of a PDF dictionary key."""
849 if self.is_closed or self.is_encrypted:
850 raise ValueError("document closed or encrypted")
851 if not key or not isinstance(key, str) or INVALID_NAME_CHARS.intersection(key) not in (set(), {"/"}):
852 raise ValueError("bad 'key'")
853 if not isinstance(value, str) or not value or value[0] == "/" and INVALID_NAME_CHARS.intersection(value[1:]) != set():
854 raise ValueError("bad 'value'")
855 %}
856 PyObject *
857 xref_set_key(int xref, const char *key, char *value)
858 {
859 pdf_document *pdf = pdf_specifics(gctx, (fz_document *)$self);
860 pdf_obj *obj = NULL, *new_obj = NULL;
861 int i, n;
862 fz_try(gctx) {
863 ASSERT_PDF(pdf);
864 if (!key || strlen(key) == 0) {
865 RAISEPY(gctx, "bad 'key'", PyExc_ValueError);
866 }
867 if (!value || strlen(value) == 0) {
868 RAISEPY(gctx, "bad 'value'", PyExc_ValueError);
869 }
870 int xreflen = pdf_xref_len(gctx, pdf);
871 if (!INRANGE(xref, 1, xreflen-1) && xref != -1) {
872 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
873 }
874 if (xref != -1) {
875 obj = pdf_load_object(gctx, pdf, xref);
876 } else {
877 obj = pdf_trailer(gctx, pdf);
878 }
879 // if val=="null" and no path hierarchy, delete "key" from object
880 // chr(47) = "/"
881 if (strcmp(value, "null") == 0 && strchr(key, 47) == NULL) {
882 pdf_dict_dels(gctx, obj, key);
883 goto finished;
884 }
885 new_obj = JM_set_object_value(gctx, obj, key, value);
886 if (!new_obj) {
887 goto finished; // did not work: skip update
888 }
889 if (xref != -1) {
890 pdf_drop_obj(gctx, obj);
891 obj = NULL;
892 pdf_update_object(gctx, pdf, xref, new_obj);
893 } else {
894 n = pdf_dict_len(gctx, new_obj);
895 for (i = 0; i < n; i++) {
896 pdf_dict_put(gctx, obj, pdf_dict_get_key(gctx, new_obj, i), pdf_dict_get_val(gctx, new_obj, i));
897 }
898 }
899 finished:;
900 }
901 fz_always(gctx) {
902 if (xref != -1) {
903 pdf_drop_obj(gctx, obj);
904 }
905 pdf_drop_obj(gctx, new_obj);
906 PyErr_Clear();
907 }
908 fz_catch(gctx) {
909 return NULL;
910 }
911 Py_RETURN_NONE;
912 }
913
914
915 FITZEXCEPTION(_extend_toc_items, !result)
916 CLOSECHECK0(_extend_toc_items, """Add color info to all items of an extended TOC list.""")
917 PyObject *
918 _extend_toc_items(PyObject *items)
919 {
920 pdf_document *pdf = pdf_specifics(gctx, (fz_document *)$self);
921 pdf_obj *bm, *col, *obj;
922 int count, flags;
923 PyObject *item=NULL, *itemdict=NULL, *xrefs, *bold, *italic, *collapse, *zoom;
924 zoom = PyUnicode_FromString("zoom");
925 bold = PyUnicode_FromString("bold");
926 italic = PyUnicode_FromString("italic");
927 collapse = PyUnicode_FromString("collapse");
928 fz_try(gctx) {
929 pdf_obj *root = pdf_dict_get(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root));
930 if (!root) goto finished;
931 pdf_obj *olroot = pdf_dict_get(gctx, root, PDF_NAME(Outlines));
932 if (!olroot) goto finished;
933 pdf_obj *first = pdf_dict_get(gctx, olroot, PDF_NAME(First));
934 if (!first) goto finished;
935 xrefs = PyList_New(0); // pre-allocate an empty list
936 xrefs = JM_outline_xrefs(gctx, first, xrefs);
937 Py_ssize_t i, n = PySequence_Size(xrefs), m = PySequence_Size(items);
938 if (!n) goto finished;
939 if (n != m) {
940 RAISEPY(gctx, "internal error finding outline xrefs", PyExc_IndexError);
941 }
942 int xref;
943
944 // update all TOC item dictionaries
945 for (i = 0; i < n; i++) {
946 JM_INT_ITEM(xrefs, i, &xref);
947 item = PySequence_ITEM(items, i);
948 itemdict = PySequence_ITEM(item, 3);
949 if (!itemdict || !PyDict_Check(itemdict)) {
950 RAISEPY(gctx, "need non-simple TOC format", PyExc_ValueError);
951 }
952 PyDict_SetItem(itemdict, dictkey_xref, PySequence_ITEM(xrefs, i));
953 bm = pdf_load_object(gctx, pdf, xref);
954 flags = pdf_to_int(gctx, (pdf_dict_get(gctx, bm, PDF_NAME(F))));
955 if (flags == 1) {
956 PyDict_SetItem(itemdict, italic, Py_True);
957 } else if (flags == 2) {
958 PyDict_SetItem(itemdict, bold, Py_True);
959 } else if (flags == 3) {
960 PyDict_SetItem(itemdict, italic, Py_True);
961 PyDict_SetItem(itemdict, bold, Py_True);
962 }
963 count = pdf_to_int(gctx, (pdf_dict_get(gctx, bm, PDF_NAME(Count))));
964 if (count < 0) {
965 PyDict_SetItem(itemdict, collapse, Py_True);
966 } else if (count > 0) {
967 PyDict_SetItem(itemdict, collapse, Py_False);
968 }
969 col = pdf_dict_get(gctx, bm, PDF_NAME(C));
970 if (pdf_is_array(gctx, col) && pdf_array_len(gctx, col) == 3) {
971 PyObject *color = PyTuple_New(3);
972 PyTuple_SET_ITEM(color, 0, Py_BuildValue("f", pdf_to_real(gctx, pdf_array_get(gctx, col, 0))));
973 PyTuple_SET_ITEM(color, 1, Py_BuildValue("f", pdf_to_real(gctx, pdf_array_get(gctx, col, 1))));
974 PyTuple_SET_ITEM(color, 2, Py_BuildValue("f", pdf_to_real(gctx, pdf_array_get(gctx, col, 2))));
975 DICT_SETITEM_DROP(itemdict, dictkey_color, color);
976 }
977 float z=0;
978 obj = pdf_dict_get(gctx, bm, PDF_NAME(Dest));
979 if (!obj || !pdf_is_array(gctx, obj)) {
980 obj = pdf_dict_getl(gctx, bm, PDF_NAME(A), PDF_NAME(D), NULL);
981 }
982 if (pdf_is_array(gctx, obj) && pdf_array_len(gctx, obj) == 5) {
983 z = pdf_to_real(gctx, pdf_array_get(gctx, obj, 4));
984 }
985 DICT_SETITEM_DROP(itemdict, zoom, Py_BuildValue("f", z));
986 PyList_SetItem(item, 3, itemdict);
987 PyList_SetItem(items, i, item);
988 pdf_drop_obj(gctx, bm);
989 bm = NULL;
990 }
991 finished:;
992 }
993 fz_always(gctx) {
994 Py_CLEAR(xrefs);
995 Py_CLEAR(bold);
996 Py_CLEAR(italic);
997 Py_CLEAR(collapse);
998 Py_CLEAR(zoom);
999 pdf_drop_obj(gctx, bm);
1000 PyErr_Clear();
1001 }
1002 fz_catch(gctx) {
1003 return NULL;
1004 }
1005 Py_RETURN_NONE;
1006 }
1007
1008 //----------------------------------------------------------------
1009 // EmbeddedFiles utility functions
1010 //----------------------------------------------------------------
1011 FITZEXCEPTION(_embfile_names, !result)
1012 CLOSECHECK0(_embfile_names, """Get list of embedded file names.""")
1013 PyObject *_embfile_names(PyObject *namelist)
1014 {
1015 fz_document *doc = (fz_document *) $self;
1016 pdf_document *pdf = pdf_specifics(gctx, doc);
1017 fz_try(gctx) {
1018 ASSERT_PDF(pdf);
1019 PyObject *val;
1020 pdf_obj *names = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
1021 PDF_NAME(Root),
1022 PDF_NAME(Names),
1023 PDF_NAME(EmbeddedFiles),
1024 PDF_NAME(Names),
1025 NULL);
1026 if (pdf_is_array(gctx, names)) {
1027 int i, n = pdf_array_len(gctx, names);
1028 for (i=0; i < n; i+=2) {
1029 val = JM_EscapeStrFromStr(pdf_to_text_string(gctx,
1030 pdf_array_get(gctx, names, i)));
1031 LIST_APPEND_DROP(namelist, val);
1032 }
1033 }
1034 }
1035 fz_catch(gctx) {
1036 return NULL;
1037 }
1038 Py_RETURN_NONE;
1039 }
1040
1041 FITZEXCEPTION(_embfile_del, !result)
1042 PyObject *_embfile_del(int idx)
1043 {
1044 fz_try(gctx) {
1045 fz_document *doc = (fz_document *) $self;
1046 pdf_document *pdf = pdf_document_from_fz_document(gctx, doc);
1047 pdf_obj *names = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
1048 PDF_NAME(Root),
1049 PDF_NAME(Names),
1050 PDF_NAME(EmbeddedFiles),
1051 PDF_NAME(Names),
1052 NULL);
1053 pdf_array_delete(gctx, names, idx + 1);
1054 pdf_array_delete(gctx, names, idx);
1055 }
1056 fz_catch(gctx) {
1057 return NULL;
1058 }
1059 Py_RETURN_NONE;
1060 }
1061
1062 FITZEXCEPTION(_embfile_info, !result)
1063 PyObject *_embfile_info(int idx, PyObject *infodict)
1064 {
1065 fz_document *doc = (fz_document *) $self;
1066 pdf_document *pdf = pdf_document_from_fz_document(gctx, doc);
1067 char *name;
1068 int xref = 0, ci_xref=0;
1069 fz_try(gctx) {
1070 pdf_obj *names = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
1071 PDF_NAME(Root),
1072 PDF_NAME(Names),
1073 PDF_NAME(EmbeddedFiles),
1074 PDF_NAME(Names),
1075 NULL);
1076
1077 pdf_obj *o = pdf_array_get(gctx, names, 2*idx+1);
1078 pdf_obj *ci = pdf_dict_get(gctx, o, PDF_NAME(CI));
1079 if (ci) {
1080 ci_xref = pdf_to_num(gctx, ci);
1081 }
1082 DICT_SETITEMSTR_DROP(infodict, "collection", Py_BuildValue("i", ci_xref));
1083 name = (char *) pdf_to_text_string(gctx,
1084 pdf_dict_get(gctx, o, PDF_NAME(F)));
1085 DICT_SETITEM_DROP(infodict, dictkey_filename, JM_EscapeStrFromStr(name));
1086
1087 name = (char *) pdf_to_text_string(gctx,
1088 pdf_dict_get(gctx, o, PDF_NAME(UF)));
1089 DICT_SETITEM_DROP(infodict, dictkey_ufilename, JM_EscapeStrFromStr(name));
1090
1091 name = (char *) pdf_to_text_string(gctx,
1092 pdf_dict_get(gctx, o, PDF_NAME(Desc)));
1093 DICT_SETITEM_DROP(infodict, dictkey_desc, JM_UnicodeFromStr(name));
1094
1095 int len = -1, DL = -1;
1096 pdf_obj *fileentry = pdf_dict_getl(gctx, o, PDF_NAME(EF), PDF_NAME(F), NULL);
1097 xref = pdf_to_num(gctx, fileentry);
1098 o = pdf_dict_get(gctx, fileentry, PDF_NAME(Length));
1099 if (o) len = pdf_to_int(gctx, o);
1100
1101 o = pdf_dict_get(gctx, fileentry, PDF_NAME(DL));
1102 if (o) {
1103 DL = pdf_to_int(gctx, o);
1104 } else {
1105 o = pdf_dict_getl(gctx, fileentry, PDF_NAME(Params),
1106 PDF_NAME(Size), NULL);
1107 if (o) DL = pdf_to_int(gctx, o);
1108 }
1109 DICT_SETITEM_DROP(infodict, dictkey_size, Py_BuildValue("i", DL));
1110 DICT_SETITEM_DROP(infodict, dictkey_length, Py_BuildValue("i", len));
1111 }
1112 fz_catch(gctx) {
1113 return NULL;
1114 }
1115 return Py_BuildValue("i", xref);
1116 }
1117
1118 FITZEXCEPTION(_embfile_upd, !result)
1119 PyObject *_embfile_upd(int idx, PyObject *buffer = NULL, char *filename = NULL, char *ufilename = NULL, char *desc = NULL)
1120 {
1121 fz_document *doc = (fz_document *) $self;
1122 pdf_document *pdf = pdf_document_from_fz_document(gctx, doc);
1123 fz_buffer *res = NULL;
1124 fz_var(res);
1125 int xref = 0;
1126 fz_try(gctx) {
1127 pdf_obj *names = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
1128 PDF_NAME(Root),
1129 PDF_NAME(Names),
1130 PDF_NAME(EmbeddedFiles),
1131 PDF_NAME(Names),
1132 NULL);
1133
1134 pdf_obj *entry = pdf_array_get(gctx, names, 2*idx+1);
1135
1136 pdf_obj *filespec = pdf_dict_getl(gctx, entry, PDF_NAME(EF),
1137 PDF_NAME(F), NULL);
1138 if (!filespec) {
1139 RAISEPY(gctx, "bad PDF: no /EF object", JM_Exc_FileDataError);
1140 }
1141 res = JM_BufferFromBytes(gctx, buffer);
1142 if (EXISTS(buffer) && !res) {
1143 RAISEPY(gctx, MSG_BAD_BUFFER, PyExc_TypeError);
1144 }
1145 if (res && buffer != Py_None)
1146 {
1147 JM_update_stream(gctx, pdf, filespec, res, 1);
1148 // adjust /DL and /Size parameters
1149 int64_t len = (int64_t) fz_buffer_storage(gctx, res, NULL);
1150 pdf_obj *l = pdf_new_int(gctx, len);
1151 pdf_dict_put(gctx, filespec, PDF_NAME(DL), l);
1152 pdf_dict_putl(gctx, filespec, l, PDF_NAME(Params), PDF_NAME(Size), NULL);
1153 }
1154 xref = pdf_to_num(gctx, filespec);
1155 if (filename)
1156 pdf_dict_put_text_string(gctx, entry, PDF_NAME(F), filename);
1157
1158 if (ufilename)
1159 pdf_dict_put_text_string(gctx, entry, PDF_NAME(UF), ufilename);
1160
1161 if (desc)
1162 pdf_dict_put_text_string(gctx, entry, PDF_NAME(Desc), desc);
1163 }
1164 fz_always(gctx) {
1165 fz_drop_buffer(gctx, res);
1166 }
1167 fz_catch(gctx)
1168 return NULL;
1169
1170 return Py_BuildValue("i", xref);
1171 }
1172
1173 FITZEXCEPTION(_embeddedFileGet, !result)
1174 PyObject *_embeddedFileGet(int idx)
1175 {
1176 fz_document *doc = (fz_document *) $self;
1177 PyObject *cont = NULL;
1178 pdf_document *pdf = pdf_document_from_fz_document(gctx, doc);
1179 fz_buffer *buf = NULL;
1180 fz_var(buf);
1181 fz_try(gctx) {
1182 pdf_obj *names = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
1183 PDF_NAME(Root),
1184 PDF_NAME(Names),
1185 PDF_NAME(EmbeddedFiles),
1186 PDF_NAME(Names),
1187 NULL);
1188
1189 pdf_obj *entry = pdf_array_get(gctx, names, 2*idx+1);
1190 pdf_obj *filespec = pdf_dict_getl(gctx, entry, PDF_NAME(EF),
1191 PDF_NAME(F), NULL);
1192 buf = pdf_load_stream(gctx, filespec);
1193 cont = JM_BinFromBuffer(gctx, buf);
1194 }
1195 fz_always(gctx) {
1196 fz_drop_buffer(gctx, buf);
1197 }
1198 fz_catch(gctx) {
1199 return NULL;
1200 }
1201 return cont;
1202 }
1203
1204 FITZEXCEPTION(_embfile_add, !result)
1205 PyObject *_embfile_add(const char *name, PyObject *buffer, char *filename=NULL, char *ufilename=NULL, char *desc=NULL)
1206 {
1207 fz_document *doc = (fz_document *) $self;
1208 pdf_document *pdf = pdf_document_from_fz_document(gctx, doc);
1209 fz_buffer *data = NULL;
1210 fz_var(data);
1211 pdf_obj *names = NULL;
1212 int xref = 0; // xref of file entry
1213 fz_try(gctx) {
1214 ASSERT_PDF(pdf);
1215 data = JM_BufferFromBytes(gctx, buffer);
1216 if (!data) {
1217 RAISEPY(gctx, MSG_BAD_BUFFER, PyExc_TypeError);
1218 }
1219
1220 names = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
1221 PDF_NAME(Root),
1222 PDF_NAME(Names),
1223 PDF_NAME(EmbeddedFiles),
1224 PDF_NAME(Names),
1225 NULL);
1226 if (!pdf_is_array(gctx, names)) {
1227 pdf_obj *root = pdf_dict_get(gctx, pdf_trailer(gctx, pdf),
1228 PDF_NAME(Root));
1229 names = pdf_new_array(gctx, pdf, 6); // an even number!
1230 pdf_dict_putl_drop(gctx, root, names,
1231 PDF_NAME(Names),
1232 PDF_NAME(EmbeddedFiles),
1233 PDF_NAME(Names),
1234 NULL);
1235 }
1236
1237 pdf_obj *fileentry = JM_embed_file(gctx, pdf, data,
1238 filename,
1239 ufilename,
1240 desc, 1);
1241 xref = pdf_to_num(gctx, pdf_dict_getl(gctx, fileentry,
1242 PDF_NAME(EF), PDF_NAME(F), NULL));
1243 pdf_array_push_drop(gctx, names, pdf_new_text_string(gctx, name));
1244 pdf_array_push_drop(gctx, names, fileentry);
1245 }
1246 fz_always(gctx) {
1247 fz_drop_buffer(gctx, data);
1248 }
1249 fz_catch(gctx) {
1250 return NULL;
1251 }
1252
1253 return Py_BuildValue("i", xref);
1254 }
1255
1256
1257 %pythoncode %{
1258 def embfile_names(self) -> list:
1259 """Get list of names of EmbeddedFiles."""
1260 filenames = []
1261 self._embfile_names(filenames)
1262 return filenames
1263
1264 def _embeddedFileIndex(self, item: typing.Union[int, str]) -> int:
1265 filenames = self.embfile_names()
1266 msg = "'%s' not in EmbeddedFiles array." % str(item)
1267 if item in filenames:
1268 idx = filenames.index(item)
1269 elif item in range(len(filenames)):
1270 idx = item
1271 else:
1272 raise ValueError(msg)
1273 return idx
1274
1275 def embfile_count(self) -> int:
1276 """Get number of EmbeddedFiles."""
1277 return len(self.embfile_names())
1278
1279 def embfile_del(self, item: typing.Union[int, str]):
1280 """Delete an entry from EmbeddedFiles.
1281
1282 Notes:
1283 The argument must be name or index of an EmbeddedFiles item.
1284 Physical deletion of data will happen on save to a new
1285 file with appropriate garbage option.
1286 Args:
1287 item: name or number of item.
1288 Returns:
1289 None
1290 """
1291 idx = self._embeddedFileIndex(item)
1292 return self._embfile_del(idx)
1293
1294 def embfile_info(self, item: typing.Union[int, str]) -> dict:
1295 """Get information of an item in the EmbeddedFiles array.
1296
1297 Args:
1298 item: number or name of item.
1299 Returns:
1300 Information dictionary.
1301 """
1302 idx = self._embeddedFileIndex(item)
1303 infodict = {"name": self.embfile_names()[idx]}
1304 xref = self._embfile_info(idx, infodict)
1305 t, date = self.xref_get_key(xref, "Params/CreationDate")
1306 if t != "null":
1307 infodict["creationDate"] = date
1308 t, date = self.xref_get_key(xref, "Params/ModDate")
1309 if t != "null":
1310 infodict["modDate"] = date
1311 t, md5 = self.xref_get_key(xref, "Params/CheckSum")
1312 if t != "null":
1313 infodict["checksum"] = binascii.hexlify(md5.encode()).decode()
1314 return infodict
1315
1316 def embfile_get(self, item: typing.Union[int, str]) -> bytes:
1317 """Get the content of an item in the EmbeddedFiles array.
1318
1319 Args:
1320 item: number or name of item.
1321 Returns:
1322 (bytes) The file content.
1323 """
1324 idx = self._embeddedFileIndex(item)
1325 return self._embeddedFileGet(idx)
1326
1327 def embfile_upd(self, item: typing.Union[int, str],
1328 buffer: OptBytes =None,
1329 filename: OptStr =None,
1330 ufilename: OptStr =None,
1331 desc: OptStr =None,) -> None:
1332 """Change an item of the EmbeddedFiles array.
1333
1334 Notes:
1335 Only provided parameters are changed. If all are omitted,
1336 the method is a no-op.
1337 Args:
1338 item: number or name of item.
1339 buffer: (binary data) the new file content.
1340 filename: (str) the new file name.
1341 ufilename: (unicode) the new filen ame.
1342 desc: (str) the new description.
1343 """
1344 idx = self._embeddedFileIndex(item)
1345 xref = self._embfile_upd(idx, buffer=buffer,
1346 filename=filename,
1347 ufilename=ufilename,
1348 desc=desc)
1349 date = get_pdf_now()
1350 self.xref_set_key(xref, "Params/ModDate", get_pdf_str(date))
1351 return xref
1352
1353 def embfile_add(self, name: str, buffer: ByteString,
1354 filename: OptStr =None,
1355 ufilename: OptStr =None,
1356 desc: OptStr =None,) -> None:
1357 """Add an item to the EmbeddedFiles array.
1358
1359 Args:
1360 name: name of the new item, must not already exist.
1361 buffer: (binary data) the file content.
1362 filename: (str) the file name, default: the name
1363 ufilename: (unicode) the file name, default: filename
1364 desc: (str) the description.
1365 """
1366 filenames = self.embfile_names()
1367 msg = "Name '%s' already exists." % str(name)
1368 if name in filenames:
1369 raise ValueError(msg)
1370
1371 if filename is None:
1372 filename = name
1373 if ufilename is None:
1374 ufilename = unicode(filename, "utf8") if str is bytes else filename
1375 if desc is None:
1376 desc = name
1377 xref = self._embfile_add(name, buffer=buffer,
1378 filename=filename,
1379 ufilename=ufilename,
1380 desc=desc)
1381 date = get_pdf_now()
1382 self.xref_set_key(xref, "Type", "/EmbeddedFile")
1383 self.xref_set_key(xref, "Params/CreationDate", get_pdf_str(date))
1384 self.xref_set_key(xref, "Params/ModDate", get_pdf_str(date))
1385 return xref
1386 %}
1387
1388 FITZEXCEPTION(convert_to_pdf, !result)
1389 %pythonprepend convert_to_pdf %{
1390 """Convert document to a PDF, selecting page range and optional rotation. Output bytes object."""
1391 if self.is_closed or self.is_encrypted:
1392 raise ValueError("document closed or encrypted")
1393 %}
1394 PyObject *convert_to_pdf(int from_page=0, int to_page=-1, int rotate=0)
1395 {
1396 PyObject *doc = NULL;
1397 fz_document *fz_doc = (fz_document *) $self;
1398 fz_try(gctx) {
1399 int fp = from_page, tp = to_page, srcCount = fz_count_pages(gctx, fz_doc);
1400 if (fp < 0) fp = 0;
1401 if (fp > srcCount - 1) fp = srcCount - 1;
1402 if (tp < 0) tp = srcCount - 1;
1403 if (tp > srcCount - 1) tp = srcCount - 1;
1404 Py_ssize_t len0 = PyList_Size(JM_mupdf_warnings_store);
1405 doc = JM_convert_to_pdf(gctx, fz_doc, fp, tp, rotate);
1406 Py_ssize_t len1 = PyList_Size(JM_mupdf_warnings_store);
1407 Py_ssize_t i = len0;
1408 while (i < len1) {
1409 PySys_WriteStderr("%s\n", JM_StrAsChar(PyList_GetItem(JM_mupdf_warnings_store, i)));
1410 i++;
1411 }
1412 }
1413 fz_catch(gctx) {
1414 return NULL;
1415 }
1416 if (doc) {
1417 return doc;
1418 }
1419 Py_RETURN_NONE;
1420 }
1421
1422
1423 FITZEXCEPTION(page_count, !result)
1424 CLOSECHECK0(page_count, """Number of pages.""")
1425 %pythoncode%{@property%}
1426 PyObject *page_count()
1427 {
1428 PyObject *ret;
1429 fz_try(gctx) {
1430 ret = PyLong_FromLong((long) fz_count_pages(gctx, (fz_document *) $self));
1431 }
1432 fz_catch(gctx) {
1433 PyErr_Clear();
1434 return NULL;
1435 }
1436 return ret;
1437 }
1438
1439 FITZEXCEPTION(chapter_count, !result)
1440 CLOSECHECK0(chapter_count, """Number of chapters.""")
1441 %pythoncode%{@property%}
1442 PyObject *chapter_count()
1443 {
1444 PyObject *ret;
1445 fz_try(gctx) {
1446 ret = PyLong_FromLong((long) fz_count_chapters(gctx, (fz_document *) $self));
1447 }
1448 fz_catch(gctx) {
1449 return NULL;
1450 }
1451 return ret;
1452 }
1453
1454 FITZEXCEPTION(last_location, !result)
1455 CLOSECHECK0(last_location, """Id (chapter, page) of last page.""")
1456 %pythoncode%{@property%}
1457 PyObject *last_location()
1458 {
1459 fz_document *this_doc = (fz_document *) $self;
1460 fz_location last_loc;
1461 fz_try(gctx) {
1462 last_loc = fz_last_page(gctx, this_doc);
1463 }
1464 fz_catch(gctx) {
1465 return NULL;
1466 }
1467 return Py_BuildValue("ii", last_loc.chapter, last_loc.page);
1468 }
1469
1470
1471 FITZEXCEPTION(chapter_page_count, !result)
1472 CLOSECHECK0(chapter_page_count, """Page count of chapter.""")
1473 PyObject *chapter_page_count(int chapter)
1474 {
1475 long pages = 0;
1476 fz_try(gctx) {
1477 int chapters = fz_count_chapters(gctx, (fz_document *) $self);
1478 if (chapter < 0 || chapter >= chapters) {
1479 RAISEPY(gctx, "bad chapter number", PyExc_ValueError);
1480 }
1481 pages = (long) fz_count_chapter_pages(gctx, (fz_document *) $self, chapter);
1482 }
1483 fz_catch(gctx) {
1484 return NULL;
1485 }
1486 return PyLong_FromLong(pages);
1487 }
1488
1489 FITZEXCEPTION(prev_location, !result)
1490 %pythonprepend prev_location %{
1491 """Get (chapter, page) of previous page."""
1492 if self.is_closed or self.is_encrypted:
1493 raise ValueError("document closed or encrypted")
1494 if type(page_id) is int:
1495 page_id = (0, page_id)
1496 if page_id not in self:
1497 raise ValueError("page id not in document")
1498 if page_id == (0, 0):
1499 return ()
1500 %}
1501 PyObject *prev_location(PyObject *page_id)
1502 {
1503 fz_document *this_doc = (fz_document *) $self;
1504 fz_location prev_loc, loc;
1505 PyObject *val;
1506 int pno;
1507 fz_try(gctx) {
1508 val = PySequence_GetItem(page_id, 0);
1509 if (!val) {
1510 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1511 }
1512 int chapter = (int) PyLong_AsLong(val);
1513 Py_DECREF(val);
1514 if (PyErr_Occurred()) {
1515 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1516 }
1517
1518 val = PySequence_GetItem(page_id, 1);
1519 if (!val) {
1520 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1521 }
1522 pno = (int) PyLong_AsLong(val);
1523 Py_DECREF(val);
1524 if (PyErr_Occurred()) {
1525 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1526 }
1527 loc = fz_make_location(chapter, pno);
1528 prev_loc = fz_previous_page(gctx, this_doc, loc);
1529 }
1530 fz_catch(gctx) {
1531 PyErr_Clear();
1532 return NULL;
1533 }
1534 return Py_BuildValue("ii", prev_loc.chapter, prev_loc.page);
1535 }
1536
1537
1538 FITZEXCEPTION(next_location, !result)
1539 %pythonprepend next_location %{
1540 """Get (chapter, page) of next page."""
1541 if self.is_closed or self.is_encrypted:
1542 raise ValueError("document closed or encrypted")
1543 if type(page_id) is int:
1544 page_id = (0, page_id)
1545 if page_id not in self:
1546 raise ValueError("page id not in document")
1547 if tuple(page_id) == self.last_location:
1548 return ()
1549 %}
1550 PyObject *next_location(PyObject *page_id)
1551 {
1552 fz_document *this_doc = (fz_document *) $self;
1553 fz_location next_loc, loc;
1554 PyObject *val;
1555 int pno;
1556 fz_try(gctx) {
1557 val = PySequence_GetItem(page_id, 0);
1558 if (!val) {
1559 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1560 }
1561 int chapter = (int) PyLong_AsLong(val);
1562 Py_DECREF(val);
1563 if (PyErr_Occurred()) {
1564 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1565 }
1566
1567 val = PySequence_GetItem(page_id, 1);
1568 if (!val) {
1569 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1570 }
1571 pno = (int) PyLong_AsLong(val);
1572 Py_DECREF(val);
1573 if (PyErr_Occurred()) {
1574 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1575 }
1576 loc = fz_make_location(chapter, pno);
1577 next_loc = fz_next_page(gctx, this_doc, loc);
1578 }
1579 fz_catch(gctx) {
1580 PyErr_Clear();
1581 return NULL;
1582 }
1583 return Py_BuildValue("ii", next_loc.chapter, next_loc.page);
1584 }
1585
1586
1587 FITZEXCEPTION(location_from_page_number, !result)
1588 CLOSECHECK0(location_from_page_number, """Convert pno to (chapter, page).""")
1589 PyObject *location_from_page_number(int pno)
1590 {
1591 fz_document *this_doc = (fz_document *) $self;
1592 fz_location loc = fz_make_location(-1, -1);
1593 int page_count = fz_count_pages(gctx, this_doc);
1594 while (pno < 0) pno += page_count;
1595 fz_try(gctx) {
1596 if (pno >= page_count) {
1597 RAISEPY(gctx, MSG_BAD_PAGENO, PyExc_ValueError);
1598 }
1599 loc = fz_location_from_page_number(gctx, this_doc, pno);
1600 }
1601 fz_catch(gctx) {
1602 return NULL;
1603 }
1604 return Py_BuildValue("ii", loc.chapter, loc.page);
1605 }
1606
1607 FITZEXCEPTION(page_number_from_location, !result)
1608 %pythonprepend page_number_from_location%{
1609 """Convert (chapter, pno) to page number."""
1610 if type(page_id) is int:
1611 np = self.page_count
1612 while page_id < 0:
1613 page_id += np
1614 page_id = (0, page_id)
1615 if page_id not in self:
1616 raise ValueError("page id not in document")
1617 %}
1618 PyObject *page_number_from_location(PyObject *page_id)
1619 {
1620 fz_document *this_doc = (fz_document *) $self;
1621 fz_location loc;
1622 long page_n = -1;
1623 PyObject *val;
1624 int pno;
1625 fz_try(gctx) {
1626 val = PySequence_GetItem(page_id, 0);
1627 if (!val) {
1628 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1629 }
1630 int chapter = (int) PyLong_AsLong(val);
1631 Py_DECREF(val);
1632 if (PyErr_Occurred()) {
1633 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1634 }
1635
1636 val = PySequence_GetItem(page_id, 1);
1637 if (!val) {
1638 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1639 }
1640 pno = (int) PyLong_AsLong(val);
1641 Py_DECREF(val);
1642 if (PyErr_Occurred()) {
1643 RAISEPY(gctx, MSG_BAD_PAGEID, PyExc_ValueError);
1644 }
1645
1646 loc = fz_make_location(chapter, pno);
1647 page_n = (long) fz_page_number_from_location(gctx, this_doc, loc);
1648 }
1649 fz_catch(gctx) {
1650 PyErr_Clear();
1651 return NULL;
1652 }
1653 return PyLong_FromLong(page_n);
1654 }
1655
1656 FITZEXCEPTION(_getMetadata, !result)
1657 CLOSECHECK0(_getMetadata, """Get metadata.""")
1658 PyObject *
1659 _getMetadata(const char *key)
1660 {
1661 PyObject *res = NULL;
1662 fz_document *doc = (fz_document *) $self;
1663 int vsize;
1664 char *value;
1665 fz_try(gctx) {
1666 vsize = fz_lookup_metadata(gctx, doc, key, NULL, 0)+1;
1667 if(vsize > 1) {
1668 value = JM_Alloc(char, vsize);
1669 fz_lookup_metadata(gctx, doc, key, value, vsize);
1670 res = JM_UnicodeFromStr(value);
1671 JM_Free(value);
1672 } else {
1673 res = EMPTY_STRING;
1674 }
1675 }
1676 fz_always(gctx) {
1677 PyErr_Clear();
1678 }
1679 fz_catch(gctx) {
1680 return EMPTY_STRING;
1681 }
1682 return res;
1683 }
1684
1685 CLOSECHECK0(needs_pass, """Indicate password required.""")
1686 %pythoncode%{@property%}
1687 PyObject *needs_pass() {
1688 return JM_BOOL(fz_needs_password(gctx, (fz_document *) $self));
1689 }
1690
1691 %pythoncode%{@property%}
1692 CLOSECHECK0(language, """Document language.""")
1693 PyObject *language()
1694 {
1695 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
1696 if (!pdf) Py_RETURN_NONE;
1697 fz_text_language lang = pdf_document_language(gctx, pdf);
1698 char buf[8];
1699 if (lang == FZ_LANG_UNSET) Py_RETURN_NONE;
1700 return PyUnicode_FromString(fz_string_from_text_language(buf, lang));
1701 }
1702
1703 FITZEXCEPTION(set_language, !result)
1704 PyObject *set_language(char *language=NULL)
1705 {
1706 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
1707 fz_try(gctx) {
1708 ASSERT_PDF(pdf);
1709 fz_text_language lang;
1710 if (!language)
1711 lang = FZ_LANG_UNSET;
1712 else
1713 lang = fz_text_language_from_string(language);
1714 pdf_set_document_language(gctx, pdf, lang);
1715 }
1716 fz_catch(gctx) {
1717 return NULL;
1718 }
1719 Py_RETURN_TRUE;
1720 }
1721
1722
1723 %pythonprepend resolve_link %{
1724 """Calculate internal link destination.
1725
1726 Args:
1727 uri: (str) some Link.uri
1728 chapters: (bool) whether to use (chapter, page) format
1729 Returns:
1730 (page_id, x, y) where x, y are point coordinates on the page.
1731 page_id is either page number (if chapters=0), or (chapter, pno).
1732 """
1733 %}
1734 PyObject *resolve_link(char *uri=NULL, int chapters=0)
1735 {
1736 if (!uri) {
1737 if (chapters) return Py_BuildValue("(ii)ff", -1, -1, 0, 0);
1738 return Py_BuildValue("iff", -1, 0, 0);
1739 }
1740 fz_document *this_doc = (fz_document *) $self;
1741 float xp = 0, yp = 0;
1742 fz_location loc = {0, 0};
1743 fz_try(gctx) {
1744 loc = fz_resolve_link(gctx, (fz_document *) $self, uri, &xp, &yp);
1745 }
1746 fz_catch(gctx) {
1747 if (chapters) return Py_BuildValue("(ii)ff", -1, -1, 0, 0);
1748 return Py_BuildValue("iff", -1, 0, 0);
1749 }
1750 if (chapters)
1751 return Py_BuildValue("(ii)ff", loc.chapter, loc.page, xp, yp);
1752 int pno = fz_page_number_from_location(gctx, this_doc, loc);
1753 return Py_BuildValue("iff", pno, xp, yp);
1754 }
1755
1756 FITZEXCEPTION(layout, !result)
1757 CLOSECHECK(layout, """Re-layout a reflowable document.""")
1758 %pythonappend layout %{
1759 self._reset_page_refs()
1760 self.init_doc()%}
1761 PyObject *layout(PyObject *rect = NULL, float width = 0, float height = 0, float fontsize = 11)
1762 {
1763 fz_document *doc = (fz_document *) $self;
1764 if (!fz_is_document_reflowable(gctx, doc)) Py_RETURN_NONE;
1765 fz_try(gctx) {
1766 float w = width, h = height;
1767 fz_rect r = JM_rect_from_py(rect);
1768 if (!fz_is_infinite_rect(r)) {
1769 w = r.x1 - r.x0;
1770 h = r.y1 - r.y0;
1771 }
1772 if (w <= 0.0f || h <= 0.0f) {
1773 RAISEPY(gctx, "bad page size", PyExc_ValueError);
1774 }
1775 fz_layout_document(gctx, doc, w, h, fontsize);
1776 }
1777 fz_catch(gctx) {
1778 return NULL;
1779 }
1780 Py_RETURN_NONE;
1781 }
1782
1783 FITZEXCEPTION(make_bookmark, !result)
1784 CLOSECHECK(make_bookmark, """Make a page pointer before layouting document.""")
1785 PyObject *make_bookmark(PyObject *loc)
1786 {
1787 fz_document *doc = (fz_document *) $self;
1788 fz_location location;
1789 fz_bookmark mark;
1790 fz_try(gctx) {
1791 if (JM_INT_ITEM(loc, 0, &location.chapter) == 1) {
1792 RAISEPY(gctx, MSG_BAD_LOCATION, PyExc_ValueError);
1793 }
1794 if (JM_INT_ITEM(loc, 1, &location.page) == 1) {
1795 RAISEPY(gctx, MSG_BAD_LOCATION, PyExc_ValueError);
1796 }
1797 mark = fz_make_bookmark(gctx, doc, location);
1798 if (!mark) {
1799 RAISEPY(gctx, MSG_BAD_LOCATION, PyExc_ValueError);
1800 }
1801 }
1802 fz_catch(gctx) {
1803 return NULL;
1804 }
1805 return PyLong_FromVoidPtr((void *) mark);
1806 }
1807
1808
1809 FITZEXCEPTION(find_bookmark, !result)
1810 CLOSECHECK(find_bookmark, """Find new location after layouting a document.""")
1811 PyObject *find_bookmark(PyObject *bm)
1812 {
1813 fz_document *doc = (fz_document *) $self;
1814 fz_location location;
1815 fz_try(gctx) {
1816 intptr_t mark = (intptr_t) PyLong_AsVoidPtr(bm);
1817 location = fz_lookup_bookmark(gctx, doc, mark);
1818 }
1819 fz_catch(gctx) {
1820 return NULL;
1821 }
1822 return Py_BuildValue("ii", location.chapter, location.page);
1823 }
1824
1825
1826 CLOSECHECK0(is_reflowable, """Check if document is layoutable.""")
1827 %pythoncode%{@property%}
1828 PyObject *is_reflowable()
1829 {
1830 return JM_BOOL(fz_is_document_reflowable(gctx, (fz_document *) $self));
1831 }
1832
1833 FITZEXCEPTION(_deleteObject, !result)
1834 CLOSECHECK0(_deleteObject, """Delete object.""")
1835 PyObject *_deleteObject(int xref)
1836 {
1837 fz_document *doc = (fz_document *) $self;
1838 pdf_document *pdf = pdf_specifics(gctx, doc);
1839 fz_try(gctx) {
1840 ASSERT_PDF(pdf);
1841 if (!INRANGE(xref, 1, pdf_xref_len(gctx, pdf)-1)) {
1842 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
1843 }
1844 pdf_delete_object(gctx, pdf, xref);
1845 }
1846 fz_catch(gctx) {
1847 return NULL;
1848 }
1849
1850 Py_RETURN_NONE;
1851 }
1852
1853 FITZEXCEPTION(pdf_catalog, !result)
1854 CLOSECHECK0(pdf_catalog, """Get xref of PDF catalog.""")
1855 PyObject *pdf_catalog()
1856 {
1857 fz_document *doc = (fz_document *) $self;
1858 pdf_document *pdf = pdf_specifics(gctx, doc);
1859 int xref = 0;
1860 if (!pdf) return Py_BuildValue("i", xref);
1861 fz_try(gctx) {
1862 pdf_obj *root = pdf_dict_get(gctx, pdf_trailer(gctx, pdf),
1863 PDF_NAME(Root));
1864 xref = pdf_to_num(gctx, root);
1865 }
1866 fz_catch(gctx) {
1867 return NULL;
1868 }
1869 return Py_BuildValue("i", xref);
1870 }
1871
1872 FITZEXCEPTION(_getPDFfileid, !result)
1873 CLOSECHECK0(_getPDFfileid, """Get PDF file id.""")
1874 PyObject *_getPDFfileid()
1875 {
1876 fz_document *doc = (fz_document *) $self;
1877 pdf_document *pdf = pdf_specifics(gctx, doc);
1878 if (!pdf) Py_RETURN_NONE;
1879 PyObject *idlist = PyList_New(0);
1880 fz_buffer *buffer = NULL;
1881 unsigned char *hex;
1882 pdf_obj *o;
1883 int n, i, len;
1884 PyObject *bytes;
1885
1886 fz_try(gctx) {
1887 pdf_obj *identity = pdf_dict_get(gctx, pdf_trailer(gctx, pdf),
1888 PDF_NAME(ID));
1889 if (identity) {
1890 n = pdf_array_len(gctx, identity);
1891 for (i = 0; i < n; i++) {
1892 o = pdf_array_get(gctx, identity, i);
1893 len = (int) pdf_to_str_len(gctx, o);
1894 buffer = fz_new_buffer(gctx, 2 * len);
1895 fz_buffer_storage(gctx, buffer, &hex);
1896 hexlify(len, (unsigned char *) pdf_to_text_string(gctx, o), hex);
1897 LIST_APPEND_DROP(idlist, JM_UnicodeFromStr(hex));
1898 Py_CLEAR(bytes);
1899 fz_drop_buffer(gctx, buffer);
1900 buffer = NULL;
1901 }
1902 }
1903 }
1904 fz_catch(gctx) {
1905 fz_drop_buffer(gctx, buffer);
1906 }
1907 return idlist;
1908 }
1909
1910 CLOSECHECK0(version_count, """Count versions of PDF document.""")
1911 %pythoncode%{@property%}
1912 PyObject *version_count()
1913 {
1914 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
1915 if (!pdf) return Py_BuildValue("i", 0);
1916 return Py_BuildValue("i", pdf_count_versions(gctx, pdf));
1917 }
1918
1919
1920 CLOSECHECK0(is_pdf, """Check for PDF.""")
1921 %pythoncode%{@property%}
1922 PyObject *is_pdf()
1923 {
1924 if (pdf_specifics(gctx, (fz_document *) $self)) Py_RETURN_TRUE;
1925 else Py_RETURN_FALSE;
1926 }
1927
1928 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR <= 21
1929 /* The underlying struct members that these methods give access to, are
1930 not available. */
1931 CLOSECHECK0(has_xref_streams, """Check if xref table is a stream.""")
1932 %pythoncode%{@property%}
1933 PyObject *has_xref_streams()
1934 {
1935 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
1936 if (!pdf) Py_RETURN_FALSE;
1937 if (pdf->has_xref_streams) Py_RETURN_TRUE;
1938 Py_RETURN_FALSE;
1939 }
1940
1941 CLOSECHECK0(has_old_style_xrefs, """Check if xref table is old style.""")
1942 %pythoncode%{@property%}
1943 PyObject *has_old_style_xrefs()
1944 {
1945 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
1946 if (!pdf) Py_RETURN_FALSE;
1947 if (pdf->has_old_style_xrefs) Py_RETURN_TRUE;
1948 Py_RETURN_FALSE;
1949 }
1950 #endif
1951
1952 CLOSECHECK0(is_dirty, """True if PDF has unsaved changes.""")
1953 %pythoncode%{@property%}
1954 PyObject *is_dirty()
1955 {
1956 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
1957 if (!pdf) Py_RETURN_FALSE;
1958 return JM_BOOL(pdf_has_unsaved_changes(gctx, pdf));
1959 }
1960
1961 CLOSECHECK0(can_save_incrementally, """Check whether incremental saves are possible.""")
1962 PyObject *can_save_incrementally()
1963 {
1964 pdf_document *pdf = pdf_document_from_fz_document(gctx, (fz_document *) $self);
1965 if (!pdf) Py_RETURN_FALSE; // gracefully handle non-PDF
1966 return JM_BOOL(pdf_can_be_saved_incrementally(gctx, pdf));
1967 }
1968
1969 CLOSECHECK0(is_fast_webaccess, """Check whether we have a linearized PDF.""")
1970 %pythoncode%{@property%}
1971 PyObject *is_fast_webaccess()
1972 {
1973 pdf_document *pdf = pdf_document_from_fz_document(gctx, (fz_document *) $self);
1974 if (!pdf) Py_RETURN_FALSE; // gracefully handle non-PDF
1975 return JM_BOOL(pdf_doc_was_linearized(gctx, pdf));
1976 }
1977
1978 CLOSECHECK0(is_repaired, """Check whether PDF was repaired.""")
1979 %pythoncode%{@property%}
1980 PyObject *is_repaired()
1981 {
1982 pdf_document *pdf = pdf_document_from_fz_document(gctx, (fz_document *) $self);
1983 if (!pdf) Py_RETURN_FALSE; // gracefully handle non-PDF
1984 return JM_BOOL(pdf_was_repaired(gctx, pdf));
1985 }
1986
1987 FITZEXCEPTION(save_snapshot, !result)
1988 %pythonprepend save_snapshot %{
1989 """Save a file snapshot suitable for journalling."""
1990 if self.is_closed:
1991 raise ValueError("doc is closed")
1992 if type(filename) == str:
1993 pass
1994 elif hasattr(filename, "open"): # assume: pathlib.Path
1995 filename = str(filename)
1996 elif hasattr(filename, "name"): # assume: file object
1997 filename = filename.name
1998 else:
1999 raise ValueError("filename must be str, Path or file object")
2000 if filename == self.name:
2001 raise ValueError("cannot snapshot to original")
2002 %}
2003 PyObject *save_snapshot(const char *filename)
2004 {
2005 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2006 fz_try(gctx) {
2007 ASSERT_PDF(pdf);
2008 pdf_save_snapshot(gctx, pdf, filename);
2009 }
2010 fz_catch(gctx) {
2011 return NULL;
2012 }
2013 Py_RETURN_NONE;
2014 }
2015
2016 CLOSECHECK0(authenticate, """Decrypt document.""")
2017 %pythonappend authenticate %{
2018 if val: # the doc is decrypted successfully and we init the outline
2019 self.is_encrypted = False
2020 self.isEncrypted = False
2021 self.init_doc()
2022 self.thisown = True
2023 %}
2024 PyObject *authenticate(char *password)
2025 {
2026 return Py_BuildValue("i", fz_authenticate_password(gctx, (fz_document *) $self, (const char *) password));
2027 }
2028
2029 //------------------------------------------------------------------
2030 // save a PDF
2031 //------------------------------------------------------------------
2032 FITZEXCEPTION(save, !result)
2033 %pythonprepend save %{
2034 """Save PDF to file, pathlib.Path or file pointer."""
2035 if self.is_closed or self.is_encrypted:
2036 raise ValueError("document closed or encrypted")
2037 if type(filename) == str:
2038 pass
2039 elif hasattr(filename, "open"): # assume: pathlib.Path
2040 filename = str(filename)
2041 elif hasattr(filename, "name"): # assume: file object
2042 filename = filename.name
2043 elif not hasattr(filename, "seek"): # assume file object
2044 raise ValueError("filename must be str, Path or file object")
2045 if filename == self.name and not incremental:
2046 raise ValueError("save to original must be incremental")
2047 if self.page_count < 1:
2048 raise ValueError("cannot save with zero pages")
2049 if incremental:
2050 if self.name != filename or self.stream:
2051 raise ValueError("incremental needs original file")
2052 if user_pw and len(user_pw) > 40 or owner_pw and len(owner_pw) > 40:
2053 raise ValueError("password length must not exceed 40")
2054 %}
2055
2056 PyObject *
2057 save(PyObject *filename, int garbage=0, int clean=0,
2058 int deflate=0, int deflate_images=0, int deflate_fonts=0,
2059 int incremental=0, int ascii=0, int expand=0, int linear=0,
2060 int no_new_id=0, int appearance=0,
2061 int pretty=0, int encryption=1, int permissions=4095,
2062 char *owner_pw=NULL, char *user_pw=NULL)
2063 {
2064 pdf_write_options opts = pdf_default_write_options;
2065 opts.do_incremental = incremental;
2066 opts.do_ascii = ascii;
2067 opts.do_compress = deflate;
2068 opts.do_compress_images = deflate_images;
2069 opts.do_compress_fonts = deflate_fonts;
2070 opts.do_decompress = expand;
2071 opts.do_garbage = garbage;
2072 opts.do_pretty = pretty;
2073 opts.do_linear = linear;
2074 opts.do_clean = clean;
2075 opts.do_sanitize = clean;
2076 opts.dont_regenerate_id = no_new_id;
2077 opts.do_appearance = appearance;
2078 opts.do_encrypt = encryption;
2079 opts.permissions = permissions;
2080 if (owner_pw) {
2081 memcpy(&opts.opwd_utf8, owner_pw, strlen(owner_pw)+1);
2082 } else if (user_pw) {
2083 memcpy(&opts.opwd_utf8, user_pw, strlen(user_pw)+1);
2084 }
2085 if (user_pw) {
2086 memcpy(&opts.upwd_utf8, user_pw, strlen(user_pw)+1);
2087 }
2088 fz_document *doc = (fz_document *) $self;
2089 pdf_document *pdf = pdf_specifics(gctx, doc);
2090 fz_output *out = NULL;
2091 fz_try(gctx) {
2092 ASSERT_PDF(pdf);
2093 pdf->resynth_required = 0;
2094 JM_embedded_clean(gctx, pdf);
2095 if (no_new_id == 0) {
2096 JM_ensure_identity(gctx, pdf);
2097 }
2098 if (PyUnicode_Check(filename)) {
2099 pdf_save_document(gctx, pdf, JM_StrAsChar(filename), &opts);
2100 } else {
2101 out = JM_new_output_fileptr(gctx, filename);
2102 pdf_write_document(gctx, pdf, out, &opts);
2103 }
2104 }
2105 fz_always(gctx) {
2106 fz_drop_output(gctx, out);
2107 }
2108 fz_catch(gctx) {
2109 return NULL;
2110 }
2111 Py_RETURN_NONE;
2112 }
2113
2114 %pythoncode %{
2115 def write(self, garbage=False, clean=False,
2116 deflate=False, deflate_images=False, deflate_fonts=False,
2117 incremental=False, ascii=False, expand=False, linear=False,
2118 no_new_id=False, appearance=False, pretty=False, encryption=1, permissions=4095,
2119 owner_pw=None, user_pw=None):
2120 from io import BytesIO
2121 bio = BytesIO()
2122 self.save(bio, garbage=garbage, clean=clean,
2123 no_new_id=no_new_id, appearance=appearance,
2124 deflate=deflate, deflate_images=deflate_images, deflate_fonts=deflate_fonts,
2125 incremental=incremental, ascii=ascii, expand=expand, linear=linear,
2126 pretty=pretty, encryption=encryption, permissions=permissions,
2127 owner_pw=owner_pw, user_pw=user_pw)
2128 return bio.getvalue()
2129 %}
2130
2131 //----------------------------------------------------------------
2132 // Insert pages from a source PDF into this PDF.
2133 // For reconstructing the links (_do_links method), we must save the
2134 // insertion point (start_at) if it was specified as -1.
2135 //----------------------------------------------------------------
2136 FITZEXCEPTION(insert_pdf, !result)
2137 %pythonprepend insert_pdf %{
2138 """Insert a page range from another PDF.
2139
2140 Args:
2141 docsrc: PDF to copy from. Must be different object, but may be same file.
2142 from_page: (int) first source page to copy, 0-based, default 0.
2143 to_page: (int) last source page to copy, 0-based, default last page.
2144 start_at: (int) from_page will become this page number in target.
2145 rotate: (int) rotate copied pages, default -1 is no change.
2146 links: (int/bool) whether to also copy links.
2147 annots: (int/bool) whether to also copy annotations.
2148 show_progress: (int) progress message interval, 0 is no messages.
2149 final: (bool) indicates last insertion from this source PDF.
2150 _gmap: internal use only
2151
2152 Copy sequence reversed if from_page > to_page."""
2153
2154 if self.is_closed or self.is_encrypted:
2155 raise ValueError("document closed or encrypted")
2156 if self._graft_id == docsrc._graft_id:
2157 raise ValueError("source and target cannot be same object")
2158 sa = start_at
2159 if sa < 0:
2160 sa = self.page_count
2161 if len(docsrc) > show_progress > 0:
2162 inname = os.path.basename(docsrc.name)
2163 if not inname:
2164 inname = "memory PDF"
2165 outname = os.path.basename(self.name)
2166 if not outname:
2167 outname = "memory PDF"
2168 print("Inserting '%s' at '%s'" % (inname, outname))
2169
2170 # retrieve / make a Graftmap to avoid duplicate objects
2171 isrt = docsrc._graft_id
2172 _gmap = self.Graftmaps.get(isrt, None)
2173 if _gmap is None:
2174 _gmap = Graftmap(self)
2175 self.Graftmaps[isrt] = _gmap
2176 %}
2177
2178 %pythonappend insert_pdf %{
2179 self._reset_page_refs()
2180 if links:
2181 self._do_links(docsrc, from_page = from_page, to_page = to_page,
2182 start_at = sa)
2183 if final == 1:
2184 self.Graftmaps[isrt] = None%}
2185
2186 PyObject *
2187 insert_pdf(struct Document *docsrc,
2188 int from_page=-1,
2189 int to_page=-1,
2190 int start_at=-1,
2191 int rotate=-1,
2192 int links=1,
2193 int annots=1,
2194 int show_progress=0,
2195 int final = 1,
2196 struct Graftmap *_gmap=NULL)
2197 {
2198 fz_document *doc = (fz_document *) $self;
2199 fz_document *src = (fz_document *) docsrc;
2200 pdf_document *pdfout = pdf_specifics(gctx, doc);
2201 pdf_document *pdfsrc = pdf_specifics(gctx, src);
2202 int outCount = fz_count_pages(gctx, doc);
2203 int srcCount = fz_count_pages(gctx, src);
2204
2205 // local copies of page numbers
2206 int fp = from_page, tp = to_page, sa = start_at;
2207
2208 // normalize page numbers
2209 fp = Py_MAX(fp, 0); // -1 = first page
2210 fp = Py_MIN(fp, srcCount - 1); // but do not exceed last page
2211
2212 if (tp < 0) tp = srcCount - 1; // -1 = last page
2213 tp = Py_MIN(tp, srcCount - 1); // but do not exceed last page
2214
2215 if (sa < 0) sa = outCount; // -1 = behind last page
2216 sa = Py_MIN(sa, outCount); // but that is also the limit
2217
2218 fz_try(gctx) {
2219 if (!pdfout || !pdfsrc) {
2220 RAISEPY(gctx, "source or target not a PDF", PyExc_TypeError);
2221 }
2222 ENSURE_OPERATION(gctx, pdfout);
2223 JM_merge_range(gctx, pdfout, pdfsrc, fp, tp, sa, rotate, links, annots, show_progress, (pdf_graft_map *) _gmap);
2224 }
2225 fz_catch(gctx) {
2226 return NULL;
2227 }
2228 Py_RETURN_NONE;
2229 }
2230
2231 %pythoncode %{
2232 def insert_file(self, infile, from_page=-1, to_page=-1, start_at=-1, rotate=-1, links=True, annots=True,show_progress=0, final=1):
2233 """Insert an arbitrary supported document to an existing PDF.
2234
2235 The infile may be given as a filename, a Document or a Pixmap.
2236 Other paramters - where applicable - equal those of insert_pdf().
2237 """
2238 src = None
2239 if isinstance(infile, Pixmap):
2240 if infile.colorspace.n > 3:
2241 infile = Pixmap(csRGB, infile)
2242 src = Document("png", infile.tobytes())
2243 elif isinstance(infile, Document):
2244 src = infile
2245 else:
2246 src = Document(infile)
2247 if not src:
2248 raise ValueError("bad infile parameter")
2249 if not src.is_pdf:
2250 pdfbytes = src.convert_to_pdf()
2251 src = Document("pdf", pdfbytes)
2252 return self.insert_pdf(src, from_page=from_page, to_page=to_page, start_at=start_at, rotate=rotate,links=links, annots=annots, show_progress=show_progress, final=final)
2253 %}
2254
2255 //------------------------------------------------------------------
2256 // Create and insert a new page (PDF)
2257 //------------------------------------------------------------------
2258 FITZEXCEPTION(_newPage, !result)
2259 CLOSECHECK(_newPage, """Make a new PDF page.""")
2260 %pythonappend _newPage %{self._reset_page_refs()%}
2261 PyObject *_newPage(int pno=-1, float width=595, float height=842)
2262 {
2263 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2264 fz_rect mediabox = fz_unit_rect;
2265 mediabox.x1 = width;
2266 mediabox.y1 = height;
2267 pdf_obj *resources = NULL, *page_obj = NULL;
2268 fz_buffer *contents = NULL;
2269 fz_var(contents);
2270 fz_var(page_obj);
2271 fz_var(resources);
2272 fz_try(gctx) {
2273 ASSERT_PDF(pdf);
2274 if (pno < -1) {
2275 RAISEPY(gctx, MSG_BAD_PAGENO, PyExc_ValueError);
2276 }
2277 ENSURE_OPERATION(gctx, pdf);
2278 // create /Resources and /Contents objects
2279 resources = pdf_add_new_dict(gctx, pdf, 1);
2280 page_obj = pdf_add_page(gctx, pdf, mediabox, 0, resources, contents);
2281 pdf_insert_page(gctx, pdf, pno, page_obj);
2282 }
2283 fz_always(gctx) {
2284 fz_drop_buffer(gctx, contents);
2285 pdf_drop_obj(gctx, page_obj);
2286 pdf_drop_obj(gctx, resources);
2287 }
2288 fz_catch(gctx) {
2289 return NULL;
2290 }
2291
2292 Py_RETURN_NONE;
2293 }
2294
2295 //------------------------------------------------------------------
2296 // Create sub-document to keep only selected pages.
2297 // Parameter is a Python sequence of the wanted page numbers.
2298 //------------------------------------------------------------------
2299 FITZEXCEPTION(select, !result)
2300 %pythonprepend select %{"""Build sub-pdf with page numbers in the list."""
2301 if self.is_closed or self.is_encrypted:
2302 raise ValueError("document closed or encrypted")
2303 if not self.is_pdf:
2304 raise ValueError("is no PDF")
2305 if not hasattr(pyliste, "__getitem__"):
2306 raise ValueError("sequence required")
2307 if len(pyliste) == 0 or min(pyliste) not in range(len(self)) or max(pyliste) not in range(len(self)):
2308 raise ValueError("bad page number(s)")
2309 pyliste = tuple(pyliste)%}
2310 %pythonappend select %{self._reset_page_refs()%}
2311 PyObject *select(PyObject *pyliste)
2312 {
2313 // preparatory stuff:
2314 // (1) get underlying pdf document,
2315 // (2) transform Python list into integer array
2316
2317 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2318 int *pages = NULL;
2319 fz_try(gctx) {
2320 // call retainpages (code copy of fz_clean_file.c)
2321 int i, len = (int) PyTuple_Size(pyliste);
2322 pages = fz_realloc_array(gctx, pages, len, int);
2323 for (i = 0; i < len; i++) {
2324 pages[i] = (int) PyLong_AsLong(PyTuple_GET_ITEM(pyliste, (Py_ssize_t) i));
2325 }
2326 pdf_rearrange_pages(gctx, pdf, len, pages);
2327 if (pdf->rev_page_map)
2328 {
2329 pdf_drop_page_tree(gctx, pdf);
2330 }
2331 }
2332 fz_always(gctx) {
2333 fz_free(gctx, pages);
2334 }
2335 fz_catch(gctx) {
2336 return NULL;
2337 }
2338
2339 Py_RETURN_NONE;
2340 }
2341
2342 //------------------------------------------------------------------
2343 // remove one page
2344 //------------------------------------------------------------------
2345 FITZEXCEPTION(_delete_page, !result)
2346 PyObject *_delete_page(int pno)
2347 {
2348 fz_try(gctx) {
2349 fz_document *doc = (fz_document *) $self;
2350 pdf_document *pdf = pdf_specifics(gctx, doc);
2351 pdf_delete_page(gctx, pdf, pno);
2352 if (pdf->rev_page_map)
2353 {
2354 pdf_drop_page_tree(gctx, pdf);
2355 }
2356 }
2357 fz_catch(gctx) {
2358 return NULL;
2359 }
2360 Py_RETURN_NONE;
2361 }
2362
2363 //------------------------------------------------------------------
2364 // get document permissions
2365 //------------------------------------------------------------------
2366 %pythoncode%{@property%}
2367 %pythonprepend permissions %{
2368 """Document permissions."""
2369
2370 if self.is_encrypted:
2371 return 0
2372 %}
2373 PyObject *permissions()
2374 {
2375 fz_document *doc = (fz_document *) $self;
2376 pdf_document *pdf = pdf_document_from_fz_document(gctx, doc);
2377
2378 // for PDF return result of standard function
2379 if (pdf)
2380 return Py_BuildValue("i", pdf_document_permissions(gctx, pdf));
2381
2382 // otherwise simulate the PDF return value
2383 int perm = (int) 0xFFFFFFFC; // all permissions granted
2384 // now switch off where needed
2385 if (!fz_has_permission(gctx, doc, FZ_PERMISSION_PRINT))
2386 perm = perm ^ PDF_PERM_PRINT;
2387 if (!fz_has_permission(gctx, doc, FZ_PERMISSION_EDIT))
2388 perm = perm ^ PDF_PERM_MODIFY;
2389 if (!fz_has_permission(gctx, doc, FZ_PERMISSION_COPY))
2390 perm = perm ^ PDF_PERM_COPY;
2391 if (!fz_has_permission(gctx, doc, FZ_PERMISSION_ANNOTATE))
2392 perm = perm ^ PDF_PERM_ANNOTATE;
2393 return Py_BuildValue("i", perm);
2394 }
2395
2396
2397 FITZEXCEPTION(journal_enable, !result)
2398 CLOSECHECK(journal_enable, """Activate document journalling.""")
2399 PyObject *journal_enable()
2400 {
2401 fz_try(gctx) {
2402 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2403 ASSERT_PDF(pdf);
2404 pdf_enable_journal(gctx, pdf);
2405 }
2406 fz_catch(gctx) {
2407 return NULL;
2408 }
2409 Py_RETURN_NONE;
2410 }
2411
2412
2413 FITZEXCEPTION(journal_start_op, !result)
2414 CLOSECHECK(journal_start_op, """Begin a journalling operation.""")
2415 PyObject *journal_start_op(const char *name=NULL)
2416 {
2417 fz_try(gctx) {
2418 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2419 ASSERT_PDF(pdf);
2420 if (!pdf->journal) {
2421 RAISEPY(gctx, "Journalling not enabled", PyExc_RuntimeError);
2422 }
2423 if (name) {
2424 pdf_begin_operation(gctx, pdf, name);
2425 } else {
2426 pdf_begin_implicit_operation(gctx, pdf);
2427 }
2428 }
2429 fz_catch(gctx) {
2430 return NULL;
2431 }
2432 Py_RETURN_NONE;
2433 }
2434
2435
2436 FITZEXCEPTION(journal_stop_op, !result)
2437 CLOSECHECK(journal_stop_op, """End a journalling operation.""")
2438 PyObject *journal_stop_op()
2439 {
2440 fz_try(gctx) {
2441 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2442 ASSERT_PDF(pdf);
2443 pdf_end_operation(gctx, pdf);
2444 }
2445 fz_catch(gctx) {
2446 return NULL;
2447 }
2448 Py_RETURN_NONE;
2449 }
2450
2451
2452 FITZEXCEPTION(journal_position, !result)
2453 CLOSECHECK(journal_position, """Show journalling state.""")
2454 PyObject *journal_position()
2455 {
2456 int rc, steps=0;
2457 fz_try(gctx) {
2458 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2459 ASSERT_PDF(pdf);
2460 rc = pdf_undoredo_state(gctx, pdf, &steps);
2461 }
2462 fz_catch(gctx) {
2463 return NULL;
2464 }
2465 return Py_BuildValue("ii", rc, steps);
2466 }
2467
2468
2469 FITZEXCEPTION(journal_op_name, !result)
2470 CLOSECHECK(journal_op_name, """Show operation name for given step.""")
2471 PyObject *journal_op_name(int step)
2472 {
2473 const char *name=NULL;
2474 fz_try(gctx) {
2475 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2476 ASSERT_PDF(pdf);
2477 name = pdf_undoredo_step(gctx, pdf, step);
2478 }
2479 fz_catch(gctx) {
2480 return NULL;
2481 }
2482 if (name) {
2483 return PyUnicode_FromString(name);
2484 } else {
2485 Py_RETURN_NONE;
2486 }
2487 }
2488
2489
2490 FITZEXCEPTION(journal_can_do, !result)
2491 CLOSECHECK(journal_can_do, """Show if undo and / or redo are possible.""")
2492 PyObject *journal_can_do()
2493 {
2494 int undo=0, redo=0;
2495 fz_try(gctx) {
2496 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2497 ASSERT_PDF(pdf);
2498 undo = pdf_can_undo(gctx, pdf);
2499 redo = pdf_can_redo(gctx, pdf);
2500 }
2501 fz_catch(gctx) {
2502 return NULL;
2503 }
2504 return Py_BuildValue("{s:N,s:N}", "undo", JM_BOOL(undo), "redo", JM_BOOL(redo));
2505 }
2506
2507
2508 FITZEXCEPTION(journal_undo, !result)
2509 CLOSECHECK(journal_undo, """Move backwards in the journal.""")
2510 PyObject *journal_undo()
2511 {
2512 fz_try(gctx) {
2513 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2514 ASSERT_PDF(pdf);
2515 pdf_undo(gctx, pdf);
2516 }
2517 fz_catch(gctx) {
2518 return NULL;
2519 }
2520 Py_RETURN_TRUE;
2521 }
2522
2523
2524 FITZEXCEPTION(journal_redo, !result)
2525 CLOSECHECK(journal_redo, """Move forward in the journal.""")
2526 PyObject *journal_redo()
2527 {
2528 fz_try(gctx) {
2529 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2530 ASSERT_PDF(pdf);
2531 pdf_redo(gctx, pdf);
2532 }
2533 fz_catch(gctx) {
2534 return NULL;
2535 }
2536 Py_RETURN_TRUE;
2537 }
2538
2539
2540 FITZEXCEPTION(journal_save, !result)
2541 CLOSECHECK(journal_save, """Save journal to a file.""")
2542 PyObject *journal_save(PyObject *filename)
2543 {
2544 fz_output *out = NULL;
2545 fz_try(gctx) {
2546 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2547 ASSERT_PDF(pdf);
2548 if (PyUnicode_Check(filename)) {
2549 pdf_save_journal(gctx, pdf, (const char *) PyUnicode_AsUTF8(filename));
2550 } else {
2551 out = JM_new_output_fileptr(gctx, filename);
2552 pdf_write_journal(gctx, pdf, out);
2553 }
2554 }
2555 fz_always(gctx) {
2556 fz_drop_output(gctx, out);
2557 }
2558 fz_catch(gctx) {
2559 return NULL;
2560 }
2561 Py_RETURN_NONE;
2562 }
2563
2564
2565 FITZEXCEPTION(journal_load, !result)
2566 CLOSECHECK(journal_load, """Load a journal from a file.""")
2567 PyObject *journal_load(PyObject *filename)
2568 {
2569 fz_buffer *res = NULL;
2570 fz_stream *stm = NULL;
2571 fz_try(gctx) {
2572 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2573 ASSERT_PDF(pdf);
2574 if (PyUnicode_Check(filename)) {
2575 pdf_load_journal(gctx, pdf, PyUnicode_AsUTF8(filename));
2576 } else {
2577 res = JM_BufferFromBytes(gctx, filename);
2578 stm = fz_open_buffer(gctx, res);
2579 pdf_deserialise_journal(gctx, pdf, stm);
2580 }
2581 if (!pdf->journal) {
2582 RAISEPY(gctx, "Journal and document do not match", JM_Exc_FileDataError);
2583 }
2584 }
2585 fz_always(gctx) {
2586 fz_drop_stream(gctx, stm);
2587 fz_drop_buffer(gctx, res);
2588 }
2589 fz_catch(gctx) {
2590 return NULL;
2591 }
2592 Py_RETURN_NONE;
2593 }
2594
2595
2596 FITZEXCEPTION(journal_is_enabled, !result)
2597 CLOSECHECK(journal_is_enabled, """Check if journalling is enabled.""")
2598 PyObject *journal_is_enabled()
2599 {
2600 int enabled = 0;
2601 fz_try(gctx) {
2602 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2603 enabled = pdf && pdf->journal;
2604 }
2605 fz_catch(gctx) {
2606 return NULL;
2607 }
2608 return JM_BOOL(enabled);
2609 }
2610
2611
2612 FITZEXCEPTION(_get_char_widths, !result)
2613 CLOSECHECK(_get_char_widths, """Return list of glyphs and glyph widths of a font.""")
2614 PyObject *_get_char_widths(int xref, char *bfname, char *ext,
2615 int ordering, int limit, int idx = 0)
2616 {
2617 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2618 PyObject *wlist = NULL;
2619 int i, glyph, mylimit;
2620 mylimit = limit;
2621 if (mylimit < 256) mylimit = 256;
2622 const unsigned char *data;
2623 int size, index;
2624 fz_font *font = NULL;
2625 fz_buffer *buf = NULL;
2626
2627 fz_try(gctx) {
2628 ASSERT_PDF(pdf);
2629 if (ordering >= 0) {
2630 data = fz_lookup_cjk_font(gctx, ordering, &size, &index);
2631 font = fz_new_font_from_memory(gctx, NULL, data, size, index, 0);
2632 goto weiter;
2633 }
2634 data = fz_lookup_base14_font(gctx, bfname, &size);
2635 if (data) {
2636 font = fz_new_font_from_memory(gctx, bfname, data, size, 0, 0);
2637 goto weiter;
2638 }
2639 buf = JM_get_fontbuffer(gctx, pdf, xref);
2640 if (!buf) {
2641 fz_throw(gctx, FZ_ERROR_GENERIC, "font at xref %d is not supported", xref);
2642 }
2643 font = fz_new_font_from_buffer(gctx, NULL, buf, idx, 0);
2644
2645 weiter:;
2646 wlist = PyList_New(0);
2647 float adv;
2648 for (i = 0; i < mylimit; i++) {
2649 glyph = fz_encode_character(gctx, font, i);
2650 adv = fz_advance_glyph(gctx, font, glyph, 0);
2651 if (ordering >= 0) {
2652 glyph = i;
2653 }
2654 if (glyph > 0) {
2655 LIST_APPEND_DROP(wlist, Py_BuildValue("if", glyph, adv));
2656 } else {
2657 LIST_APPEND_DROP(wlist, Py_BuildValue("if", glyph, 0.0));
2658 }
2659 }
2660 }
2661 fz_always(gctx) {
2662 fz_drop_buffer(gctx, buf);
2663 fz_drop_font(gctx, font);
2664 }
2665 fz_catch(gctx) {
2666 return NULL;
2667 }
2668 return wlist;
2669 }
2670
2671
2672 FITZEXCEPTION(page_xref, !result)
2673 CLOSECHECK0(page_xref, """Get xref of page number.""")
2674 PyObject *page_xref(int pno)
2675 {
2676 fz_document *this_doc = (fz_document *) $self;
2677 int page_count = fz_count_pages(gctx, this_doc);
2678 int n = pno;
2679 while (n < 0) n += page_count;
2680 pdf_document *pdf = pdf_specifics(gctx, this_doc);
2681 int xref = 0;
2682 fz_try(gctx) {
2683 if (n >= page_count) {
2684 RAISEPY(gctx, MSG_BAD_PAGENO, PyExc_ValueError);
2685 }
2686 ASSERT_PDF(pdf);
2687 xref = pdf_to_num(gctx, pdf_lookup_page_obj(gctx, pdf, n));
2688 }
2689 fz_catch(gctx) {
2690 return NULL;
2691 }
2692 return Py_BuildValue("i", xref);
2693 }
2694
2695
2696 FITZEXCEPTION(page_annot_xrefs, !result)
2697 CLOSECHECK0(page_annot_xrefs, """Get list annotations of page number.""")
2698 PyObject *page_annot_xrefs(int pno)
2699 {
2700 fz_document *this_doc = (fz_document *) $self;
2701 int page_count = fz_count_pages(gctx, this_doc);
2702 int n = pno;
2703 while (n < 0) n += page_count;
2704 pdf_document *pdf = pdf_specifics(gctx, this_doc);
2705 PyObject *annots = NULL;
2706 fz_try(gctx) {
2707 if (n >= page_count) {
2708 RAISEPY(gctx, MSG_BAD_PAGENO, PyExc_ValueError);
2709 }
2710 ASSERT_PDF(pdf);
2711 annots = JM_get_annot_xref_list(gctx, pdf_lookup_page_obj(gctx, pdf, n));
2712 }
2713 fz_catch(gctx) {
2714 return NULL;
2715 }
2716 return annots;
2717 }
2718
2719
2720 FITZEXCEPTION(page_cropbox, !result)
2721 CLOSECHECK0(page_cropbox, """Get CropBox of page number (without loading page).""")
2722 %pythonappend page_cropbox %{val = Rect(JM_TUPLE3(val))%}
2723 PyObject *page_cropbox(int pno)
2724 {
2725 fz_document *this_doc = (fz_document *) $self;
2726 int page_count = fz_count_pages(gctx, this_doc);
2727 int n = pno;
2728 while (n < 0) n += page_count;
2729 pdf_obj *pageref = NULL;
2730 fz_var(pageref);
2731 pdf_document *pdf = pdf_specifics(gctx, this_doc);
2732 fz_try(gctx) {
2733 if (n >= page_count) {
2734 RAISEPY(gctx, MSG_BAD_PAGENO, PyExc_ValueError);
2735 }
2736 ASSERT_PDF(pdf);
2737 pageref = pdf_lookup_page_obj(gctx, pdf, n);
2738 }
2739 fz_catch(gctx) {
2740 return NULL;
2741 }
2742 return JM_py_from_rect(JM_cropbox(gctx, pageref));
2743 }
2744
2745
2746 FITZEXCEPTION(_getPageInfo, !result)
2747 CLOSECHECK(_getPageInfo, """List fonts, images, XObjects used on a page.""")
2748 PyObject *_getPageInfo(int pno, int what)
2749 {
2750 fz_document *doc = (fz_document *) $self;
2751 pdf_document *pdf = pdf_specifics(gctx, doc);
2752 pdf_obj *pageref, *rsrc;
2753 PyObject *liste = NULL, *tracer = NULL;
2754 fz_var(liste);
2755 fz_var(tracer);
2756 fz_try(gctx) {
2757 int page_count = fz_count_pages(gctx, doc);
2758 int n = pno; // pno < 0 is allowed
2759 while (n < 0) n += page_count; // make it non-negative
2760 if (n >= page_count) {
2761 RAISEPY(gctx, MSG_BAD_PAGENO, PyExc_ValueError);
2762 }
2763 ASSERT_PDF(pdf);
2764 pageref = pdf_lookup_page_obj(gctx, pdf, n);
2765 rsrc = pdf_dict_get_inheritable(gctx,
2766 pageref, PDF_NAME(Resources));
2767 liste = PyList_New(0);
2768 tracer = PyList_New(0);
2769 if (rsrc) {
2770 JM_scan_resources(gctx, pdf, rsrc, liste, what, 0, tracer);
2771 }
2772 }
2773 fz_always(gctx) {
2774 Py_CLEAR(tracer);
2775 }
2776 fz_catch(gctx) {
2777 Py_CLEAR(liste);
2778 return NULL;
2779 }
2780 return liste;
2781 }
2782
2783 FITZEXCEPTION(extract_font, !result)
2784 CLOSECHECK(extract_font, """Get a font by xref. Returns a tuple or dictionary.""")
2785 PyObject *extract_font(int xref=0, int info_only=0, PyObject *named=NULL)
2786 {
2787 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2788
2789 fz_try(gctx) {
2790 ASSERT_PDF(pdf);
2791 }
2792 fz_catch(gctx) {
2793 return NULL;
2794 }
2795
2796 fz_buffer *buffer = NULL;
2797 pdf_obj *obj, *basefont, *bname;
2798 PyObject *bytes = NULL;
2799 char *ext = NULL;
2800 PyObject *rc;
2801 fz_try(gctx) {
2802 obj = pdf_load_object(gctx, pdf, xref);
2803 pdf_obj *type = pdf_dict_get(gctx, obj, PDF_NAME(Type));
2804 pdf_obj *subtype = pdf_dict_get(gctx, obj, PDF_NAME(Subtype));
2805 if(pdf_name_eq(gctx, type, PDF_NAME(Font)) &&
2806 strncmp(pdf_to_name(gctx, subtype), "CIDFontType", 11) != 0) {
2807 basefont = pdf_dict_get(gctx, obj, PDF_NAME(BaseFont));
2808 if (!basefont || pdf_is_null(gctx, basefont)) {
2809 bname = pdf_dict_get(gctx, obj, PDF_NAME(Name));
2810 } else {
2811 bname = basefont;
2812 }
2813 ext = JM_get_fontextension(gctx, pdf, xref);
2814 if (strcmp(ext, "n/a") != 0 && !info_only) {
2815 buffer = JM_get_fontbuffer(gctx, pdf, xref);
2816 bytes = JM_BinFromBuffer(gctx, buffer);
2817 fz_drop_buffer(gctx, buffer);
2818 } else {
2819 bytes = Py_BuildValue("y", "");
2820 }
2821 if (PyObject_Not(named)) {
2822 rc = PyTuple_New(4);
2823 PyTuple_SET_ITEM(rc, 0, JM_EscapeStrFromStr(pdf_to_name(gctx, bname)));
2824 PyTuple_SET_ITEM(rc, 1, JM_UnicodeFromStr(ext));
2825 PyTuple_SET_ITEM(rc, 2, JM_UnicodeFromStr(pdf_to_name(gctx, subtype)));
2826 PyTuple_SET_ITEM(rc, 3, bytes);
2827 } else {
2828 rc = PyDict_New();
2829 DICT_SETITEM_DROP(rc, dictkey_name, JM_EscapeStrFromStr(pdf_to_name(gctx, bname)));
2830 DICT_SETITEM_DROP(rc, dictkey_ext, JM_UnicodeFromStr(ext));
2831 DICT_SETITEM_DROP(rc, dictkey_type, JM_UnicodeFromStr(pdf_to_name(gctx, subtype)));
2832 DICT_SETITEM_DROP(rc, dictkey_content, bytes);
2833 }
2834 } else {
2835 if (PyObject_Not(named)) {
2836 rc = Py_BuildValue("sssy", "", "", "", "");
2837 } else {
2838 rc = PyDict_New();
2839 DICT_SETITEM_DROP(rc, dictkey_name, Py_BuildValue("s", ""));
2840 DICT_SETITEM_DROP(rc, dictkey_ext, Py_BuildValue("s", ""));
2841 DICT_SETITEM_DROP(rc, dictkey_type, Py_BuildValue("s", ""));
2842 DICT_SETITEM_DROP(rc, dictkey_content, Py_BuildValue("y", ""));
2843 }
2844 }
2845 }
2846 fz_always(gctx) {
2847 pdf_drop_obj(gctx, obj);
2848 JM_PyErr_Clear;
2849 }
2850 fz_catch(gctx) {
2851 if (PyObject_Not(named)) {
2852 rc = Py_BuildValue("sssy", "invalid-name", "", "", "");
2853 } else {
2854 rc = PyDict_New();
2855 DICT_SETITEM_DROP(rc, dictkey_name, Py_BuildValue("s", "invalid-name"));
2856 DICT_SETITEM_DROP(rc, dictkey_ext, Py_BuildValue("s", ""));
2857 DICT_SETITEM_DROP(rc, dictkey_type, Py_BuildValue("s", ""));
2858 DICT_SETITEM_DROP(rc, dictkey_content, Py_BuildValue("y", ""));
2859 }
2860 }
2861 return rc;
2862 }
2863
2864
2865 FITZEXCEPTION(extract_image, !result)
2866 CLOSECHECK(extract_image, """Get image by xref. Returns a dictionary.""")
2867 PyObject *extract_image(int xref)
2868 {
2869 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2870 pdf_obj *obj = NULL;
2871 fz_buffer *res = NULL;
2872 fz_image *img = NULL;
2873 PyObject *rc = NULL;
2874 const char *ext = NULL;
2875 const char *cs_name = NULL;
2876 int img_type = 0, xres, yres, colorspace;
2877 int smask = 0, width, height, bpc;
2878 fz_compressed_buffer *cbuf = NULL;
2879 fz_var(img);
2880 fz_var(res);
2881 fz_var(obj);
2882
2883 fz_try(gctx) {
2884 ASSERT_PDF(pdf);
2885 if (!INRANGE(xref, 1, pdf_xref_len(gctx, pdf)-1)) {
2886 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
2887 }
2888 obj = pdf_new_indirect(gctx, pdf, xref, 0);
2889 pdf_obj *subtype = pdf_dict_get(gctx, obj, PDF_NAME(Subtype));
2890
2891 if (!pdf_name_eq(gctx, subtype, PDF_NAME(Image))) {
2892 RAISEPY(gctx, "not an image", PyExc_ValueError);
2893 }
2894
2895 pdf_obj *o = pdf_dict_geta(gctx, obj, PDF_NAME(SMask), PDF_NAME(Mask));
2896 if (o) smask = pdf_to_num(gctx, o);
2897
2898 if (pdf_is_jpx_image(gctx, obj)) {
2899 img_type = FZ_IMAGE_JPX;
2900 res = pdf_load_stream(gctx, obj);
2901 ext = "jpx";
2902 }
2903 if (JM_is_jbig2_image(gctx, obj)) {
2904 img_type = FZ_IMAGE_JBIG2;
2905 res = pdf_load_stream(gctx, obj);
2906 ext = "jb2";
2907 }
2908 if (img_type == FZ_IMAGE_UNKNOWN) {
2909 res = pdf_load_raw_stream(gctx, obj);
2910 unsigned char *c = NULL;
2911 fz_buffer_storage(gctx, res, &c);
2912 img_type = fz_recognize_image_format(gctx, c);
2913 ext = JM_image_extension(img_type);
2914 }
2915 if (img_type == FZ_IMAGE_UNKNOWN) {
2916 fz_drop_buffer(gctx, res);
2917 res = NULL;
2918 img = pdf_load_image(gctx, pdf, obj);
2919 cbuf = fz_compressed_image_buffer(gctx, img);
2920 if (cbuf &&
2921 cbuf->params.type != FZ_IMAGE_RAW &&
2922 cbuf->params.type != FZ_IMAGE_FAX &&
2923 cbuf->params.type != FZ_IMAGE_FLATE &&
2924 cbuf->params.type != FZ_IMAGE_LZW &&
2925 cbuf->params.type != FZ_IMAGE_RLD) {
2926 img_type = cbuf->params.type;
2927 ext = JM_image_extension(img_type);
2928 res = cbuf->buffer;
2929 } else {
2930 res = fz_new_buffer_from_image_as_png(gctx, img,
2931 fz_default_color_params);
2932 ext = "png";
2933 }
2934 } else {
2935 img = fz_new_image_from_buffer(gctx, res);
2936 }
2937
2938 fz_image_resolution(img, &xres, &yres);
2939 width = img->w;
2940 height = img->h;
2941 colorspace = img->n;
2942 bpc = img->bpc;
2943 cs_name = fz_colorspace_name(gctx, img->colorspace);
2944
2945 rc = PyDict_New();
2946 DICT_SETITEM_DROP(rc, dictkey_ext,
2947 JM_UnicodeFromStr(ext));
2948 DICT_SETITEM_DROP(rc, dictkey_smask,
2949 Py_BuildValue("i", smask));
2950 DICT_SETITEM_DROP(rc, dictkey_width,
2951 Py_BuildValue("i", width));
2952 DICT_SETITEM_DROP(rc, dictkey_height,
2953 Py_BuildValue("i", height));
2954 DICT_SETITEM_DROP(rc, dictkey_colorspace,
2955 Py_BuildValue("i", colorspace));
2956 DICT_SETITEM_DROP(rc, dictkey_bpc,
2957 Py_BuildValue("i", bpc));
2958 DICT_SETITEM_DROP(rc, dictkey_xres,
2959 Py_BuildValue("i", xres));
2960 DICT_SETITEM_DROP(rc, dictkey_yres,
2961 Py_BuildValue("i", yres));
2962 DICT_SETITEM_DROP(rc, dictkey_cs_name,
2963 JM_UnicodeFromStr(cs_name));
2964 DICT_SETITEM_DROP(rc, dictkey_image,
2965 JM_BinFromBuffer(gctx, res));
2966 }
2967 fz_always(gctx) {
2968 fz_drop_image(gctx, img);
2969 if (!cbuf) fz_drop_buffer(gctx, res);
2970 pdf_drop_obj(gctx, obj);
2971 }
2972
2973 fz_catch(gctx) {
2974 Py_CLEAR(rc);
2975 fz_warn(gctx, "%s", fz_caught_message(gctx));
2976 Py_RETURN_FALSE;
2977 }
2978 if (!rc)
2979 Py_RETURN_NONE;
2980 return rc;
2981 }
2982
2983
2984 //------------------------------------------------------------------
2985 // Delete all bookmarks (table of contents)
2986 // returns list of deleted (now available) xref numbers
2987 //------------------------------------------------------------------
2988 CLOSECHECK(_delToC, """Delete the TOC.""")
2989 %pythonappend _delToC %{self.init_doc()%}
2990 PyObject *_delToC()
2991 {
2992 PyObject *xrefs = PyList_New(0); // create Python list
2993 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
2994 if (!pdf) return xrefs; // not a pdf
2995
2996 pdf_obj *root, *olroot, *first;
2997 int xref_count, olroot_xref, i, xref;
2998
2999 // get the main root
3000 root = pdf_dict_get(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root));
3001 // get the outline root
3002 olroot = pdf_dict_get(gctx, root, PDF_NAME(Outlines));
3003 if (!olroot) return xrefs; // no outlines or some problem
3004
3005 first = pdf_dict_get(gctx, olroot, PDF_NAME(First)); // first outline
3006
3007 xrefs = JM_outline_xrefs(gctx, first, xrefs);
3008 xref_count = (int) PyList_Size(xrefs);
3009
3010 olroot_xref = pdf_to_num(gctx, olroot); // delete OL root
3011 pdf_delete_object(gctx, pdf, olroot_xref); // delete OL root
3012 pdf_dict_del(gctx, root, PDF_NAME(Outlines)); // delete OL root
3013
3014 for (i = 0; i < xref_count; i++)
3015 {
3016 JM_INT_ITEM(xrefs, i, &xref);
3017 pdf_delete_object(gctx, pdf, xref); // delete outline item
3018 }
3019 LIST_APPEND_DROP(xrefs, Py_BuildValue("i", olroot_xref));
3020
3021 return xrefs;
3022 }
3023
3024
3025 //------------------------------------------------------------------
3026 // Check: is xref a stream object?
3027 //------------------------------------------------------------------
3028 CLOSECHECK0(xref_is_stream, """Check if xref is a stream object.""")
3029 PyObject *xref_is_stream(int xref=0)
3030 {
3031 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3032 if (!pdf) Py_RETURN_FALSE; // not a PDF
3033 return JM_BOOL(pdf_obj_num_is_stream(gctx, pdf, xref));
3034 }
3035
3036 //------------------------------------------------------------------
3037 // Return or set NeedAppearances
3038 //------------------------------------------------------------------
3039 %pythonprepend need_appearances
3040 %{"""Get/set the NeedAppearances value."""
3041 if self.is_closed:
3042 raise ValueError("document closed")
3043 if not self.is_form_pdf:
3044 return None
3045 %}
3046 PyObject *need_appearances(PyObject *value=NULL)
3047 {
3048 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3049 int oldval = -1;
3050 pdf_obj *app = NULL;
3051 char appkey[] = "NeedAppearances";
3052 fz_try(gctx) {
3053 pdf_obj *form = pdf_dict_getp(gctx, pdf_trailer(gctx, pdf),
3054 "Root/AcroForm");
3055 app = pdf_dict_gets(gctx, form, appkey);
3056 if (pdf_is_bool(gctx, app)) {
3057 oldval = pdf_to_bool(gctx, app);
3058 }
3059
3060 if (EXISTS(value)) {
3061 pdf_dict_puts_drop(gctx, form, appkey, PDF_TRUE);
3062 } else if (value == Py_False) {
3063 pdf_dict_puts_drop(gctx, form, appkey, PDF_FALSE);
3064 }
3065 }
3066 fz_catch(gctx) {
3067 Py_RETURN_NONE;
3068 }
3069 if (value != Py_None) {
3070 return value;
3071 }
3072 if (oldval >= 0) {
3073 return JM_BOOL(oldval);
3074 }
3075 Py_RETURN_NONE;
3076 }
3077
3078 //------------------------------------------------------------------
3079 // Return the /SigFlags value
3080 //------------------------------------------------------------------
3081 CLOSECHECK0(get_sigflags, """Get the /SigFlags value.""")
3082 int get_sigflags()
3083 {
3084 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3085 if (!pdf) return -1; // not a PDF
3086 int sigflag = -1;
3087 fz_try(gctx) {
3088 pdf_obj *sigflags = pdf_dict_getl(gctx,
3089 pdf_trailer(gctx, pdf),
3090 PDF_NAME(Root),
3091 PDF_NAME(AcroForm),
3092 PDF_NAME(SigFlags),
3093 NULL);
3094 if (sigflags) {
3095 sigflag = (int) pdf_to_int(gctx, sigflags);
3096 }
3097 }
3098 fz_catch(gctx) {
3099 return -1; // any problem
3100 }
3101 return sigflag;
3102 }
3103
3104 //------------------------------------------------------------------
3105 // Check: is this an AcroForm with at least one field?
3106 //------------------------------------------------------------------
3107 CLOSECHECK0(is_form_pdf, """Either False or PDF field count.""")
3108 %pythoncode%{@property%}
3109 PyObject *is_form_pdf()
3110 {
3111 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3112 if (!pdf) Py_RETURN_FALSE; // not a PDF
3113 int count = -1; // init count
3114 fz_try(gctx) {
3115 pdf_obj *fields = pdf_dict_getl(gctx,
3116 pdf_trailer(gctx, pdf),
3117 PDF_NAME(Root),
3118 PDF_NAME(AcroForm),
3119 PDF_NAME(Fields),
3120 NULL);
3121 if (pdf_is_array(gctx, fields)) {
3122 count = pdf_array_len(gctx, fields);
3123 }
3124 }
3125 fz_catch(gctx) {
3126 Py_RETURN_FALSE;
3127 }
3128 if (count >= 0) {
3129 return Py_BuildValue("i", count);
3130 } else {
3131 Py_RETURN_FALSE;
3132 }
3133 }
3134
3135 //------------------------------------------------------------------
3136 // Return the list of field font resource names
3137 //------------------------------------------------------------------
3138 CLOSECHECK0(FormFonts, """Get list of field font resource names.""")
3139 %pythoncode%{@property%}
3140 PyObject *FormFonts()
3141 {
3142 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3143 if (!pdf) Py_RETURN_NONE; // not a PDF
3144 pdf_obj *fonts = NULL;
3145 PyObject *liste = PyList_New(0);
3146 fz_var(liste);
3147 fz_try(gctx) {
3148 fonts = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root), PDF_NAME(AcroForm), PDF_NAME(DR), PDF_NAME(Font), NULL);
3149 if (fonts && pdf_is_dict(gctx, fonts)) // fonts exist
3150 {
3151 int i, n = pdf_dict_len(gctx, fonts);
3152 for (i = 0; i < n; i++)
3153 {
3154 pdf_obj *f = pdf_dict_get_key(gctx, fonts, i);
3155 LIST_APPEND_DROP(liste, JM_UnicodeFromStr(pdf_to_name(gctx, f)));
3156 }
3157 }
3158 }
3159 fz_catch(gctx) {
3160 Py_DECREF(liste);
3161 Py_RETURN_NONE; // any problem yields None
3162 }
3163 return liste;
3164 }
3165
3166 //------------------------------------------------------------------
3167 // Add a field font
3168 //------------------------------------------------------------------
3169 FITZEXCEPTION(_addFormFont, !result)
3170 CLOSECHECK(_addFormFont, """Add new form font.""")
3171 PyObject *_addFormFont(char *name, char *font)
3172 {
3173 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3174 if (!pdf) Py_RETURN_NONE; // not a PDF
3175 pdf_obj *fonts = NULL;
3176 fz_try(gctx) {
3177 fonts = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root),
3178 PDF_NAME(AcroForm), PDF_NAME(DR), PDF_NAME(Font), NULL);
3179 if (!fonts || !pdf_is_dict(gctx, fonts)) {
3180 RAISEPY(gctx, "PDF has no form fonts yet", PyExc_RuntimeError);
3181 }
3182 pdf_obj *k = pdf_new_name(gctx, (const char *) name);
3183 pdf_obj *v = JM_pdf_obj_from_str(gctx, pdf, font);
3184 pdf_dict_put(gctx, fonts, k, v);
3185 }
3186 fz_catch(gctx) NULL;
3187 Py_RETURN_NONE;
3188 }
3189
3190 //------------------------------------------------------------------
3191 // Get Xref Number of Outline Root, create it if missing
3192 //------------------------------------------------------------------
3193 FITZEXCEPTION(_getOLRootNumber, !result)
3194 CLOSECHECK(_getOLRootNumber, """Get xref of Outline Root, create it if missing.""")
3195 PyObject *_getOLRootNumber()
3196 {
3197 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3198 pdf_obj *ind_obj = NULL;
3199 pdf_obj *olroot2 = NULL;
3200 int ret;
3201 fz_var(ind_obj);
3202 fz_var(olroot2);
3203 fz_try(gctx) {
3204 ASSERT_PDF(pdf);
3205 // get main root
3206 pdf_obj *root = pdf_dict_get(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root));
3207 // get outline root
3208 pdf_obj *olroot = pdf_dict_get(gctx, root, PDF_NAME(Outlines));
3209 if (!olroot)
3210 {
3211 olroot2 = pdf_new_dict(gctx, pdf, 4);
3212 pdf_dict_put(gctx, olroot2, PDF_NAME(Type), PDF_NAME(Outlines));
3213 ind_obj = pdf_add_object(gctx, pdf, olroot2);
3214 pdf_dict_put(gctx, root, PDF_NAME(Outlines), ind_obj);
3215 olroot = pdf_dict_get(gctx, root, PDF_NAME(Outlines));
3216
3217 }
3218 ret = pdf_to_num(gctx, olroot);
3219 }
3220 fz_always(gctx) {
3221 pdf_drop_obj(gctx, ind_obj);
3222 pdf_drop_obj(gctx, olroot2);
3223 }
3224 fz_catch(gctx) {
3225 return NULL;
3226 }
3227 return Py_BuildValue("i", ret);
3228 }
3229
3230 //------------------------------------------------------------------
3231 // Get a new Xref number
3232 //------------------------------------------------------------------
3233 FITZEXCEPTION(get_new_xref, !result)
3234 CLOSECHECK(get_new_xref, """Make a new xref.""")
3235 PyObject *get_new_xref()
3236 {
3237 int xref = 0;
3238 fz_try(gctx) {
3239 fz_document *doc = (fz_document *) $self;
3240 pdf_document *pdf = pdf_specifics(gctx, doc);
3241 ASSERT_PDF(pdf);
3242 ENSURE_OPERATION(gctx, pdf);
3243 xref = pdf_create_object(gctx, pdf);
3244 }
3245 fz_catch(gctx) {
3246 return NULL;
3247 }
3248 return Py_BuildValue("i", xref);
3249 }
3250
3251 //------------------------------------------------------------------
3252 // Get Length of XREF table
3253 //------------------------------------------------------------------
3254 FITZEXCEPTION(xref_length, !result)
3255 CLOSECHECK0(xref_length, """Get length of xref table.""")
3256 PyObject *xref_length()
3257 {
3258 int xreflen = 0;
3259 fz_try(gctx) {
3260 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3261 if (pdf) xreflen = pdf_xref_len(gctx, pdf);
3262 }
3263 fz_catch(gctx) {
3264 return NULL;
3265 }
3266 return Py_BuildValue("i", xreflen);
3267 }
3268
3269 //------------------------------------------------------------------
3270 // Get XML Metadata
3271 //------------------------------------------------------------------
3272 CLOSECHECK0(get_xml_metadata, """Get document XML metadata.""")
3273 PyObject *get_xml_metadata()
3274 {
3275 PyObject *rc = NULL;
3276 fz_buffer *buff = NULL;
3277 pdf_obj *xml = NULL;
3278 fz_try(gctx) {
3279 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3280 if (pdf) {
3281 xml = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root), PDF_NAME(Metadata), NULL);
3282 }
3283 if (xml) {
3284 buff = pdf_load_stream(gctx, xml);
3285 rc = JM_UnicodeFromBuffer(gctx, buff);
3286 } else {
3287 rc = EMPTY_STRING;
3288 }
3289 }
3290 fz_always(gctx) {
3291 fz_drop_buffer(gctx, buff);
3292 PyErr_Clear();
3293 }
3294 fz_catch(gctx) {
3295 return EMPTY_STRING;
3296 }
3297 return rc;
3298 }
3299
3300 //------------------------------------------------------------------
3301 // Get XML Metadata xref
3302 //------------------------------------------------------------------
3303 FITZEXCEPTION(xref_xml_metadata, !result)
3304 CLOSECHECK0(xref_xml_metadata, """Get xref of document XML metadata.""")
3305 PyObject *xref_xml_metadata()
3306 {
3307 int xref = 0;
3308 fz_try(gctx) {
3309 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3310 ASSERT_PDF(pdf);
3311 pdf_obj *root = pdf_dict_get(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root));
3312 if (!root) {
3313 RAISEPY(gctx, MSG_BAD_PDFROOT, JM_Exc_FileDataError);
3314 }
3315 pdf_obj *xml = pdf_dict_get(gctx, root, PDF_NAME(Metadata));
3316 if (xml) xref = pdf_to_num(gctx, xml);
3317 }
3318 fz_catch(gctx) {;}
3319 return Py_BuildValue("i", xref);
3320 }
3321
3322 //------------------------------------------------------------------
3323 // Delete XML Metadata
3324 //------------------------------------------------------------------
3325 FITZEXCEPTION(del_xml_metadata, !result)
3326 CLOSECHECK(del_xml_metadata, """Delete XML metadata.""")
3327 PyObject *del_xml_metadata()
3328 {
3329 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3330 fz_try(gctx) {
3331 ASSERT_PDF(pdf);
3332 pdf_obj *root = pdf_dict_get(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root));
3333 if (root) pdf_dict_del(gctx, root, PDF_NAME(Metadata));
3334 }
3335 fz_catch(gctx) {
3336 return NULL;
3337 }
3338
3339 Py_RETURN_NONE;
3340 }
3341
3342 //------------------------------------------------------------------
3343 // Set XML-based Metadata
3344 //------------------------------------------------------------------
3345 FITZEXCEPTION(set_xml_metadata, !result)
3346 CLOSECHECK(set_xml_metadata, """Store XML document level metadata.""")
3347 PyObject *set_xml_metadata(char *metadata)
3348 {
3349 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3350 fz_buffer *res = NULL;
3351 fz_try(gctx) {
3352 ASSERT_PDF(pdf);
3353 pdf_obj *root = pdf_dict_get(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root));
3354 if (!root) {
3355 RAISEPY(gctx, MSG_BAD_PDFROOT, JM_Exc_FileDataError);
3356 }
3357 res = fz_new_buffer_from_copied_data(gctx, (const unsigned char *) metadata, strlen(metadata));
3358 pdf_obj *xml = pdf_dict_get(gctx, root, PDF_NAME(Metadata));
3359 if (xml) {
3360 JM_update_stream(gctx, pdf, xml, res, 0);
3361 } else {
3362 xml = pdf_add_stream(gctx, pdf, res, NULL, 0);
3363 pdf_dict_put(gctx, xml, PDF_NAME(Type), PDF_NAME(Metadata));
3364 pdf_dict_put(gctx, xml, PDF_NAME(Subtype), PDF_NAME(XML));
3365 pdf_dict_put_drop(gctx, root, PDF_NAME(Metadata), xml);
3366 }
3367 }
3368 fz_always(gctx) {
3369 fz_drop_buffer(gctx, res);
3370 }
3371 fz_catch(gctx) {
3372 return NULL;
3373 }
3374
3375 Py_RETURN_NONE;
3376 }
3377
3378 //------------------------------------------------------------------
3379 // Get Object String of xref
3380 //------------------------------------------------------------------
3381 FITZEXCEPTION(xref_object, !result)
3382 CLOSECHECK0(xref_object, """Get xref object source as a string.""")
3383 PyObject *xref_object(int xref, int compressed=0, int ascii=0)
3384 {
3385 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3386 pdf_obj *obj = NULL;
3387 PyObject *text = NULL;
3388 fz_buffer *res=NULL;
3389 fz_try(gctx) {
3390 ASSERT_PDF(pdf);
3391 int xreflen = pdf_xref_len(gctx, pdf);
3392 if (!INRANGE(xref, 1, xreflen-1) && xref != -1) {
3393 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
3394 }
3395 if (xref > 0) {
3396 obj = pdf_load_object(gctx, pdf, xref);
3397 } else {
3398 obj = pdf_trailer(gctx, pdf);
3399 }
3400 res = JM_object_to_buffer(gctx, pdf_resolve_indirect(gctx, obj), compressed, ascii);
3401 text = JM_EscapeStrFromBuffer(gctx, res);
3402 }
3403 fz_always(gctx) {
3404 if (xref > 0) {
3405 pdf_drop_obj(gctx, obj);
3406 }
3407 fz_drop_buffer(gctx, res);
3408 }
3409 fz_catch(gctx) return EMPTY_STRING;
3410 return text;
3411 }
3412 %pythoncode %{
3413 def pdf_trailer(self, compressed: bool=False, ascii:bool=False)->str:
3414 """Get PDF trailer as a string."""
3415 return self.xref_object(-1, compressed=compressed, ascii=ascii)%}
3416
3417
3418 //------------------------------------------------------------------
3419 // Get compressed stream of an object by xref
3420 // Py_RETURN_NONE if not stream
3421 //------------------------------------------------------------------
3422 FITZEXCEPTION(xref_stream_raw, !result)
3423 CLOSECHECK(xref_stream_raw, """Get xref stream without decompression.""")
3424 PyObject *xref_stream_raw(int xref)
3425 {
3426 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3427 PyObject *r = NULL;
3428 pdf_obj *obj = NULL;
3429 fz_var(obj);
3430 fz_buffer *res = NULL;
3431 fz_var(res);
3432 fz_try(gctx) {
3433 ASSERT_PDF(pdf);
3434 int xreflen = pdf_xref_len(gctx, pdf);
3435 if (!INRANGE(xref, 1, xreflen-1) && xref != -1) {
3436 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
3437 }
3438 if (xref >= 0) {
3439 obj = pdf_new_indirect(gctx, pdf, xref, 0);
3440 } else {
3441 obj = pdf_trailer(gctx, pdf);
3442 }
3443 if (pdf_is_stream(gctx, obj))
3444 {
3445 res = pdf_load_raw_stream_number(gctx, pdf, xref);
3446 r = JM_BinFromBuffer(gctx, res);
3447 }
3448 }
3449 fz_always(gctx) {
3450 fz_drop_buffer(gctx, res);
3451 if (xref >= 0) {
3452 pdf_drop_obj(gctx, obj);
3453 }
3454 }
3455 fz_catch(gctx)
3456 {
3457 Py_CLEAR(r);
3458 return NULL;
3459 }
3460 if (!r) Py_RETURN_NONE;
3461 return r;
3462 }
3463
3464 //------------------------------------------------------------------
3465 // Get decompressed stream of an object by xref
3466 // Py_RETURN_NONE if not stream
3467 //------------------------------------------------------------------
3468 FITZEXCEPTION(xref_stream, !result)
3469 CLOSECHECK(xref_stream, """Get decompressed xref stream.""")
3470 PyObject *xref_stream(int xref)
3471 {
3472 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3473 PyObject *r = Py_None;
3474 pdf_obj *obj = NULL;
3475 fz_var(obj);
3476 fz_buffer *res = NULL;
3477 fz_var(res);
3478 fz_try(gctx) {
3479 ASSERT_PDF(pdf);
3480 int xreflen = pdf_xref_len(gctx, pdf);
3481 if (!INRANGE(xref, 1, xreflen-1) && xref != -1) {
3482 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
3483 }
3484 if (xref >= 0) {
3485 obj = pdf_new_indirect(gctx, pdf, xref, 0);
3486 } else {
3487 obj = pdf_trailer(gctx, pdf);
3488 }
3489 if (pdf_is_stream(gctx, obj))
3490 {
3491 res = pdf_load_stream_number(gctx, pdf, xref);
3492 r = JM_BinFromBuffer(gctx, res);
3493 }
3494 }
3495 fz_always(gctx) {
3496 fz_drop_buffer(gctx, res);
3497 if (xref >= 0) {
3498 pdf_drop_obj(gctx, obj);
3499 }
3500 }
3501 fz_catch(gctx)
3502 {
3503 Py_CLEAR(r);
3504 return NULL;
3505 }
3506 return r;
3507 }
3508
3509 //------------------------------------------------------------------
3510 // Update an Xref number with a new object given as a string
3511 //------------------------------------------------------------------
3512 FITZEXCEPTION(update_object, !result)
3513 CLOSECHECK(update_object, """Replace object definition source.""")
3514 PyObject *update_object(int xref, char *text, struct Page *page = NULL)
3515 {
3516 pdf_obj *new_obj;
3517 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3518 fz_try(gctx) {
3519 ASSERT_PDF(pdf);
3520 int xreflen = pdf_xref_len(gctx, pdf);
3521 if (!INRANGE(xref, 1, xreflen-1)) {
3522 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
3523 }
3524 ENSURE_OPERATION(gctx, pdf);
3525 // create new object with passed-in string
3526 new_obj = JM_pdf_obj_from_str(gctx, pdf, text);
3527 pdf_update_object(gctx, pdf, xref, new_obj);
3528 pdf_drop_obj(gctx, new_obj);
3529 if (page) {
3530 pdf_page *pdfpage = pdf_page_from_fz_page(gctx, (fz_page *) page);
3531 JM_refresh_links(gctx, pdfpage);
3532 }
3533 }
3534 fz_catch(gctx) {
3535 return NULL;
3536 }
3537
3538 Py_RETURN_NONE;
3539 }
3540
3541 //------------------------------------------------------------------
3542 // Update a stream identified by its xref
3543 //------------------------------------------------------------------
3544 FITZEXCEPTION(update_stream, !result)
3545 CLOSECHECK(update_stream, """Replace xref stream part.""")
3546 PyObject *update_stream(int xref=0, PyObject *stream=NULL, int new=1, int compress=1)
3547 {
3548 pdf_obj *obj = NULL;
3549 fz_var(obj);
3550 fz_buffer *res = NULL;
3551 fz_var(res);
3552 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3553 fz_try(gctx) {
3554 ASSERT_PDF(pdf);
3555 int xreflen = pdf_xref_len(gctx, pdf);
3556 if (!INRANGE(xref, 1, xreflen-1)) {
3557 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
3558 }
3559 ENSURE_OPERATION(gctx, pdf);
3560 // get the object
3561 obj = pdf_new_indirect(gctx, pdf, xref, 0);
3562 if (!pdf_is_dict(gctx, obj)) {
3563 RAISEPY(gctx, MSG_IS_NO_DICT, PyExc_ValueError);
3564 }
3565 res = JM_BufferFromBytes(gctx, stream);
3566 if (!res) {
3567 RAISEPY(gctx, MSG_BAD_BUFFER, PyExc_TypeError);
3568 }
3569 JM_update_stream(gctx, pdf, obj, res, compress);
3570 }
3571 fz_always(gctx) {
3572 fz_drop_buffer(gctx, res);
3573 pdf_drop_obj(gctx, obj);
3574 }
3575 fz_catch(gctx)
3576 return NULL;
3577
3578 Py_RETURN_NONE;
3579 }
3580
3581
3582 //------------------------------------------------------------------
3583 // create / refresh the page map
3584 //------------------------------------------------------------------
3585 FITZEXCEPTION(_make_page_map, !result)
3586 CLOSECHECK0(_make_page_map, """Make an array page number -> page object.""")
3587 PyObject *_make_page_map()
3588 {
3589 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3590 if (!pdf) Py_RETURN_NONE;
3591 fz_try(gctx) {
3592 pdf_drop_page_tree(gctx, pdf);
3593 pdf_load_page_tree(gctx, pdf);
3594 }
3595 fz_catch(gctx) {
3596 return NULL;
3597 }
3598 return Py_BuildValue("i", pdf->map_page_count);
3599 }
3600
3601
3602 //------------------------------------------------------------------
3603 // full (deep) copy of one page
3604 //------------------------------------------------------------------
3605 FITZEXCEPTION(fullcopy_page, !result)
3606 CLOSECHECK0(fullcopy_page, """Make a full page duplicate.""")
3607 %pythonappend fullcopy_page %{self._reset_page_refs()%}
3608 PyObject *fullcopy_page(int pno, int to = -1)
3609 {
3610 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3611 int page_count = pdf_count_pages(gctx, pdf);
3612 fz_buffer *res = NULL, *nres=NULL;
3613 fz_buffer *contents_buffer = NULL;
3614 fz_var(pdf);
3615 fz_var(res);
3616 fz_var(nres);
3617 fz_var(contents_buffer);
3618 fz_try(gctx) {
3619 ASSERT_PDF(pdf);
3620 if (!INRANGE(pno, 0, page_count - 1) ||
3621 !INRANGE(to, -1, page_count - 1)) {
3622 RAISEPY(gctx, MSG_BAD_PAGENO, PyExc_ValueError);
3623 }
3624
3625 pdf_obj *page1 = pdf_resolve_indirect(gctx,
3626 pdf_lookup_page_obj(gctx, pdf, pno));
3627
3628 pdf_obj *page2 = pdf_deep_copy_obj(gctx, page1);
3629 pdf_obj *old_annots = pdf_dict_get(gctx, page2, PDF_NAME(Annots));
3630
3631 // copy annotations, but remove Popup and IRT types
3632 if (old_annots) {
3633 int i, n = pdf_array_len(gctx, old_annots);
3634 pdf_obj *new_annots = pdf_new_array(gctx, pdf, n);
3635 for (i = 0; i < n; i++) {
3636 pdf_obj *o = pdf_array_get(gctx, old_annots, i);
3637 pdf_obj *subtype = pdf_dict_get(gctx, o, PDF_NAME(Subtype));
3638 if (pdf_name_eq(gctx, subtype, PDF_NAME(Popup))) continue;
3639 if (pdf_dict_gets(gctx, o, "IRT")) continue;
3640 pdf_obj *copy_o = pdf_deep_copy_obj(gctx,
3641 pdf_resolve_indirect(gctx, o));
3642 int xref = pdf_create_object(gctx, pdf);
3643 pdf_update_object(gctx, pdf, xref, copy_o);
3644 pdf_drop_obj(gctx, copy_o);
3645 copy_o = pdf_new_indirect(gctx, pdf, xref, 0);
3646 pdf_dict_del(gctx, copy_o, PDF_NAME(Popup));
3647 pdf_dict_del(gctx, copy_o, PDF_NAME(P));
3648 pdf_array_push_drop(gctx, new_annots, copy_o);
3649 }
3650 pdf_dict_put_drop(gctx, page2, PDF_NAME(Annots), new_annots);
3651 }
3652
3653 // copy the old contents stream(s)
3654 res = JM_read_contents(gctx, page1);
3655
3656 // create new /Contents object for page2
3657 if (res) {
3658 contents_buffer = fz_new_buffer_from_copied_data(gctx, " ", 1);
3659 pdf_obj *contents = pdf_add_stream(gctx, pdf, contents_buffer, NULL, 0);
3660 JM_update_stream(gctx, pdf, contents, res, 1);
3661 pdf_dict_put_drop(gctx, page2, PDF_NAME(Contents), contents);
3662 }
3663
3664 // now insert target page, making sure it is an indirect object
3665 int xref = pdf_create_object(gctx, pdf); // get new xref
3666 pdf_update_object(gctx, pdf, xref, page2); // store new page
3667 pdf_drop_obj(gctx, page2); // give up this object for now
3668
3669 page2 = pdf_new_indirect(gctx, pdf, xref, 0); // reread object
3670 pdf_insert_page(gctx, pdf, to, page2); // and store the page
3671 pdf_drop_obj(gctx, page2);
3672 }
3673 fz_always(gctx) {
3674 pdf_drop_page_tree(gctx, pdf);
3675 fz_drop_buffer(gctx, res);
3676 fz_drop_buffer(gctx, nres);
3677 fz_drop_buffer(gctx, contents_buffer);
3678 }
3679 fz_catch(gctx) {
3680 return NULL;
3681 }
3682 Py_RETURN_NONE;
3683 }
3684
3685
3686 //------------------------------------------------------------------
3687 // move or copy one page
3688 //------------------------------------------------------------------
3689 FITZEXCEPTION(_move_copy_page, !result)
3690 CLOSECHECK0(_move_copy_page, """Move or copy a PDF page reference.""")
3691 %pythonappend _move_copy_page %{self._reset_page_refs()%}
3692 PyObject *_move_copy_page(int pno, int nb, int before, int copy)
3693 {
3694 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3695 int i1, i2, pos, count, same = 0;
3696 pdf_obj *parent1 = NULL, *parent2 = NULL, *parent = NULL;
3697 pdf_obj *kids1, *kids2;
3698 fz_try(gctx) {
3699 ASSERT_PDF(pdf);
3700 // get the two page objects -----------------------------------
3701 // locate the /Kids arrays and indices in each
3702 pdf_obj *page1 = pdf_lookup_page_loc(gctx, pdf, pno, &parent1, &i1);
3703 kids1 = pdf_dict_get(gctx, parent1, PDF_NAME(Kids));
3704
3705 pdf_obj *page2 = pdf_lookup_page_loc(gctx, pdf, nb, &parent2, &i2);
3706 (void) page2;
3707 kids2 = pdf_dict_get(gctx, parent2, PDF_NAME(Kids));
3708
3709 if (before) // calc index of source page in target /Kids
3710 pos = i2;
3711 else
3712 pos = i2 + 1;
3713
3714 // same /Kids array? ------------------------------------------
3715 same = pdf_objcmp(gctx, kids1, kids2);
3716
3717 // put source page in target /Kids array ----------------------
3718 if (!copy && same != 0) // update parent in page object
3719 {
3720 pdf_dict_put(gctx, page1, PDF_NAME(Parent), parent2);
3721 }
3722 pdf_array_insert(gctx, kids2, page1, pos);
3723
3724 if (same != 0) // different /Kids arrays ----------------------
3725 {
3726 parent = parent2;
3727 while (parent) // increase /Count objects in parents
3728 {
3729 count = pdf_dict_get_int(gctx, parent, PDF_NAME(Count));
3730 pdf_dict_put_int(gctx, parent, PDF_NAME(Count), count + 1);
3731 parent = pdf_dict_get(gctx, parent, PDF_NAME(Parent));
3732 }
3733 if (!copy) // delete original item
3734 {
3735 pdf_array_delete(gctx, kids1, i1);
3736 parent = parent1;
3737 while (parent) // decrease /Count objects in parents
3738 {
3739 count = pdf_dict_get_int(gctx, parent, PDF_NAME(Count));
3740 pdf_dict_put_int(gctx, parent, PDF_NAME(Count), count - 1);
3741 parent = pdf_dict_get(gctx, parent, PDF_NAME(Parent));
3742 }
3743 }
3744 }
3745 else { // same /Kids array
3746 if (copy) { // source page is copied
3747 parent = parent2;
3748 while (parent) // increase /Count object in parents
3749 {
3750 count = pdf_dict_get_int(gctx, parent, PDF_NAME(Count));
3751 pdf_dict_put_int(gctx, parent, PDF_NAME(Count), count + 1);
3752 parent = pdf_dict_get(gctx, parent, PDF_NAME(Parent));
3753 }
3754 } else {
3755 if (i1 < pos)
3756 pdf_array_delete(gctx, kids1, i1);
3757 else
3758 pdf_array_delete(gctx, kids1, i1 + 1);
3759 }
3760 }
3761 if (pdf->rev_page_map) { // page map no longer valid: drop it
3762 pdf_drop_page_tree(gctx, pdf);
3763 }
3764 }
3765 fz_catch(gctx) {
3766 return NULL;
3767 }
3768 Py_RETURN_NONE;
3769 }
3770
3771 FITZEXCEPTION(_remove_toc_item, !result)
3772 PyObject *_remove_toc_item(int xref)
3773 {
3774 // "remove" bookmark by letting it point to nowhere
3775 pdf_obj *item = NULL, *color;
3776 int i;
3777 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3778 fz_try(gctx) {
3779 item = pdf_new_indirect(gctx, pdf, xref, 0);
3780 pdf_dict_del(gctx, item, PDF_NAME(Dest));
3781 pdf_dict_del(gctx, item, PDF_NAME(A));
3782 color = pdf_new_array(gctx, pdf, 3);
3783 for (i=0; i < 3; i++) {
3784 pdf_array_push_real(gctx, color, 0.8);
3785 }
3786 pdf_dict_put_drop(gctx, item, PDF_NAME(C), color);
3787 }
3788 fz_always(gctx) {
3789 pdf_drop_obj(gctx, item);
3790 }
3791 fz_catch(gctx){
3792 return NULL;
3793 }
3794 Py_RETURN_NONE;
3795 }
3796
3797 FITZEXCEPTION(_update_toc_item, !result)
3798 PyObject *_update_toc_item(int xref, char *action=NULL, char *title=NULL, int flags=0, PyObject *collapse=NULL, PyObject *color=NULL)
3799 {
3800 // "update" bookmark by letting it point to nowhere
3801 pdf_obj *item = NULL;
3802 pdf_obj *obj = NULL;
3803 Py_ssize_t i;
3804 double f;
3805 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3806 fz_try(gctx) {
3807 item = pdf_new_indirect(gctx, pdf, xref, 0);
3808 if (title) {
3809 pdf_dict_put_text_string(gctx, item, PDF_NAME(Title), title);
3810 }
3811 if (action) {
3812 pdf_dict_del(gctx, item, PDF_NAME(Dest));
3813 obj = JM_pdf_obj_from_str(gctx, pdf, action);
3814 pdf_dict_put_drop(gctx, item, PDF_NAME(A), obj);
3815 }
3816 pdf_dict_put_int(gctx, item, PDF_NAME(F), flags);
3817 if (EXISTS(color)) {
3818 pdf_obj *c = pdf_new_array(gctx, pdf, 3);
3819 for (i = 0; i < 3; i++) {
3820 JM_FLOAT_ITEM(color, i, &f);
3821 pdf_array_push_real(gctx, c, f);
3822 }
3823 pdf_dict_put_drop(gctx, item, PDF_NAME(C), c);
3824 } else if (color != Py_None) {
3825 pdf_dict_del(gctx, item, PDF_NAME(C));
3826 }
3827 if (collapse != Py_None) {
3828 if (pdf_dict_get(gctx, item, PDF_NAME(Count))) {
3829 i = pdf_dict_get_int(gctx, item, PDF_NAME(Count));
3830 if ((i < 0 && collapse == Py_False) || (i > 0 && collapse == Py_True)) {
3831 i = i * (-1);
3832 pdf_dict_put_int(gctx, item, PDF_NAME(Count), i);
3833 }
3834 }
3835 }
3836 }
3837 fz_always(gctx) {
3838 pdf_drop_obj(gctx, item);
3839 }
3840 fz_catch(gctx){
3841 return NULL;
3842 }
3843 Py_RETURN_NONE;
3844 }
3845
3846 //------------------------------------------------------------------
3847 // PDF page label getting / setting
3848 //------------------------------------------------------------------
3849 FITZEXCEPTION(_get_page_labels, !result)
3850 PyObject *
3851 _get_page_labels()
3852 {
3853 pdf_obj *obj, *nums, *kids;
3854 PyObject *rc = NULL;
3855 int i, n;
3856 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3857
3858 pdf_obj *pagelabels = NULL;
3859 fz_var(pagelabels);
3860 fz_try(gctx) {
3861 ASSERT_PDF(pdf);
3862 rc = PyList_New(0);
3863 pagelabels = pdf_new_name(gctx, "PageLabels");
3864 obj = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
3865 PDF_NAME(Root), pagelabels, NULL);
3866 if (!obj) {
3867 goto finished;
3868 }
3869 // simple case: direct /Nums object
3870 nums = pdf_resolve_indirect(gctx,
3871 pdf_dict_get(gctx, obj, PDF_NAME(Nums)));
3872 if (nums) {
3873 JM_get_page_labels(gctx, rc, nums);
3874 goto finished;
3875 }
3876 // case: /Kids/Nums
3877 nums = pdf_resolve_indirect(gctx,
3878 pdf_dict_getl(gctx, obj, PDF_NAME(Kids), PDF_NAME(Nums), NULL)
3879 );
3880 if (nums) {
3881 JM_get_page_labels(gctx, rc, nums);
3882 goto finished;
3883 }
3884 // case: /Kids is an array of multiple /Nums
3885 kids = pdf_resolve_indirect(gctx,
3886 pdf_dict_get(gctx, obj, PDF_NAME(Kids)));
3887 if (!kids || !pdf_is_array(gctx, kids)) {
3888 goto finished;
3889 }
3890
3891 n = pdf_array_len(gctx, kids);
3892 for (i = 0; i < n; i++) {
3893 nums = pdf_resolve_indirect(gctx,
3894 pdf_dict_get(gctx,
3895 pdf_array_get(gctx, kids, i),
3896 PDF_NAME(Nums)));
3897 JM_get_page_labels(gctx, rc, nums);
3898 }
3899 finished:;
3900 }
3901 fz_always(gctx) {
3902 PyErr_Clear();
3903 pdf_drop_obj(gctx, pagelabels);
3904 }
3905 fz_catch(gctx){
3906 Py_CLEAR(rc);
3907 return NULL;
3908 }
3909 return rc;
3910 }
3911
3912
3913 FITZEXCEPTION(_set_page_labels, !result)
3914 %pythonappend _set_page_labels %{
3915 xref = self.pdf_catalog()
3916 text = self.xref_object(xref, compressed=True)
3917 text = text.replace("/Nums[]", "/Nums[%s]" % labels)
3918 self.update_object(xref, text)%}
3919 PyObject *
3920 _set_page_labels(char *labels)
3921 {
3922 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self);
3923 pdf_obj *pagelabels = NULL;
3924 fz_var(pagelabels);
3925 fz_try(gctx) {
3926 ASSERT_PDF(pdf);
3927 pagelabels = pdf_new_name(gctx, "PageLabels");
3928 pdf_obj *root = pdf_dict_get(gctx, pdf_trailer(gctx, pdf), PDF_NAME(Root));
3929 pdf_dict_del(gctx, root, pagelabels);
3930 pdf_dict_putl_drop(gctx, root, pdf_new_array(gctx, pdf, 0), pagelabels, PDF_NAME(Nums), NULL);
3931 }
3932 fz_always(gctx) {
3933 PyErr_Clear();
3934 pdf_drop_obj(gctx, pagelabels);
3935 }
3936 fz_catch(gctx){
3937 return NULL;
3938 }
3939 Py_RETURN_NONE;
3940 }
3941
3942
3943 //------------------------------------------------------------------
3944 // PDF Optional Content functions
3945 //------------------------------------------------------------------
3946 FITZEXCEPTION(get_layers, !result)
3947 CLOSECHECK0(get_layers, """Show optional OC layers.""")
3948 PyObject *
3949 get_layers()
3950 {
3951 PyObject *rc = NULL;
3952 pdf_layer_config info = {NULL, NULL};
3953 fz_try(gctx) {
3954 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) self);
3955 ASSERT_PDF(pdf);
3956 int i, n = pdf_count_layer_configs(gctx, pdf);
3957 if (n == 1) {
3958 pdf_obj *obj = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
3959 PDF_NAME(Root), PDF_NAME(OCProperties), PDF_NAME(Configs), NULL);
3960 if (!pdf_is_array(gctx, obj)) n = 0;
3961 }
3962 rc = PyTuple_New(n);
3963 for (i = 0; i < n; i++) {
3964 pdf_layer_config_info(gctx, pdf, i, &info);
3965 PyObject *item = Py_BuildValue("{s:i,s:s,s:s}",
3966 "number", i, "name", info.name, "creator", info.creator);
3967 PyTuple_SET_ITEM(rc, i, item);
3968 info.name = NULL;
3969 info.creator = NULL;
3970 }
3971 }
3972 fz_catch(gctx) {
3973 Py_CLEAR(rc);
3974 return NULL;
3975 }
3976 return rc;
3977 }
3978
3979
3980 FITZEXCEPTION(switch_layer, !result)
3981 CLOSECHECK0(switch_layer, """Activate an OC layer.""")
3982 PyObject *
3983 switch_layer(int config, int as_default=0)
3984 {
3985 fz_try(gctx) {
3986 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) self);
3987 ASSERT_PDF(pdf);
3988 pdf_obj *cfgs = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
3989 PDF_NAME(Root), PDF_NAME(OCProperties), PDF_NAME(Configs), NULL);
3990 if (!pdf_is_array(gctx, cfgs) || !pdf_array_len(gctx, cfgs)) {
3991 if (config < 1) goto finished;
3992 RAISEPY(gctx, MSG_BAD_OC_LAYER, PyExc_ValueError);
3993 }
3994 if (config < 0) goto finished;
3995 pdf_select_layer_config(gctx, pdf, config);
3996 if (as_default) {
3997 pdf_set_layer_config_as_default(gctx, pdf);
3998 pdf_read_ocg(gctx, pdf);
3999 }
4000 finished:;
4001 }
4002 fz_catch(gctx) {
4003 return NULL;
4004 }
4005 Py_RETURN_NONE;
4006 }
4007
4008
4009 FITZEXCEPTION(get_layer, !result)
4010 CLOSECHECK0(get_layer, """Content of ON, OFF, RBGroups of an OC layer.""")
4011 PyObject *
4012 get_layer(int config=-1)
4013 {
4014 PyObject *rc;
4015 pdf_obj *obj = NULL;
4016 fz_try(gctx) {
4017 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) self);
4018 ASSERT_PDF(pdf);
4019 pdf_obj *ocp = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
4020 PDF_NAME(Root), PDF_NAME(OCProperties), NULL);
4021 if (!ocp) {
4022 rc = Py_BuildValue("s", NULL);
4023 goto finished;
4024 }
4025 if (config == -1) {
4026 obj = pdf_dict_get(gctx, ocp, PDF_NAME(D));
4027 } else {
4028 obj = pdf_array_get(gctx, pdf_dict_get(gctx, ocp, PDF_NAME(Configs)), config);
4029 }
4030 if (!obj) {
4031 RAISEPY(gctx, MSG_BAD_OC_CONFIG, PyExc_ValueError);
4032 }
4033 rc = JM_get_ocg_arrays(gctx, obj);
4034 finished:;
4035 }
4036 fz_catch(gctx) {
4037 Py_CLEAR(rc);
4038 PyErr_Clear();
4039 return NULL;
4040 }
4041 return rc;
4042 }
4043
4044
4045 FITZEXCEPTION(set_layer, !result)
4046 %pythonprepend set_layer
4047 %{"""Set the PDF keys /ON, /OFF, /RBGroups of an OC layer."""
4048 if self.is_closed:
4049 raise ValueError("document closed")
4050 ocgs = set(self.get_ocgs().keys())
4051 if ocgs == set():
4052 raise ValueError("document has no optional content")
4053
4054 if on:
4055 if type(on) not in (list, tuple):
4056 raise ValueError("bad type: 'on'")
4057 s = set(on).difference(ocgs)
4058 if s != set():
4059 raise ValueError("bad OCGs in 'on': %s" % s)
4060
4061 if off:
4062 if type(off) not in (list, tuple):
4063 raise ValueError("bad type: 'off'")
4064 s = set(off).difference(ocgs)
4065 if s != set():
4066 raise ValueError("bad OCGs in 'off': %s" % s)
4067
4068 if locked:
4069 if type(locked) not in (list, tuple):
4070 raise ValueError("bad type: 'locked'")
4071 s = set(locked).difference(ocgs)
4072 if s != set():
4073 raise ValueError("bad OCGs in 'locked': %s" % s)
4074
4075 if rbgroups:
4076 if type(rbgroups) not in (list, tuple):
4077 raise ValueError("bad type: 'rbgroups'")
4078 for x in rbgroups:
4079 if not type(x) in (list, tuple):
4080 raise ValueError("bad RBGroup '%s'" % x)
4081 s = set(x).difference(ocgs)
4082 if s != set():
4083 raise ValueError("bad OCGs in RBGroup: %s" % s)
4084
4085 if basestate:
4086 basestate = str(basestate).upper()
4087 if basestate == "UNCHANGED":
4088 basestate = "Unchanged"
4089 if basestate not in ("ON", "OFF", "Unchanged"):
4090 raise ValueError("bad 'basestate'")
4091 %}
4092 PyObject *
4093 set_layer(int config, const char *basestate=NULL, PyObject *on=NULL,
4094 PyObject *off=NULL, PyObject *rbgroups=NULL, PyObject *locked=NULL)
4095 {
4096 pdf_obj *obj = NULL;
4097 fz_try(gctx) {
4098 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) self);
4099 ASSERT_PDF(pdf);
4100 pdf_obj *ocp = pdf_dict_getl(gctx, pdf_trailer(gctx, pdf),
4101 PDF_NAME(Root), PDF_NAME(OCProperties), NULL);
4102 if (!ocp) {
4103 goto finished;
4104 }
4105 if (config == -1) {
4106 obj = pdf_dict_get(gctx, ocp, PDF_NAME(D));
4107 } else {
4108 obj = pdf_array_get(gctx, pdf_dict_get(gctx, ocp, PDF_NAME(Configs)), config);
4109 }
4110 if (!obj) {
4111 RAISEPY(gctx, MSG_BAD_OC_CONFIG, PyExc_ValueError);
4112 }
4113 JM_set_ocg_arrays(gctx, obj, basestate, on, off, rbgroups, locked);
4114 pdf_read_ocg(gctx, pdf);
4115 finished:;
4116 }
4117 fz_catch(gctx) {
4118 return NULL;
4119 }
4120 Py_RETURN_NONE;
4121 }
4122
4123
4124 FITZEXCEPTION(add_layer, !result)
4125 CLOSECHECK0(add_layer, """Add a new OC layer.""")
4126 PyObject *add_layer(char *name, char *creator=NULL, PyObject *on=NULL)
4127 {
4128 fz_try(gctx) {
4129 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) self);
4130 ASSERT_PDF(pdf);
4131 JM_add_layer_config(gctx, pdf, name, creator, on);
4132 pdf_read_ocg(gctx, pdf);
4133 }
4134 fz_catch(gctx) {
4135 return NULL;
4136 }
4137 Py_RETURN_NONE;
4138 }
4139
4140
4141 FITZEXCEPTION(layer_ui_configs, !result)
4142 CLOSECHECK0(layer_ui_configs, """Show OC visibility status modifiable by user.""")
4143 PyObject *layer_ui_configs()
4144 {
4145 typedef struct
4146 {
4147 const char *text;
4148 int depth;
4149 pdf_layer_config_ui_type type;
4150 int selected;
4151 int locked;
4152 } pdf_layer_config_ui;
4153 PyObject *rc = NULL;
4154
4155 fz_try(gctx) {
4156 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) self);
4157 ASSERT_PDF(pdf);
4158 pdf_layer_config_ui info;
4159 int i, n = pdf_count_layer_config_ui(gctx, pdf);
4160 rc = PyTuple_New(n);
4161 char *type = NULL;
4162 for (i = 0; i < n; i++) {
4163 pdf_layer_config_ui_info(gctx, pdf, i, (void *) &info);
4164 switch (info.type)
4165 {
4166 case (1): type = "checkbox"; break;
4167 case (2): type = "radiobox"; break;
4168 default: type = "label"; break;
4169 }
4170 PyObject *item = Py_BuildValue("{s:i,s:N,s:i,s:s,s:N,s:N}",
4171 "number", i,
4172 "text", JM_UnicodeFromStr(info.text),
4173 "depth", info.depth,
4174 "type", type,
4175 "on", JM_BOOL(info.selected),
4176 "locked", JM_BOOL(info.locked));
4177 PyTuple_SET_ITEM(rc, i, item);
4178 }
4179 }
4180 fz_catch(gctx) {
4181 Py_CLEAR(rc);
4182 return NULL;
4183 }
4184 return rc;
4185 }
4186
4187
4188 FITZEXCEPTION(set_layer_ui_config, !result)
4189 CLOSECHECK0(set_layer_ui_config, )
4190 %pythonprepend set_layer_ui_config %{
4191 """Set / unset OC intent configuration."""
4192 # The user might have given the name instead of sequence number,
4193 # so select by that name and continue with corresp. number
4194 if isinstance(number, str):
4195 select = [ui["number"] for ui in self.layer_ui_configs() if ui["text"] == number]
4196 if select == []:
4197 raise ValueError(f"bad OCG '{number}'.")
4198 number = select[0] # this is the number for the name
4199 %}
4200 PyObject *set_layer_ui_config(int number, int action=0)
4201 {
4202 fz_try(gctx) {
4203 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) self);
4204 ASSERT_PDF(pdf);
4205 switch (action)
4206 {
4207 case (1):
4208 pdf_toggle_layer_config_ui(gctx, pdf, number);
4209 break;
4210 case (2):
4211 pdf_deselect_layer_config_ui(gctx, pdf, number);
4212 break;
4213 default:
4214 pdf_select_layer_config_ui(gctx, pdf, number);
4215 break;
4216 }
4217 }
4218 fz_catch(gctx) {
4219 return NULL;
4220 }
4221 Py_RETURN_NONE;
4222 }
4223
4224
4225 FITZEXCEPTION(get_ocgs, !result)
4226 CLOSECHECK0(get_ocgs, """Show existing optional content groups.""")
4227 PyObject *
4228 get_ocgs()
4229 {
4230 PyObject *rc = NULL;
4231 pdf_obj *ci = pdf_new_name(gctx, "CreatorInfo");
4232 fz_try(gctx) {
4233 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) self);
4234 ASSERT_PDF(pdf);
4235 pdf_obj *ocgs = pdf_dict_getl(gctx,
4236 pdf_dict_get(gctx,
4237 pdf_trailer(gctx, pdf), PDF_NAME(Root)),
4238 PDF_NAME(OCProperties), PDF_NAME(OCGs), NULL);
4239 rc = PyDict_New();
4240 if (!pdf_is_array(gctx, ocgs)) goto fertig;
4241 int i, n = pdf_array_len(gctx, ocgs);
4242 for (i = 0; i < n; i++) {
4243 pdf_obj *ocg = pdf_array_get(gctx, ocgs, i);
4244 int xref = pdf_to_num(gctx, ocg);
4245 const char *name = pdf_to_text_string(gctx, pdf_dict_get(gctx, ocg, PDF_NAME(Name)));
4246 pdf_obj *obj = pdf_dict_getl(gctx, ocg, PDF_NAME(Usage), ci, PDF_NAME(Subtype), NULL);
4247 const char *usage = NULL;
4248 if (obj) usage = pdf_to_name(gctx, obj);
4249 PyObject *intents = PyList_New(0);
4250 pdf_obj *intent = pdf_dict_get(gctx, ocg, PDF_NAME(Intent));
4251 if (intent) {
4252 if (pdf_is_name(gctx, intent)) {
4253 LIST_APPEND_DROP(intents, Py_BuildValue("s", pdf_to_name(gctx, intent)));
4254 } else if (pdf_is_array(gctx, intent)) {
4255 int j, m = pdf_array_len(gctx, intent);
4256 for (j = 0; j < m; j++) {
4257 pdf_obj *o = pdf_array_get(gctx, intent, j);
4258 if (pdf_is_name(gctx, o))
4259 LIST_APPEND_DROP(intents, Py_BuildValue("s", pdf_to_name(gctx, o)));
4260 }
4261 }
4262 }
4263 int hidden = pdf_is_ocg_hidden(gctx, pdf, NULL, usage, ocg);
4264 PyObject *item = Py_BuildValue("{s:s,s:O,s:O,s:s}",
4265 "name", name,
4266 "intent", intents,
4267 "on", JM_BOOL(!hidden),
4268 "usage", usage);
4269 Py_DECREF(intents);
4270 PyObject *temp = Py_BuildValue("i", xref);
4271 DICT_SETITEM_DROP(rc, temp, item);
4272 Py_DECREF(temp);
4273 }
4274 fertig:;
4275 }
4276 fz_always(gctx) {
4277 pdf_drop_obj(gctx, ci);
4278 }
4279 fz_catch(gctx) {
4280 Py_CLEAR(rc);
4281 return NULL;
4282 }
4283 return rc;
4284 }
4285
4286
4287 FITZEXCEPTION(add_ocg, !result)
4288 CLOSECHECK0(add_ocg, """Add new optional content group.""")
4289 PyObject *
4290 add_ocg(char *name, int config=-1, int on=1, PyObject *intent=NULL, const char *usage=NULL)
4291 {
4292 int xref = 0;
4293 pdf_obj *obj = NULL, *cfg = NULL;
4294 pdf_obj *indocg = NULL;
4295 pdf_obj *ocg = NULL;
4296 pdf_obj *ci_name = NULL;
4297 fz_var(indocg);
4298 fz_var(ocg);
4299 fz_var(ci_name);
4300 fz_try(gctx) {
4301 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) self);
4302 ASSERT_PDF(pdf);
4303
4304 // ------------------------------
4305 // make the OCG
4306 // ------------------------------
4307 ocg = pdf_add_new_dict(gctx, pdf, 3);
4308 pdf_dict_put(gctx, ocg, PDF_NAME(Type), PDF_NAME(OCG));
4309 pdf_dict_put_text_string(gctx, ocg, PDF_NAME(Name), name);
4310 pdf_obj *intents = pdf_dict_put_array(gctx, ocg, PDF_NAME(Intent), 2);
4311 if (!EXISTS(intent)) {
4312 pdf_array_push(gctx, intents, PDF_NAME(View));
4313 } else if (!PyUnicode_Check(intent)) {
4314 int i, n = PySequence_Size(intent);
4315 for (i = 0; i < n; i++) {
4316 PyObject *item = PySequence_ITEM(intent, i);
4317 char *c = JM_StrAsChar(item);
4318 if (c) {
4319 pdf_array_push_drop(gctx, intents, pdf_new_name(gctx, c));
4320 }
4321 Py_DECREF(item);
4322 }
4323 } else {
4324 char *c = JM_StrAsChar(intent);
4325 if (c) {
4326 pdf_array_push_drop(gctx, intents, pdf_new_name(gctx, c));
4327 }
4328 }
4329 pdf_obj *use_for = pdf_dict_put_dict(gctx, ocg, PDF_NAME(Usage), 3);
4330 ci_name = pdf_new_name(gctx, "CreatorInfo");
4331 pdf_obj *cre_info = pdf_dict_put_dict(gctx, use_for, ci_name, 2);
4332 pdf_dict_put_text_string(gctx, cre_info, PDF_NAME(Creator), "PyMuPDF");
4333 if (usage) {
4334 pdf_dict_put_name(gctx, cre_info, PDF_NAME(Subtype), usage);
4335 } else {
4336 pdf_dict_put_name(gctx, cre_info, PDF_NAME(Subtype), "Artwork");
4337 }
4338 indocg = pdf_add_object(gctx, pdf, ocg);
4339
4340 // ------------------------------
4341 // Insert OCG in the right config
4342 // ------------------------------
4343 pdf_obj *ocp = JM_ensure_ocproperties(gctx, pdf);
4344 obj = pdf_dict_get(gctx, ocp, PDF_NAME(OCGs));
4345 pdf_array_push(gctx, obj, indocg);
4346
4347 if (config > -1) {
4348 obj = pdf_dict_get(gctx, ocp, PDF_NAME(Configs));
4349 if (!pdf_is_array(gctx, obj)) {
4350 RAISEPY(gctx, MSG_BAD_OC_CONFIG, PyExc_ValueError);
4351 }
4352 cfg = pdf_array_get(gctx, obj, config);
4353 if (!cfg) {
4354 RAISEPY(gctx, MSG_BAD_OC_CONFIG, PyExc_ValueError);
4355 }
4356 } else {
4357 cfg = pdf_dict_get(gctx, ocp, PDF_NAME(D));
4358 }
4359
4360 obj = pdf_dict_get(gctx, cfg, PDF_NAME(Order));
4361 if (!obj) {
4362 obj = pdf_dict_put_array(gctx, cfg, PDF_NAME(Order), 1);
4363 }
4364 pdf_array_push(gctx, obj, indocg);
4365 if (on) {
4366 obj = pdf_dict_get(gctx, cfg, PDF_NAME(ON));
4367 if (!obj) {
4368 obj = pdf_dict_put_array(gctx, cfg, PDF_NAME(ON), 1);
4369 }
4370 } else {
4371 obj = pdf_dict_get(gctx, cfg, PDF_NAME(OFF));
4372 if (!obj) {
4373 obj = pdf_dict_put_array(gctx, cfg, PDF_NAME(OFF), 1);
4374 }
4375 }
4376 pdf_array_push(gctx, obj, indocg);
4377
4378 // let MuPDF take note: re-read OCProperties
4379 pdf_read_ocg(gctx, pdf);
4380
4381 xref = pdf_to_num(gctx, indocg);
4382 }
4383 fz_always(gctx) {
4384 pdf_drop_obj(gctx, indocg);
4385 pdf_drop_obj(gctx, ocg);
4386 pdf_drop_obj(gctx, ci_name);
4387 }
4388 fz_catch(gctx) {
4389 return NULL;
4390 }
4391 return Py_BuildValue("i", xref);
4392 }
4393
4394 struct Annot;
4395
4396 void internal_keep_annot(struct Annot* annot)
4397 {
4398 pdf_keep_annot(gctx, (pdf_annot*) annot);
4399 }
4400
4401 //------------------------------------------------------------------
4402 // Initialize document: set outline and metadata properties
4403 //------------------------------------------------------------------
4404 %pythoncode %{
4405 def init_doc(self):
4406 if self.is_encrypted:
4407 raise ValueError("cannot initialize - document still encrypted")
4408 self._outline = self._loadOutline()
4409 if self._outline:
4410 self._outline.thisown = True
4411 self.metadata = dict([(k,self._getMetadata(v)) for k,v in {'format':'format', 'title':'info:Title', 'author':'info:Author','subject':'info:Subject', 'keywords':'info:Keywords','creator':'info:Creator', 'producer':'info:Producer', 'creationDate':'info:CreationDate', 'modDate':'info:ModDate', 'trapped':'info:Trapped'}.items()])
4412 self.metadata['encryption'] = None if self._getMetadata('encryption')=='None' else self._getMetadata('encryption')
4413
4414 outline = property(lambda self: self._outline)
4415
4416
4417 def get_page_fonts(self, pno: int, full: bool =False) -> list:
4418 """Retrieve a list of fonts used on a page.
4419 """
4420 if self.is_closed or self.is_encrypted:
4421 raise ValueError("document closed or encrypted")
4422 if not self.is_pdf:
4423 return ()
4424 if type(pno) is not int:
4425 try:
4426 pno = pno.number
4427 except:
4428 raise ValueError("need a Page or page number")
4429 val = self._getPageInfo(pno, 1)
4430 if full is False:
4431 return [v[:-1] for v in val]
4432 return val
4433
4434
4435 def get_page_images(self, pno: int, full: bool =False) -> list:
4436 """Retrieve a list of images used on a page.
4437 """
4438 if self.is_closed or self.is_encrypted:
4439 raise ValueError("document closed or encrypted")
4440 if not self.is_pdf:
4441 return ()
4442 if type(pno) is not int:
4443 try:
4444 pno = pno.number
4445 except:
4446 raise ValueError("need a Page or page number")
4447 val = self._getPageInfo(pno, 2)
4448 if full is False:
4449 return [v[:-1] for v in val]
4450 return val
4451
4452
4453 def get_page_xobjects(self, pno: int) -> list:
4454 """Retrieve a list of XObjects used on a page.
4455 """
4456 if self.is_closed or self.is_encrypted:
4457 raise ValueError("document closed or encrypted")
4458 if not self.is_pdf:
4459 return ()
4460 if type(pno) is not int:
4461 try:
4462 pno = pno.number
4463 except:
4464 raise ValueError("need a Page or page number")
4465 val = self._getPageInfo(pno, 3)
4466 rc = [(v[0], v[1], v[2], Rect(v[3])) for v in val]
4467 return rc
4468
4469
4470 def xref_is_image(self, xref):
4471 """Check if xref is an image object."""
4472 if self.is_closed or self.is_encrypted:
4473 raise ValueError("document closed or encrypted")
4474 if self.xref_get_key(xref, "Subtype")[1] == "/Image":
4475 return True
4476 return False
4477
4478 def xref_is_font(self, xref):
4479 """Check if xref is a font object."""
4480 if self.is_closed or self.is_encrypted:
4481 raise ValueError("document closed or encrypted")
4482 if self.xref_get_key(xref, "Type")[1] == "/Font":
4483 return True
4484 return False
4485
4486 def xref_is_xobject(self, xref):
4487 """Check if xref is a form xobject."""
4488 if self.is_closed or self.is_encrypted:
4489 raise ValueError("document closed or encrypted")
4490 if self.xref_get_key(xref, "Subtype")[1] == "/Form":
4491 return True
4492 return False
4493
4494 def copy_page(self, pno: int, to: int =-1):
4495 """Copy a page within a PDF document.
4496
4497 This will only create another reference of the same page object.
4498 Args:
4499 pno: source page number
4500 to: put before this page, '-1' means after last page.
4501 """
4502 if self.is_closed:
4503 raise ValueError("document closed")
4504
4505 page_count = len(self)
4506 if (
4507 pno not in range(page_count) or
4508 to not in range(-1, page_count)
4509 ):
4510 raise ValueError("bad page number(s)")
4511 before = 1
4512 copy = 1
4513 if to == -1:
4514 to = page_count - 1
4515 before = 0
4516
4517 return self._move_copy_page(pno, to, before, copy)
4518
4519 def move_page(self, pno: int, to: int =-1):
4520 """Move a page within a PDF document.
4521
4522 Args:
4523 pno: source page number.
4524 to: put before this page, '-1' means after last page.
4525 """
4526 if self.is_closed:
4527 raise ValueError("document closed")
4528
4529 page_count = len(self)
4530 if (
4531 pno not in range(page_count) or
4532 to not in range(-1, page_count)
4533 ):
4534 raise ValueError("bad page number(s)")
4535 before = 1
4536 copy = 0
4537 if to == -1:
4538 to = page_count - 1
4539 before = 0
4540
4541 return self._move_copy_page(pno, to, before, copy)
4542
4543 def delete_page(self, pno: int =-1):
4544 """ Delete one page from a PDF.
4545 """
4546 if not self.is_pdf:
4547 raise ValueError("is no PDF")
4548 if self.is_closed:
4549 raise ValueError("document closed")
4550
4551 page_count = self.page_count
4552 while pno < 0:
4553 pno += page_count
4554
4555 if pno >= page_count:
4556 raise ValueError("bad page number(s)")
4557
4558 # remove TOC bookmarks pointing to deleted page
4559 toc = self.get_toc()
4560 ol_xrefs = self.get_outline_xrefs()
4561 for i, item in enumerate(toc):
4562 if item[2] == pno + 1:
4563 self._remove_toc_item(ol_xrefs[i])
4564
4565 self._remove_links_to(frozenset((pno,)))
4566 self._delete_page(pno)
4567 self._reset_page_refs()
4568
4569
4570 def delete_pages(self, *args, **kw):
4571 """Delete pages from a PDF.
4572
4573 Args:
4574 Either keywords 'from_page'/'to_page', or two integers to
4575 specify the first/last page to delete.
4576 Or a list/tuple/range object, which can contain arbitrary
4577 page numbers.
4578 """
4579 if not self.is_pdf:
4580 raise ValueError("is no PDF")
4581 if self.is_closed:
4582 raise ValueError("document closed")
4583
4584 page_count = self.page_count # page count of document
4585 f = t = -1
4586 if kw: # check if keywords were used
4587 if args: # then no positional args are allowed
4588 raise ValueError("cannot mix keyword and positional argument")
4589 f = kw.get("from_page", -1) # first page to delete
4590 t = kw.get("to_page", -1) # last page to delete
4591 while f < 0:
4592 f += page_count
4593 while t < 0:
4594 t += page_count
4595 if not f <= t < page_count:
4596 raise ValueError("bad page number(s)")
4597 numbers = tuple(range(f, t + 1))
4598 else:
4599 if len(args) > 2 or args == []:
4600 raise ValueError("need 1 or 2 positional arguments")
4601 if len(args) == 2:
4602 f, t = args
4603 if not (type(f) is int and type(t) is int):
4604 raise ValueError("both arguments must be int")
4605 if f > t:
4606 f, t = t, f
4607 if not f <= t < page_count:
4608 raise ValueError("bad page number(s)")
4609 numbers = tuple(range(f, t + 1))
4610 else:
4611 r = args[0]
4612 if type(r) not in (int, range, list, tuple):
4613 raise ValueError("need int or sequence if one argument")
4614 numbers = tuple(r)
4615
4616 numbers = list(map(int, set(numbers))) # ensure unique integers
4617 if numbers == []:
4618 print("nothing to delete")
4619 return
4620 numbers.sort()
4621 if numbers[0] < 0 or numbers[-1] >= page_count:
4622 raise ValueError("bad page number(s)")
4623 frozen_numbers = frozenset(numbers)
4624 toc = self.get_toc()
4625 for i, xref in enumerate(self.get_outline_xrefs()):
4626 if toc[i][2] - 1 in frozen_numbers:
4627 self._remove_toc_item(xref) # remove target in PDF object
4628
4629 self._remove_links_to(frozen_numbers)
4630
4631 for i in reversed(numbers): # delete pages, last to first
4632 self._delete_page(i)
4633
4634 self._reset_page_refs()
4635
4636
4637 def saveIncr(self):
4638 """ Save PDF incrementally"""
4639 return self.save(self.name, incremental=True, encryption=PDF_ENCRYPT_KEEP)
4640
4641
4642 def ez_save(self, filename, garbage=3, clean=False,
4643 deflate=True, deflate_images=True, deflate_fonts=True,
4644 incremental=False, ascii=False, expand=False, linear=False,
4645 pretty=False, encryption=1, permissions=4095,
4646 owner_pw=None, user_pw=None, no_new_id=True):
4647 """ Save PDF using some different defaults"""
4648 return self.save(filename, garbage=garbage,
4649 clean=clean,
4650 deflate=deflate,
4651 deflate_images=deflate_images,
4652 deflate_fonts=deflate_fonts,
4653 incremental=incremental,
4654 ascii=ascii,
4655 expand=expand,
4656 linear=linear,
4657 pretty=pretty,
4658 encryption=encryption,
4659 permissions=permissions,
4660 owner_pw=owner_pw,
4661 user_pw=user_pw,
4662 no_new_id=no_new_id,)
4663
4664
4665 def reload_page(self, page: "struct Page *") -> "struct Page *":
4666 """Make a fresh copy of a page."""
4667 old_annots = {} # copy annot references to here
4668 pno = page.number # save the page number
4669 for k, v in page._annot_refs.items(): # save the annot dictionary
4670 # We need to call pdf_keep_annot() here, otherwise `v`'s
4671 # refcount can reach zero even if there is an external
4672 # reference.
4673 self.internal_keep_annot(v)
4674 old_annots[k] = v
4675 page._erase() # remove the page
4676 page = None
4677 TOOLS.store_shrink(100)
4678 page = self.load_page(pno) # reload the page
4679
4680 # copy annot refs over to the new dictionary
4681 page_proxy = weakref.proxy(page)
4682 for k, v in old_annots.items():
4683 annot = old_annots[k]
4684 annot.parent = page_proxy # refresh parent to new page
4685 page._annot_refs[k] = annot
4686 return page
4687
4688
4689 @property
4690 def pagemode(self) -> str:
4691 """Return the PDF PageMode value.
4692 """
4693 xref = self.pdf_catalog()
4694 if xref == 0:
4695 return None
4696 rc = self.xref_get_key(xref, "PageMode")
4697 if rc[0] == "null":
4698 return "UseNone"
4699 if rc[0] == "name":
4700 return rc[1][1:]
4701 return "UseNone"
4702
4703
4704 def set_pagemode(self, pagemode: str):
4705 """Set the PDF PageMode value."""
4706 valid = ("UseNone", "UseOutlines", "UseThumbs", "FullScreen", "UseOC", "UseAttachments")
4707 xref = self.pdf_catalog()
4708 if xref == 0:
4709 raise ValueError("not a PDF")
4710 if not pagemode:
4711 raise ValueError("bad PageMode value")
4712 if pagemode[0] == "/":
4713 pagemode = pagemode[1:]
4714 for v in valid:
4715 if pagemode.lower() == v.lower():
4716 self.xref_set_key(xref, "PageMode", f"/{v}")
4717 return True
4718 raise ValueError("bad PageMode value")
4719
4720
4721 @property
4722 def pagelayout(self) -> str:
4723 """Return the PDF PageLayout value.
4724 """
4725 xref = self.pdf_catalog()
4726 if xref == 0:
4727 return None
4728 rc = self.xref_get_key(xref, "PageLayout")
4729 if rc[0] == "null":
4730 return "SinglePage"
4731 if rc[0] == "name":
4732 return rc[1][1:]
4733 return "SinglePage"
4734
4735
4736 def set_pagelayout(self, pagelayout: str):
4737 """Set the PDF PageLayout value."""
4738 valid = ("SinglePage", "OneColumn", "TwoColumnLeft", "TwoColumnRight", "TwoPageLeft", "TwoPageRight")
4739 xref = self.pdf_catalog()
4740 if xref == 0:
4741 raise ValueError("not a PDF")
4742 if not pagelayout:
4743 raise ValueError("bad PageLayout value")
4744 if pagelayout[0] == "/":
4745 pagelayout = pagelayout[1:]
4746 for v in valid:
4747 if pagelayout.lower() == v.lower():
4748 self.xref_set_key(xref, "PageLayout", f"/{v}")
4749 return True
4750 raise ValueError("bad PageLayout value")
4751
4752
4753 @property
4754 def markinfo(self) -> dict:
4755 """Return the PDF MarkInfo value."""
4756 xref = self.pdf_catalog()
4757 if xref == 0:
4758 return None
4759 rc = self.xref_get_key(xref, "MarkInfo")
4760 if rc[0] == "null":
4761 return {}
4762 if rc[0] == "xref":
4763 xref = int(rc[1].split()[0])
4764 val = self.xref_object(xref, compressed=True)
4765 elif rc[0] == "dict":
4766 val = rc[1]
4767 else:
4768 val = None
4769 if val == None or not (val[:2] == "<<" and val[-2:] == ">>"):
4770 return {}
4771 valid = {"Marked": False, "UserProperties": False, "Suspects": False}
4772 val = val[2:-2].split("/")
4773 for v in val[1:]:
4774 try:
4775 key, value = v.split()
4776 except:
4777 return valid
4778 if value == "true":
4779 valid[key] = True
4780 return valid
4781
4782
4783 def set_markinfo(self, markinfo: dict) -> bool:
4784 """Set the PDF MarkInfo values."""
4785 xref = self.pdf_catalog()
4786 if xref == 0:
4787 raise ValueError("not a PDF")
4788 if not markinfo or not isinstance(markinfo, dict):
4789 return False
4790 valid = {"Marked": False, "UserProperties": False, "Suspects": False}
4791
4792 if not set(valid.keys()).issuperset(markinfo.keys()):
4793 badkeys = f"bad MarkInfo key(s): {set(markinfo.keys()).difference(valid.keys())}"
4794 raise ValueError(badkeys)
4795 pdfdict = "<<"
4796 valid.update(markinfo)
4797 for key, value in valid.items():
4798 value=str(value).lower()
4799 if not value in ("true", "false"):
4800 raise ValueError(f"bad key value '{key}': '{value}'")
4801 pdfdict += f"/{key} {value}"
4802 pdfdict += ">>"
4803 self.xref_set_key(xref, "MarkInfo", pdfdict)
4804 return True
4805
4806
4807 def __repr__(self) -> str:
4808 m = "closed " if self.is_closed else ""
4809 if self.stream is None:
4810 if self.name == "":
4811 return m + "Document(<new PDF, doc# %i>)" % self._graft_id
4812 return m + "Document('%s')" % (self.name,)
4813 return m + "Document('%s', <memory, doc# %i>)" % (self.name, self._graft_id)
4814
4815
4816 def __contains__(self, loc) -> bool:
4817 if type(loc) is int:
4818 if loc < self.page_count:
4819 return True
4820 return False
4821 if type(loc) not in (tuple, list) or len(loc) != 2:
4822 return False
4823
4824 chapter, pno = loc
4825 if (type(chapter) != int or
4826 chapter < 0 or
4827 chapter >= self.chapter_count
4828 ):
4829 return False
4830 if (type(pno) != int or
4831 pno < 0 or
4832 pno >= self.chapter_page_count(chapter)
4833 ):
4834 return False
4835
4836 return True
4837
4838
4839 def __getitem__(self, i: int =0)->"Page":
4840 assert isinstance(i, int) or (isinstance(i, tuple) and len(i) == 2 and all(isinstance(x, int) for x in i))
4841 if i not in self:
4842 raise IndexError("page not in document")
4843 return self.load_page(i)
4844
4845
4846 def __delitem__(self, i: AnyType)->None:
4847 if not self.is_pdf:
4848 raise ValueError("is no PDF")
4849 if type(i) is int:
4850 return self.delete_page(i)
4851 if type(i) in (list, tuple, range):
4852 return self.delete_pages(i)
4853 if type(i) is not slice:
4854 raise ValueError("bad argument type")
4855 pc = self.page_count
4856 start = i.start if i.start else 0
4857 stop = i.stop if i.stop else pc
4858 step = i.step if i.step else 1
4859 while start < 0:
4860 start += pc
4861 if start >= pc:
4862 raise ValueError("bad page number(s)")
4863 while stop < 0:
4864 stop += pc
4865 if stop > pc:
4866 raise ValueError("bad page number(s)")
4867 return self.delete_pages(range(start, stop, step))
4868
4869
4870 def pages(self, start: OptInt =None, stop: OptInt =None, step: OptInt =None):
4871 """Return a generator iterator over a page range.
4872
4873 Arguments have the same meaning as for the range() built-in.
4874 """
4875 # set the start value
4876 start = start or 0
4877 while start < 0:
4878 start += self.page_count
4879 if start not in range(self.page_count):
4880 raise ValueError("bad start page number")
4881
4882 # set the stop value
4883 stop = stop if stop is not None and stop <= self.page_count else self.page_count
4884
4885 # set the step value
4886 if step == 0:
4887 raise ValueError("arg 3 must not be zero")
4888 if step is None:
4889 if start > stop:
4890 step = -1
4891 else:
4892 step = 1
4893
4894 for pno in range(start, stop, step):
4895 yield (self.load_page(pno))
4896
4897
4898 def __len__(self) -> int:
4899 return self.page_count
4900
4901 def _forget_page(self, page: "struct Page *"):
4902 """Remove a page from document page dict."""
4903 pid = id(page)
4904 if pid in self._page_refs:
4905 self._page_refs[pid] = None
4906
4907 def _reset_page_refs(self):
4908 """Invalidate all pages in document dictionary."""
4909 if getattr(self, "is_closed", True):
4910 return
4911 for page in self._page_refs.values():
4912 if page:
4913 page._erase()
4914 page = None
4915 self._page_refs.clear()
4916
4917
4918
4919 def _cleanup(self):
4920 self._reset_page_refs()
4921 for k in self.Graftmaps.keys():
4922 self.Graftmaps[k] = None
4923 self.Graftmaps = {}
4924 self.ShownPages = {}
4925 self.InsertedImages = {}
4926 self.FontInfos = []
4927 self.metadata = None
4928 self.stream = None
4929 self.is_closed = True
4930
4931
4932 def close(self):
4933 """Close the document."""
4934 if getattr(self, "is_closed", False):
4935 raise ValueError("document closed")
4936 self._cleanup()
4937 if getattr(self, "thisown", False):
4938 self.__swig_destroy__(self)
4939 return
4940 else:
4941 raise RuntimeError("document object unavailable")
4942
4943 def __del__(self):
4944 if not type(self) is Document:
4945 return
4946 self._cleanup()
4947 if getattr(self, "thisown", False):
4948 self.__swig_destroy__(self)
4949
4950 def __enter__(self):
4951 return self
4952
4953 def __exit__(self, *args):
4954 self.close()
4955 %}
4956 }
4957 };
4958
4959 /*****************************************************************************/
4960 // fz_page
4961 /*****************************************************************************/
4962 %nodefaultctor;
4963 struct Page {
4964 %extend {
4965 ~Page()
4966 {
4967 DEBUGMSG1("Page");
4968 fz_page *this_page = (fz_page *) $self;
4969 fz_drop_page(gctx, this_page);
4970 DEBUGMSG2;
4971 }
4972 //----------------------------------------------------------------
4973 // bound()
4974 //----------------------------------------------------------------
4975 FITZEXCEPTION(bound, !result)
4976 PARENTCHECK(bound, """Get page rectangle.""")
4977 %pythonappend bound %{
4978 val = Rect(val)
4979 if val.is_infinite and self.parent.is_pdf:
4980 cb = self.cropbox
4981 w, h = cb.width, cb.height
4982 if self.rotation not in (0, 180):
4983 w, h = h, w
4984 val = Rect(0, 0, w, h)
4985 msg = TOOLS.mupdf_warnings(reset=False).splitlines()[-1]
4986 print(msg, file=sys.stderr)
4987 %}
4988 PyObject *bound() {
4989 fz_rect rect = fz_infinite_rect;
4990 fz_try(gctx) {
4991 rect = fz_bound_page(gctx, (fz_page *) $self);
4992 }
4993 fz_catch(gctx) {
4994 ;
4995 }
4996 return JM_py_from_rect(rect);
4997 }
4998 %pythoncode %{rect = property(bound, doc="page rectangle")%}
4999
5000 //----------------------------------------------------------------
5001 // Page.get_image_bbox
5002 //----------------------------------------------------------------
5003 %pythonprepend get_image_bbox %{
5004 """Get rectangle occupied by image 'name'.
5005
5006 'name' is either an item of the image list, or the referencing
5007 name string - elem[7] of the resp. item.
5008 Option 'transform' also returns the image transformation matrix.
5009 """
5010 CheckParent(self)
5011 doc = self.parent
5012 if doc.is_closed or doc.is_encrypted:
5013 raise ValueError("document closed or encrypted")
5014
5015 inf_rect = Rect(1, 1, -1, -1)
5016 null_mat = Matrix()
5017 if transform:
5018 rc = (inf_rect, null_mat)
5019 else:
5020 rc = inf_rect
5021
5022 if type(name) in (list, tuple):
5023 if not type(name[-1]) is int:
5024 raise ValueError("need item of full page image list")
5025 item = name
5026 else:
5027 imglist = [i for i in doc.get_page_images(self.number, True) if name == i[7]]
5028 if len(imglist) == 1:
5029 item = imglist[0]
5030 elif imglist == []:
5031 raise ValueError("bad image name")
5032 else:
5033 raise ValueError("found multiple images named '%s'." % name)
5034 xref = item[-1]
5035 if xref != 0 or transform == True:
5036 try:
5037 return self.get_image_rects(item, transform=transform)[0]
5038 except:
5039 return inf_rect
5040 %}
5041 %pythonappend get_image_bbox %{
5042 if not bool(val):
5043 return rc
5044
5045 for v in val:
5046 if v[0] != item[-3]:
5047 continue
5048 q = Quad(v[1])
5049 bbox = q.rect
5050 if transform == 0:
5051 rc = bbox
5052 break
5053
5054 hm = Matrix(util_hor_matrix(q.ll, q.lr))
5055 h = abs(q.ll - q.ul)
5056 w = abs(q.ur - q.ul)
5057 m0 = Matrix(1 / w, 0, 0, 1 / h, 0, 0)
5058 m = ~(hm * m0)
5059 rc = (bbox, m)
5060 break
5061 val = rc%}
5062 PyObject *
5063 get_image_bbox(PyObject *name, int transform=0)
5064 {
5065 pdf_page *pdf_page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5066 PyObject *rc =NULL;
5067 fz_try(gctx) {
5068 rc = JM_image_reporter(gctx, pdf_page);
5069 }
5070 fz_catch(gctx) {
5071 Py_RETURN_NONE;
5072 }
5073 return rc;
5074 }
5075
5076 //----------------------------------------------------------------
5077 // run()
5078 //----------------------------------------------------------------
5079 FITZEXCEPTION(run, !result)
5080 PARENTCHECK(run, """Run page through a device.""")
5081 PyObject *run(struct DeviceWrapper *dw, PyObject *m)
5082 {
5083 fz_try(gctx) {
5084 fz_run_page(gctx, (fz_page *) $self, dw->device, JM_matrix_from_py(m), NULL);
5085 }
5086 fz_catch(gctx) {
5087 return NULL;
5088 }
5089 Py_RETURN_NONE;
5090 }
5091
5092 //----------------------------------------------------------------
5093 // Page.extend_textpage
5094 //----------------------------------------------------------------
5095 FITZEXCEPTION(extend_textpage, !result)
5096 PyObject *
5097 extend_textpage(struct TextPage *tpage, int flags=0, PyObject *matrix=NULL)
5098 {
5099 fz_page *page = (fz_page *) $self;
5100 fz_stext_page *tp = (fz_stext_page *) tpage;
5101 fz_device *dev = NULL;
5102 fz_stext_options options;
5103 memset(&options, 0, sizeof options);
5104 options.flags = flags;
5105 fz_try(gctx) {
5106 fz_matrix ctm = JM_matrix_from_py(matrix);
5107 dev = fz_new_stext_device(gctx, tp, &options);
5108 fz_run_page(gctx, page, dev, ctm, NULL);
5109 fz_close_device(gctx, dev);
5110 }
5111 fz_always(gctx) {
5112 fz_drop_device(gctx, dev);
5113 }
5114 fz_catch(gctx) {
5115 return NULL;
5116 }
5117 Py_RETURN_NONE;
5118 }
5119
5120
5121 //----------------------------------------------------------------
5122 // Page.get_textpage
5123 //----------------------------------------------------------------
5124 FITZEXCEPTION(_get_textpage, !result)
5125 %pythonappend _get_textpage %{val.thisown = True%}
5126 struct TextPage *
5127 _get_textpage(PyObject *clip=NULL, int flags=0, PyObject *matrix=NULL)
5128 {
5129 fz_stext_page *tpage=NULL;
5130 fz_page *page = (fz_page *) $self;
5131 fz_device *dev = NULL;
5132 fz_stext_options options;
5133 memset(&options, 0, sizeof options);
5134 options.flags = flags;
5135 fz_try(gctx) {
5136 // Default to page's rect if `clip` not specified, for #2048.
5137 fz_rect rect = (clip==Py_None) ? fz_bound_page(gctx, page) : JM_rect_from_py(clip);
5138 fz_matrix ctm = JM_matrix_from_py(matrix);
5139 tpage = fz_new_stext_page(gctx, rect);
5140 dev = fz_new_stext_device(gctx, tpage, &options);
5141 fz_run_page(gctx, page, dev, ctm, NULL);
5142 fz_close_device(gctx, dev);
5143 }
5144 fz_always(gctx) {
5145 fz_drop_device(gctx, dev);
5146 }
5147 fz_catch(gctx) {
5148 return NULL;
5149 }
5150 return (struct TextPage *) tpage;
5151 }
5152
5153
5154 %pythoncode %{
5155 def get_textpage(self, clip: rect_like = None, flags: int = 0, matrix=None) -> "TextPage":
5156 CheckParent(self)
5157 if matrix is None:
5158 matrix = Matrix(1, 1)
5159 old_rotation = self.rotation
5160 if old_rotation != 0:
5161 self.set_rotation(0)
5162 try:
5163 textpage = self._get_textpage(clip, flags=flags, matrix=matrix)
5164 finally:
5165 if old_rotation != 0:
5166 self.set_rotation(old_rotation)
5167 textpage.parent = weakref.proxy(self)
5168 return textpage
5169 %}
5170
5171 /* ****************** currently inactive
5172 //----------------------------------------------------------------
5173 // Page._get_textpage_ocr
5174 //----------------------------------------------------------------
5175 FITZEXCEPTION(_get_textpage_ocr, !result)
5176 %pythonappend _get_textpage_ocr %{val.thisown = True%}
5177 struct TextPage *
5178 _get_textpage_ocr(PyObject *clip=NULL, int flags=0, const char *language=NULL, const char *tessdata=NULL)
5179 {
5180 fz_stext_page *textpage=NULL;
5181 fz_try(gctx) {
5182 fz_rect rect = JM_rect_from_py(clip);
5183 textpage = JM_new_stext_page_ocr_from_page(gctx, (fz_page *) $self, rect, flags, language, tessdata);
5184 }
5185 fz_catch(gctx) {
5186 return NULL;
5187 }
5188 return (struct TextPage *) textpage;
5189 }
5190 ************************* */
5191
5192 //----------------------------------------------------------------
5193 // Page.language
5194 //----------------------------------------------------------------
5195 %pythoncode%{@property%}
5196 %pythonprepend language %{"""Page language."""%}
5197 PyObject *language()
5198 {
5199 pdf_page *pdfpage = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5200 if (!pdfpage) Py_RETURN_NONE;
5201 pdf_obj *lang = pdf_dict_get_inheritable(gctx, pdfpage->obj, PDF_NAME(Lang));
5202 if (!lang) Py_RETURN_NONE;
5203 return Py_BuildValue("s", pdf_to_str_buf(gctx, lang));
5204 }
5205
5206
5207 //----------------------------------------------------------------
5208 // Page.set_language
5209 //----------------------------------------------------------------
5210 FITZEXCEPTION(set_language, !result)
5211 PARENTCHECK(set_language, """Set PDF page default language.""")
5212 PyObject *set_language(char *language=NULL)
5213 {
5214 pdf_page *pdfpage = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5215 fz_try(gctx) {
5216 ASSERT_PDF(pdfpage);
5217 fz_text_language lang;
5218 char buf[8];
5219 if (!language) {
5220 pdf_dict_del(gctx, pdfpage->obj, PDF_NAME(Lang));
5221 } else {
5222 lang = fz_text_language_from_string(language);
5223 pdf_dict_put_text_string(gctx, pdfpage->obj,
5224 PDF_NAME(Lang),
5225 fz_string_from_text_language(buf, lang));
5226 }
5227 }
5228 fz_catch(gctx) {
5229 return NULL;
5230 }
5231 Py_RETURN_TRUE;
5232 }
5233
5234
5235 //----------------------------------------------------------------
5236 // Page.get_svg_image
5237 //----------------------------------------------------------------
5238 FITZEXCEPTION(get_svg_image, !result)
5239 PARENTCHECK(get_svg_image, """Make SVG image from page.""")
5240 PyObject *get_svg_image(PyObject *matrix = NULL, int text_as_path=1)
5241 {
5242 fz_rect mediabox = fz_bound_page(gctx, (fz_page *) $self);
5243 fz_device *dev = NULL;
5244 fz_buffer *res = NULL;
5245 PyObject *text = NULL;
5246 fz_matrix ctm = JM_matrix_from_py(matrix);
5247 fz_output *out = NULL;
5248 fz_var(out);
5249 fz_var(dev);
5250 fz_var(res);
5251 fz_rect tbounds = mediabox;
5252 int text_option = (text_as_path == 1) ? FZ_SVG_TEXT_AS_PATH : FZ_SVG_TEXT_AS_TEXT;
5253 tbounds = fz_transform_rect(tbounds, ctm);
5254
5255 fz_try(gctx) {
5256 res = fz_new_buffer(gctx, 1024);
5257 out = fz_new_output_with_buffer(gctx, res);
5258 dev = fz_new_svg_device(gctx, out,
5259 tbounds.x1-tbounds.x0, // width
5260 tbounds.y1-tbounds.y0, // height
5261 text_option, 1);
5262 fz_run_page(gctx, (fz_page *) $self, dev, ctm, NULL);
5263 fz_close_device(gctx, dev);
5264 text = JM_EscapeStrFromBuffer(gctx, res);
5265 }
5266 fz_always(gctx) {
5267 fz_drop_device(gctx, dev);
5268 fz_drop_output(gctx, out);
5269 fz_drop_buffer(gctx, res);
5270 }
5271 fz_catch(gctx) {
5272 return NULL;
5273 }
5274 return text;
5275 }
5276
5277
5278 //----------------------------------------------------------------
5279 // page set opacity
5280 //----------------------------------------------------------------
5281 FITZEXCEPTION(_set_opacity, !result)
5282 %pythonprepend _set_opacity %{
5283 if CA >= 1 and ca >= 1 and blendmode == None:
5284 return None
5285 tCA = int(round(max(CA , 0) * 100))
5286 if tCA >= 100:
5287 tCA = 99
5288 tca = int(round(max(ca, 0) * 100))
5289 if tca >= 100:
5290 tca = 99
5291 gstate = "fitzca%02i%02i" % (tCA, tca)
5292 %}
5293 PyObject *
5294 _set_opacity(char *gstate=NULL, float CA=1, float ca=1, char *blendmode=NULL)
5295 {
5296 if (!gstate) Py_RETURN_NONE;
5297 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5298 fz_try(gctx) {
5299 ASSERT_PDF(page);
5300 pdf_obj *resources = pdf_dict_get(gctx, page->obj, PDF_NAME(Resources));
5301 if (!resources) {
5302 resources = pdf_dict_put_dict(gctx, page->obj, PDF_NAME(Resources), 2);
5303 }
5304 pdf_obj *extg = pdf_dict_get(gctx, resources, PDF_NAME(ExtGState));
5305 if (!extg) {
5306 extg = pdf_dict_put_dict(gctx, resources, PDF_NAME(ExtGState), 2);
5307 }
5308 int i, n = pdf_dict_len(gctx, extg);
5309 for (i = 0; i < n; i++) {
5310 pdf_obj *o1 = pdf_dict_get_key(gctx, extg, i);
5311 char *name = (char *) pdf_to_name(gctx, o1);
5312 if (strcmp(name, gstate) == 0) goto finished;
5313 }
5314 pdf_obj *opa = pdf_new_dict(gctx, page->doc, 3);
5315 pdf_dict_put_real(gctx, opa, PDF_NAME(CA), (double) CA);
5316 pdf_dict_put_real(gctx, opa, PDF_NAME(ca), (double) ca);
5317 pdf_dict_puts_drop(gctx, extg, gstate, opa);
5318 finished:;
5319 }
5320 fz_always(gctx) {
5321 }
5322 fz_catch(gctx) {
5323 return NULL;
5324 }
5325 return Py_BuildValue("s", gstate);
5326 }
5327
5328 //----------------------------------------------------------------
5329 // page add_caret_annot
5330 //----------------------------------------------------------------
5331 FITZEXCEPTION(_add_caret_annot, !result)
5332 struct Annot *
5333 _add_caret_annot(PyObject *point)
5334 {
5335 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5336 pdf_annot *annot = NULL;
5337 fz_try(gctx) {
5338 annot = pdf_create_annot(gctx, page, PDF_ANNOT_CARET);
5339 if (point)
5340 {
5341 fz_point p = JM_point_from_py(point);
5342 fz_rect r = pdf_annot_rect(gctx, annot);
5343 r = fz_make_rect(p.x, p.y, p.x + r.x1 - r.x0, p.y + r.y1 - r.y0);
5344 pdf_set_annot_rect(gctx, annot, r);
5345 }
5346 pdf_update_annot(gctx, annot);
5347 JM_add_annot_id(gctx, annot, "A");
5348 }
5349 fz_catch(gctx) {
5350 return NULL;
5351 }
5352 return (struct Annot *) annot;
5353 }
5354
5355
5356 //----------------------------------------------------------------
5357 // page addRedactAnnot
5358 //----------------------------------------------------------------
5359 FITZEXCEPTION(_add_redact_annot, !result)
5360 struct Annot *
5361 _add_redact_annot(PyObject *quad,
5362 PyObject *text=NULL,
5363 PyObject *da_str=NULL,
5364 int align=0,
5365 PyObject *fill=NULL,
5366 PyObject *text_color=NULL)
5367 {
5368 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5369 pdf_annot *annot = NULL;
5370 float fcol[4] = { 1, 1, 1, 0};
5371 int nfcol = 0, i;
5372 fz_try(gctx) {
5373 annot = pdf_create_annot(gctx, page, PDF_ANNOT_REDACT);
5374 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
5375 fz_quad q = JM_quad_from_py(quad);
5376 fz_rect r = fz_rect_from_quad(q);
5377
5378 // TODO calculate de-rotated rect
5379 pdf_set_annot_rect(gctx, annot, r);
5380 if (EXISTS(fill)) {
5381 JM_color_FromSequence(fill, &nfcol, fcol);
5382 pdf_obj *arr = pdf_new_array(gctx, page->doc, nfcol);
5383 for (i = 0; i < nfcol; i++) {
5384 pdf_array_push_real(gctx, arr, fcol[i]);
5385 }
5386 pdf_dict_put_drop(gctx, annot_obj, PDF_NAME(IC), arr);
5387 }
5388 if (EXISTS(text)) {
5389 const char *otext = PyUnicode_AsUTF8(text);
5390 pdf_dict_puts_drop(gctx, annot_obj, "OverlayText",
5391 pdf_new_text_string(gctx, otext));
5392 pdf_dict_put_text_string(gctx,annot_obj, PDF_NAME(DA), PyUnicode_AsUTF8(da_str));
5393 pdf_dict_put_int(gctx, annot_obj, PDF_NAME(Q), (int64_t) align);
5394 }
5395 pdf_update_annot(gctx, annot);
5396 JM_add_annot_id(gctx, annot, "A");
5397 }
5398 fz_catch(gctx) {
5399 return NULL;
5400 }
5401 return (struct Annot *) annot;
5402 }
5403
5404 //----------------------------------------------------------------
5405 // page addLineAnnot
5406 //----------------------------------------------------------------
5407 FITZEXCEPTION(_add_line_annot, !result)
5408 struct Annot *
5409 _add_line_annot(PyObject *p1, PyObject *p2)
5410 {
5411 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5412 pdf_annot *annot = NULL;
5413 fz_try(gctx) {
5414 ASSERT_PDF(page);
5415 annot = pdf_create_annot(gctx, page, PDF_ANNOT_LINE);
5416 fz_point a = JM_point_from_py(p1);
5417 fz_point b = JM_point_from_py(p2);
5418 pdf_set_annot_line(gctx, annot, a, b);
5419 pdf_update_annot(gctx, annot);
5420 JM_add_annot_id(gctx, annot, "A");
5421 }
5422 fz_catch(gctx) {
5423 return NULL;
5424 }
5425 return (struct Annot *) annot;
5426 }
5427
5428 //----------------------------------------------------------------
5429 // page addTextAnnot
5430 //----------------------------------------------------------------
5431 FITZEXCEPTION(_add_text_annot, !result)
5432 struct Annot *
5433 _add_text_annot(PyObject *point,
5434 char *text,
5435 char *icon=NULL)
5436 {
5437 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5438 pdf_annot *annot = NULL;
5439 fz_rect r;
5440 fz_point p = JM_point_from_py(point);
5441 fz_var(annot);
5442 fz_try(gctx) {
5443 ASSERT_PDF(page);
5444 annot = pdf_create_annot(gctx, page, PDF_ANNOT_TEXT);
5445 r = pdf_annot_rect(gctx, annot);
5446 r = fz_make_rect(p.x, p.y, p.x + r.x1 - r.x0, p.y + r.y1 - r.y0);
5447 pdf_set_annot_rect(gctx, annot, r);
5448 pdf_set_annot_contents(gctx, annot, text);
5449 if (icon) {
5450 pdf_set_annot_icon_name(gctx, annot, icon);
5451 }
5452 pdf_update_annot(gctx, annot);
5453 JM_add_annot_id(gctx, annot, "A");
5454 }
5455 fz_catch(gctx) {
5456 return NULL;
5457 }
5458 return (struct Annot *) annot;
5459 }
5460
5461 //----------------------------------------------------------------
5462 // page addInkAnnot
5463 //----------------------------------------------------------------
5464 FITZEXCEPTION(_add_ink_annot, !result)
5465 struct Annot *
5466 _add_ink_annot(PyObject *list)
5467 {
5468 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5469 pdf_annot *annot = NULL;
5470 PyObject *p = NULL, *sublist = NULL;
5471 pdf_obj *inklist = NULL, *stroke = NULL;
5472 fz_matrix ctm, inv_ctm;
5473 fz_point point;
5474 fz_var(annot);
5475 fz_try(gctx) {
5476 ASSERT_PDF(page);
5477 if (!PySequence_Check(list)) {
5478 RAISEPY(gctx, MSG_BAD_ARG_INK_ANNOT, PyExc_ValueError);
5479 }
5480 pdf_page_transform(gctx, page, NULL, &ctm);
5481 inv_ctm = fz_invert_matrix(ctm);
5482 annot = pdf_create_annot(gctx, page, PDF_ANNOT_INK);
5483 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
5484 Py_ssize_t i, j, n0 = PySequence_Size(list), n1;
5485 inklist = pdf_new_array(gctx, page->doc, n0);
5486
5487 for (j = 0; j < n0; j++) {
5488 sublist = PySequence_ITEM(list, j);
5489 n1 = PySequence_Size(sublist);
5490 stroke = pdf_new_array(gctx, page->doc, 2 * n1);
5491
5492 for (i = 0; i < n1; i++) {
5493 p = PySequence_ITEM(sublist, i);
5494 if (!PySequence_Check(p) || PySequence_Size(p) != 2) {
5495 RAISEPY(gctx, MSG_BAD_ARG_INK_ANNOT, PyExc_ValueError);
5496 }
5497 point = fz_transform_point(JM_point_from_py(p), inv_ctm);
5498 Py_CLEAR(p);
5499 pdf_array_push_real(gctx, stroke, point.x);
5500 pdf_array_push_real(gctx, stroke, point.y);
5501 }
5502
5503 pdf_array_push_drop(gctx, inklist, stroke);
5504 stroke = NULL;
5505 Py_CLEAR(sublist);
5506 }
5507
5508 pdf_dict_put_drop(gctx, annot_obj, PDF_NAME(InkList), inklist);
5509 inklist = NULL;
5510 pdf_update_annot(gctx, annot);
5511 JM_add_annot_id(gctx, annot, "A");
5512 }
5513
5514 fz_catch(gctx) {
5515 Py_CLEAR(p);
5516 Py_CLEAR(sublist);
5517 return NULL;
5518 }
5519 return (struct Annot *) annot;
5520 }
5521
5522 //----------------------------------------------------------------
5523 // page addStampAnnot
5524 //----------------------------------------------------------------
5525 FITZEXCEPTION(_add_stamp_annot, !result)
5526 struct Annot *
5527 _add_stamp_annot(PyObject *rect, int stamp=0)
5528 {
5529 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5530 pdf_annot *annot = NULL;
5531 pdf_obj *stamp_id[] = {PDF_NAME(Approved), PDF_NAME(AsIs),
5532 PDF_NAME(Confidential), PDF_NAME(Departmental),
5533 PDF_NAME(Experimental), PDF_NAME(Expired),
5534 PDF_NAME(Final), PDF_NAME(ForComment),
5535 PDF_NAME(ForPublicRelease), PDF_NAME(NotApproved),
5536 PDF_NAME(NotForPublicRelease), PDF_NAME(Sold),
5537 PDF_NAME(TopSecret), PDF_NAME(Draft)};
5538 int n = nelem(stamp_id);
5539 pdf_obj *name = stamp_id[0];
5540 fz_try(gctx) {
5541 ASSERT_PDF(page);
5542 fz_rect r = JM_rect_from_py(rect);
5543 if (fz_is_infinite_rect(r) || fz_is_empty_rect(r)) {
5544 RAISEPY(gctx, MSG_BAD_RECT, PyExc_ValueError);
5545 }
5546 if (INRANGE(stamp, 0, n-1)) {
5547 name = stamp_id[stamp];
5548 }
5549 annot = pdf_create_annot(gctx, page, PDF_ANNOT_STAMP);
5550 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
5551 pdf_set_annot_rect(gctx, annot, r);
5552 pdf_dict_put(gctx, annot_obj, PDF_NAME(Name), name);
5553 pdf_set_annot_contents(gctx, annot,
5554 pdf_dict_get_name(gctx, annot_obj, PDF_NAME(Name)));
5555 pdf_update_annot(gctx, annot);
5556 JM_add_annot_id(gctx, annot, "A");
5557 }
5558 fz_catch(gctx) {
5559 return NULL;
5560 }
5561 return (struct Annot *) annot;
5562 }
5563
5564 //----------------------------------------------------------------
5565 // page addFileAnnot
5566 //----------------------------------------------------------------
5567 FITZEXCEPTION(_add_file_annot, !result)
5568 struct Annot *
5569 _add_file_annot(PyObject *point,
5570 PyObject *buffer,
5571 char *filename,
5572 char *ufilename=NULL,
5573 char *desc=NULL,
5574 char *icon=NULL)
5575 {
5576 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5577 pdf_annot *annot = NULL;
5578 char *uf = ufilename, *d = desc;
5579 if (!ufilename) uf = filename;
5580 if (!desc) d = filename;
5581 fz_buffer *filebuf = NULL;
5582 fz_rect r;
5583 fz_point p = JM_point_from_py(point);
5584 fz_var(filebuf);
5585 fz_try(gctx) {
5586 ASSERT_PDF(page);
5587 filebuf = JM_BufferFromBytes(gctx, buffer);
5588 if (!filebuf) {
5589 RAISEPY(gctx, MSG_BAD_BUFFER, PyExc_TypeError);
5590 }
5591 annot = pdf_create_annot(gctx, page, PDF_ANNOT_FILE_ATTACHMENT);
5592 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
5593 r = pdf_annot_rect(gctx, annot);
5594 r = fz_make_rect(p.x, p.y, p.x + r.x1 - r.x0, p.y + r.y1 - r.y0);
5595 pdf_set_annot_rect(gctx, annot, r);
5596 int flags = PDF_ANNOT_IS_PRINT;
5597 pdf_set_annot_flags(gctx, annot, flags);
5598
5599 if (icon)
5600 pdf_set_annot_icon_name(gctx, annot, icon);
5601
5602 pdf_obj *val = JM_embed_file(gctx, page->doc, filebuf,
5603 filename, uf, d, 1);
5604 pdf_dict_put_drop(gctx, annot_obj, PDF_NAME(FS), val);
5605 pdf_dict_put_text_string(gctx, annot_obj, PDF_NAME(Contents), filename);
5606 pdf_update_annot(gctx, annot);
5607 pdf_set_annot_rect(gctx, annot, r);
5608 pdf_set_annot_flags(gctx, annot, flags);
5609 JM_add_annot_id(gctx, annot, "A");
5610 }
5611 fz_always(gctx) {
5612 fz_drop_buffer(gctx, filebuf);
5613 }
5614 fz_catch(gctx) {
5615 return NULL;
5616 }
5617 return (struct Annot *) annot;
5618 }
5619
5620
5621 //----------------------------------------------------------------
5622 // page: add a text marker annotation
5623 //----------------------------------------------------------------
5624 FITZEXCEPTION(_add_text_marker, !result)
5625 %pythonprepend _add_text_marker %{
5626 CheckParent(self)
5627 if not self.parent.is_pdf:
5628 raise ValueError("is no PDF")%}
5629
5630 %pythonappend _add_text_marker %{
5631 if not val:
5632 return None
5633 val.parent = weakref.proxy(self)
5634 self._annot_refs[id(val)] = val%}
5635
5636 struct Annot *
5637 _add_text_marker(PyObject *quads, int annot_type)
5638 {
5639 pdf_page *pdfpage = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5640 pdf_annot *annot = NULL;
5641 PyObject *item = NULL;
5642 int rotation = JM_page_rotation(gctx, pdfpage);
5643 fz_quad q;
5644 fz_var(annot);
5645 fz_var(item);
5646 fz_try(gctx) {
5647 if (rotation != 0) {
5648 pdf_dict_put_int(gctx, pdfpage->obj, PDF_NAME(Rotate), 0);
5649 }
5650 annot = pdf_create_annot(gctx, pdfpage, annot_type);
5651 Py_ssize_t i, len = PySequence_Size(quads);
5652 for (i = 0; i < len; i++) {
5653 item = PySequence_ITEM(quads, i);
5654 q = JM_quad_from_py(item);
5655 Py_DECREF(item);
5656 pdf_add_annot_quad_point(gctx, annot, q);
5657 }
5658 pdf_update_annot(gctx, annot);
5659 JM_add_annot_id(gctx, annot, "A");
5660 }
5661 fz_always(gctx) {
5662 if (rotation != 0) {
5663 pdf_dict_put_int(gctx, pdfpage->obj, PDF_NAME(Rotate), rotation);
5664 }
5665 }
5666 fz_catch(gctx) {
5667 pdf_drop_annot(gctx, annot);
5668 return NULL;
5669 }
5670 return (struct Annot *) annot;
5671 }
5672
5673
5674 //----------------------------------------------------------------
5675 // page: add circle or rectangle annotation
5676 //----------------------------------------------------------------
5677 FITZEXCEPTION(_add_square_or_circle, !result)
5678 struct Annot *
5679 _add_square_or_circle(PyObject *rect, int annot_type)
5680 {
5681 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5682 pdf_annot *annot = NULL;
5683 fz_try(gctx) {
5684 fz_rect r = JM_rect_from_py(rect);
5685 if (fz_is_infinite_rect(r) || fz_is_empty_rect(r)) {
5686 RAISEPY(gctx, MSG_BAD_RECT, PyExc_ValueError);
5687 }
5688 annot = pdf_create_annot(gctx, page, annot_type);
5689 pdf_set_annot_rect(gctx, annot, r);
5690 pdf_update_annot(gctx, annot);
5691 JM_add_annot_id(gctx, annot, "A");
5692 }
5693 fz_catch(gctx) {
5694 return NULL;
5695 }
5696 return (struct Annot *) annot;
5697 }
5698
5699
5700 //----------------------------------------------------------------
5701 // page: add multiline annotation
5702 //----------------------------------------------------------------
5703 FITZEXCEPTION(_add_multiline, !result)
5704 struct Annot *
5705 _add_multiline(PyObject *points, int annot_type)
5706 {
5707 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5708 pdf_annot *annot = NULL;
5709 fz_try(gctx) {
5710 Py_ssize_t i, n = PySequence_Size(points);
5711 if (n < 2) {
5712 RAISEPY(gctx, MSG_BAD_ARG_POINTS, PyExc_ValueError);
5713 }
5714 annot = pdf_create_annot(gctx, page, annot_type);
5715 for (i = 0; i < n; i++) {
5716 PyObject *p = PySequence_ITEM(points, i);
5717 if (PySequence_Size(p) != 2) {
5718 Py_DECREF(p);
5719 RAISEPY(gctx, MSG_BAD_ARG_POINTS, PyExc_ValueError);
5720 }
5721 fz_point point = JM_point_from_py(p);
5722 Py_DECREF(p);
5723 pdf_add_annot_vertex(gctx, annot, point);
5724 }
5725
5726 pdf_update_annot(gctx, annot);
5727 JM_add_annot_id(gctx, annot, "A");
5728 }
5729 fz_catch(gctx) {
5730 return NULL;
5731 }
5732 return (struct Annot *) annot;
5733 }
5734
5735
5736 //----------------------------------------------------------------
5737 // page addFreetextAnnot
5738 //----------------------------------------------------------------
5739 FITZEXCEPTION(_add_freetext_annot, !result)
5740 %pythonappend _add_freetext_annot %{
5741 ap = val._getAP()
5742 BT = ap.find(b"BT")
5743 ET = ap.find(b"ET") + 2
5744 ap = ap[BT:ET]
5745 w = rect[2]-rect[0]
5746 h = rect[3]-rect[1]
5747 if rotate in (90, -90, 270):
5748 w, h = h, w
5749 re = b"0 0 %g %g re" % (w, h)
5750 ap = re + b"\nW\nn\n" + ap
5751 ope = None
5752 bwidth = b""
5753 fill_string = ColorCode(fill_color, "f").encode()
5754 if fill_string:
5755 fill_string += b"\n"
5756 ope = b"f"
5757 stroke_string = ColorCode(border_color, "c").encode()
5758 if stroke_string:
5759 stroke_string += b"\n"
5760 bwidth = b"1 w\n"
5761 ope = b"S"
5762 if fill_string and stroke_string:
5763 ope = b"B"
5764 if ope != None:
5765 ap = bwidth + fill_string + stroke_string + re + b"\n" + ope + b"\n" + ap
5766 val._setAP(ap)
5767 %}
5768 struct Annot *
5769 _add_freetext_annot(PyObject *rect, char *text,
5770 float fontsize=11,
5771 char *fontname=NULL,
5772 PyObject *text_color=NULL,
5773 PyObject *fill_color=NULL,
5774 PyObject *border_color=NULL,
5775 int align=0,
5776 int rotate=0)
5777 {
5778 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
5779 float fcol[4] = {1, 1, 1, 1}; // fill color: white
5780 int nfcol = 0;
5781 JM_color_FromSequence(fill_color, &nfcol, fcol);
5782 float tcol[4] = {0, 0, 0, 0}; // std. text color: black
5783 int ntcol = 0;
5784 JM_color_FromSequence(text_color, &ntcol, tcol);
5785 fz_rect r = JM_rect_from_py(rect);
5786 pdf_annot *annot = NULL;
5787 fz_try(gctx) {
5788 if (fz_is_infinite_rect(r) || fz_is_empty_rect(r)) {
5789 RAISEPY(gctx, MSG_BAD_RECT, PyExc_ValueError);
5790 }
5791 annot = pdf_create_annot(gctx, page, PDF_ANNOT_FREE_TEXT);
5792 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
5793 pdf_set_annot_contents(gctx, annot, text);
5794 pdf_set_annot_rect(gctx, annot, r);
5795 pdf_dict_put_int(gctx, annot_obj, PDF_NAME(Rotate), rotate);
5796 pdf_dict_put_int(gctx, annot_obj, PDF_NAME(Q), align);
5797
5798 if (nfcol > 0) {
5799 pdf_set_annot_color(gctx, annot, nfcol, fcol);
5800 }
5801
5802 // insert the default appearance string
5803 JM_make_annot_DA(gctx, annot, ntcol, tcol, fontname, fontsize);
5804 pdf_update_annot(gctx, annot);
5805 JM_add_annot_id(gctx, annot, "A");
5806 }
5807 fz_catch(gctx) {
5808 return NULL;
5809 }
5810 return (struct Annot *) annot;
5811 }
5812
5813
5814 %pythoncode %{
5815 @property
5816 def rotation_matrix(self) -> Matrix:
5817 """Reflects page rotation."""
5818 return Matrix(TOOLS._rotate_matrix(self))
5819
5820 @property
5821 def derotation_matrix(self) -> Matrix:
5822 """Reflects page de-rotation."""
5823 return Matrix(TOOLS._derotate_matrix(self))
5824
5825 def add_caret_annot(self, point: point_like) -> "struct Annot *":
5826 """Add a 'Caret' annotation."""
5827 old_rotation = annot_preprocess(self)
5828 try:
5829 annot = self._add_caret_annot(point)
5830 finally:
5831 if old_rotation != 0:
5832 self.set_rotation(old_rotation)
5833 annot_postprocess(self, annot)
5834 return annot
5835
5836
5837 def add_strikeout_annot(self, quads=None, start=None, stop=None, clip=None) -> "struct Annot *":
5838 """Add a 'StrikeOut' annotation."""
5839 if quads is None:
5840 q = get_highlight_selection(self, start=start, stop=stop, clip=clip)
5841 else:
5842 q = CheckMarkerArg(quads)
5843 return self._add_text_marker(q, PDF_ANNOT_STRIKE_OUT)
5844
5845
5846 def add_underline_annot(self, quads=None, start=None, stop=None, clip=None) -> "struct Annot *":
5847 """Add a 'Underline' annotation."""
5848 if quads is None:
5849 q = get_highlight_selection(self, start=start, stop=stop, clip=clip)
5850 else:
5851 q = CheckMarkerArg(quads)
5852 return self._add_text_marker(q, PDF_ANNOT_UNDERLINE)
5853
5854
5855 def add_squiggly_annot(self, quads=None, start=None,
5856 stop=None, clip=None) -> "struct Annot *":
5857 """Add a 'Squiggly' annotation."""
5858 if quads is None:
5859 q = get_highlight_selection(self, start=start, stop=stop, clip=clip)
5860 else:
5861 q = CheckMarkerArg(quads)
5862 return self._add_text_marker(q, PDF_ANNOT_SQUIGGLY)
5863
5864
5865 def add_highlight_annot(self, quads=None, start=None,
5866 stop=None, clip=None) -> "struct Annot *":
5867 """Add a 'Highlight' annotation."""
5868 if quads is None:
5869 q = get_highlight_selection(self, start=start, stop=stop, clip=clip)
5870 else:
5871 q = CheckMarkerArg(quads)
5872 return self._add_text_marker(q, PDF_ANNOT_HIGHLIGHT)
5873
5874
5875 def add_rect_annot(self, rect: rect_like) -> "struct Annot *":
5876 """Add a 'Square' (rectangle) annotation."""
5877 old_rotation = annot_preprocess(self)
5878 try:
5879 annot = self._add_square_or_circle(rect, PDF_ANNOT_SQUARE)
5880 finally:
5881 if old_rotation != 0:
5882 self.set_rotation(old_rotation)
5883 annot_postprocess(self, annot)
5884 return annot
5885
5886
5887 def add_circle_annot(self, rect: rect_like) -> "struct Annot *":
5888 """Add a 'Circle' (ellipse, oval) annotation."""
5889 old_rotation = annot_preprocess(self)
5890 try:
5891 annot = self._add_square_or_circle(rect, PDF_ANNOT_CIRCLE)
5892 finally:
5893 if old_rotation != 0:
5894 self.set_rotation(old_rotation)
5895 annot_postprocess(self, annot)
5896 return annot
5897
5898
5899 def add_text_annot(self, point: point_like, text: str, icon: str ="Note") -> "struct Annot *":
5900 """Add a 'Text' (sticky note) annotation."""
5901 old_rotation = annot_preprocess(self)
5902 try:
5903 annot = self._add_text_annot(point, text, icon=icon)
5904 finally:
5905 if old_rotation != 0:
5906 self.set_rotation(old_rotation)
5907 annot_postprocess(self, annot)
5908 return annot
5909
5910
5911 def add_line_annot(self, p1: point_like, p2: point_like) -> "struct Annot *":
5912 """Add a 'Line' annotation."""
5913 old_rotation = annot_preprocess(self)
5914 try:
5915 annot = self._add_line_annot(p1, p2)
5916 finally:
5917 if old_rotation != 0:
5918 self.set_rotation(old_rotation)
5919 annot_postprocess(self, annot)
5920 return annot
5921
5922
5923 def add_polyline_annot(self, points: list) -> "struct Annot *":
5924 """Add a 'PolyLine' annotation."""
5925 old_rotation = annot_preprocess(self)
5926 try:
5927 annot = self._add_multiline(points, PDF_ANNOT_POLY_LINE)
5928 finally:
5929 if old_rotation != 0:
5930 self.set_rotation(old_rotation)
5931 annot_postprocess(self, annot)
5932 return annot
5933
5934
5935 def add_polygon_annot(self, points: list) -> "struct Annot *":
5936 """Add a 'Polygon' annotation."""
5937 old_rotation = annot_preprocess(self)
5938 try:
5939 annot = self._add_multiline(points, PDF_ANNOT_POLYGON)
5940 finally:
5941 if old_rotation != 0:
5942 self.set_rotation(old_rotation)
5943 annot_postprocess(self, annot)
5944 return annot
5945
5946
5947 def add_stamp_annot(self, rect: rect_like, stamp: int =0) -> "struct Annot *":
5948 """Add a ('rubber') 'Stamp' annotation."""
5949 old_rotation = annot_preprocess(self)
5950 try:
5951 annot = self._add_stamp_annot(rect, stamp)
5952 finally:
5953 if old_rotation != 0:
5954 self.set_rotation(old_rotation)
5955 annot_postprocess(self, annot)
5956 return annot
5957
5958
5959 def add_ink_annot(self, handwriting: list) -> "struct Annot *":
5960 """Add a 'Ink' ('handwriting') annotation.
5961
5962 The argument must be a list of lists of point_likes.
5963 """
5964 old_rotation = annot_preprocess(self)
5965 try:
5966 annot = self._add_ink_annot(handwriting)
5967 finally:
5968 if old_rotation != 0:
5969 self.set_rotation(old_rotation)
5970 annot_postprocess(self, annot)
5971 return annot
5972
5973
5974 def add_file_annot(self, point: point_like,
5975 buffer: ByteString,
5976 filename: str,
5977 ufilename: OptStr =None,
5978 desc: OptStr =None,
5979 icon: OptStr =None) -> "struct Annot *":
5980 """Add a 'FileAttachment' annotation."""
5981
5982 old_rotation = annot_preprocess(self)
5983 try:
5984 annot = self._add_file_annot(point,
5985 buffer,
5986 filename,
5987 ufilename=ufilename,
5988 desc=desc,
5989 icon=icon)
5990 finally:
5991 if old_rotation != 0:
5992 self.set_rotation(old_rotation)
5993 annot_postprocess(self, annot)
5994 return annot
5995
5996
5997 def add_freetext_annot(self, rect: rect_like, text: str, fontsize: float =11,
5998 fontname: OptStr =None, border_color: OptSeq =None,
5999 text_color: OptSeq =None,
6000 fill_color: OptSeq =None, align: int =0, rotate: int =0) -> "struct Annot *":
6001 """Add a 'FreeText' annotation."""
6002
6003 old_rotation = annot_preprocess(self)
6004 try:
6005 annot = self._add_freetext_annot(rect, text, fontsize=fontsize,
6006 fontname=fontname, border_color=border_color,text_color=text_color,
6007 fill_color=fill_color, align=align, rotate=rotate)
6008 finally:
6009 if old_rotation != 0:
6010 self.set_rotation(old_rotation)
6011 annot_postprocess(self, annot)
6012 return annot
6013
6014
6015 def add_redact_annot(self, quad, text: OptStr =None, fontname: OptStr =None,
6016 fontsize: float =11, align: int =0, fill: OptSeq =None, text_color: OptSeq =None,
6017 cross_out: bool =True) -> "struct Annot *":
6018 """Add a 'Redact' annotation."""
6019 da_str = None
6020 if text:
6021 CheckColor(fill)
6022 CheckColor(text_color)
6023 if not fontname:
6024 fontname = "Helv"
6025 if not fontsize:
6026 fontsize = 11
6027 if not text_color:
6028 text_color = (0, 0, 0)
6029 if hasattr(text_color, "__float__"):
6030 text_color = (text_color, text_color, text_color)
6031 if len(text_color) > 3:
6032 text_color = text_color[:3]
6033 fmt = "{:g} {:g} {:g} rg /{f:s} {s:g} Tf"
6034 da_str = fmt.format(*text_color, f=fontname, s=fontsize)
6035 if fill is None:
6036 fill = (1, 1, 1)
6037 if fill:
6038 if hasattr(fill, "__float__"):
6039 fill = (fill, fill, fill)
6040 if len(fill) > 3:
6041 fill = fill[:3]
6042
6043 old_rotation = annot_preprocess(self)
6044 try:
6045 annot = self._add_redact_annot(quad, text=text, da_str=da_str,
6046 align=align, fill=fill)
6047 finally:
6048 if old_rotation != 0:
6049 self.set_rotation(old_rotation)
6050 annot_postprocess(self, annot)
6051 #-------------------------------------------------------------
6052 # change appearance to show a crossed-out rectangle
6053 #-------------------------------------------------------------
6054 if cross_out:
6055 ap_tab = annot._getAP().splitlines()[:-1] # get the 4 commands only
6056 _, LL, LR, UR, UL = ap_tab
6057 ap_tab.append(LR)
6058 ap_tab.append(LL)
6059 ap_tab.append(UR)
6060 ap_tab.append(LL)
6061 ap_tab.append(UL)
6062 ap_tab.append(b"S")
6063 ap = b"\n".join(ap_tab)
6064 annot._setAP(ap, 0)
6065 return annot
6066 %}
6067
6068
6069 //----------------------------------------------------------------
6070 // page load annot by name or xref
6071 //----------------------------------------------------------------
6072 FITZEXCEPTION(_load_annot, !result)
6073 struct Annot *
6074 _load_annot(char *name, int xref)
6075 {
6076 pdf_annot *annot = NULL;
6077 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6078 fz_try(gctx) {
6079 ASSERT_PDF(page);
6080 if (xref == 0)
6081 annot = JM_get_annot_by_name(gctx, page, name);
6082 else
6083 annot = JM_get_annot_by_xref(gctx, page, xref);
6084 }
6085 fz_catch(gctx) {
6086 return NULL;
6087 }
6088 return (struct Annot *) annot;
6089 }
6090
6091
6092 //----------------------------------------------------------------
6093 // page load widget by xref
6094 //----------------------------------------------------------------
6095 FITZEXCEPTION(load_widget, !result)
6096 %pythonprepend load_widget %{
6097 """Load a widget by its xref."""
6098 CheckParent(self)
6099 %}
6100 %pythonappend load_widget %{
6101 if not val:
6102 return val
6103 val.thisown = True
6104 val.parent = weakref.proxy(self)
6105 self._annot_refs[id(val)] = val
6106 widget = Widget()
6107 TOOLS._fill_widget(val, widget)
6108 val = widget
6109 %}
6110 struct Annot *
6111 load_widget(int xref)
6112 {
6113 pdf_annot *annot = NULL;
6114 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6115 fz_try(gctx) {
6116 ASSERT_PDF(page);
6117 annot = JM_get_widget_by_xref(gctx, page, xref);
6118 }
6119 fz_catch(gctx) {
6120 return NULL;
6121 }
6122 return (struct Annot *) annot;
6123 }
6124
6125
6126 //----------------------------------------------------------------
6127 // page list Resource/Properties
6128 //----------------------------------------------------------------
6129 FITZEXCEPTION(_get_resource_properties, !result)
6130 PyObject *
6131 _get_resource_properties()
6132 {
6133 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6134 PyObject *rc;
6135 fz_try(gctx) {
6136 ASSERT_PDF(page);
6137 rc = JM_get_resource_properties(gctx, page->obj);
6138 }
6139 fz_catch(gctx) {
6140 return NULL;
6141 }
6142 return rc;
6143 }
6144
6145
6146 //----------------------------------------------------------------
6147 // page list Resource/Properties
6148 //----------------------------------------------------------------
6149 FITZEXCEPTION(_set_resource_property, !result)
6150 PyObject *
6151 _set_resource_property(char *name, int xref)
6152 {
6153 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6154 fz_try(gctx) {
6155 ASSERT_PDF(page);
6156 JM_set_resource_property(gctx, page->obj, name, xref);
6157 }
6158 fz_catch(gctx) {
6159 return NULL;
6160 }
6161 Py_RETURN_NONE;
6162 }
6163
6164 %pythoncode %{
6165 def _get_optional_content(self, oc: OptInt) -> OptStr:
6166 if oc == None or oc == 0:
6167 return None
6168 doc = self.parent
6169 check = doc.xref_object(oc, compressed=True)
6170 if not ("/Type/OCG" in check or "/Type/OCMD" in check):
6171 raise ValueError("bad optional content: 'oc'")
6172 props = {}
6173 for p, x in self._get_resource_properties():
6174 props[x] = p
6175 if oc in props.keys():
6176 return props[oc]
6177 i = 0
6178 mc = "MC%i" % i
6179 while mc in props.values():
6180 i += 1
6181 mc = "MC%i" % i
6182 self._set_resource_property(mc, oc)
6183 return mc
6184
6185 def get_oc_items(self) -> list:
6186 """Get OCGs and OCMDs used in the page's contents.
6187
6188 Returns:
6189 List of items (name, xref, type), where type is one of "ocg" / "ocmd",
6190 and name is the property name.
6191 """
6192 rc = []
6193 for pname, xref in self._get_resource_properties():
6194 text = self.parent.xref_object(xref, compressed=True)
6195 if "/Type/OCG" in text:
6196 octype = "ocg"
6197 elif "/Type/OCMD" in text:
6198 octype = "ocmd"
6199 else:
6200 continue
6201 rc.append((pname, xref, octype))
6202 return rc
6203 %}
6204
6205 //----------------------------------------------------------------
6206 // page get list of annot names
6207 //----------------------------------------------------------------
6208 PARENTCHECK(annot_names, """List of names of annotations, fields and links.""")
6209 PyObject *annot_names()
6210 {
6211 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6212
6213 if (!page) {
6214 PyObject *rc = PyList_New(0);
6215 return rc;
6216 }
6217 return JM_get_annot_id_list(gctx, page);
6218 }
6219
6220
6221 //----------------------------------------------------------------
6222 // page retrieve list of annotation xrefs
6223 //----------------------------------------------------------------
6224 PARENTCHECK(annot_xrefs,"""List of xref numbers of annotations, fields and links.""")
6225 PyObject *annot_xrefs()
6226 {
6227 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6228 if (!page) {
6229 PyObject *rc = PyList_New(0);
6230 return rc;
6231 }
6232 return JM_get_annot_xref_list(gctx, page->obj);
6233 }
6234
6235
6236 %pythoncode %{
6237 def load_annot(self, ident: typing.Union[str, int]) -> "struct Annot *":
6238 """Load an annot by name (/NM key) or xref.
6239
6240 Args:
6241 ident: identifier, either name (str) or xref (int).
6242 """
6243
6244 CheckParent(self)
6245 if type(ident) is str:
6246 xref = 0
6247 name = ident
6248 elif type(ident) is int:
6249 xref = ident
6250 name = None
6251 else:
6252 raise ValueError("identifier must be string or integer")
6253 val = self._load_annot(name, xref)
6254 if not val:
6255 return val
6256 val.thisown = True
6257 val.parent = weakref.proxy(self)
6258 self._annot_refs[id(val)] = val
6259 return val
6260
6261
6262 #---------------------------------------------------------------------
6263 # page addWidget
6264 #---------------------------------------------------------------------
6265 def add_widget(self, widget: Widget) -> "struct Annot *":
6266 """Add a 'Widget' (form field)."""
6267 CheckParent(self)
6268 doc = self.parent
6269 if not doc.is_pdf:
6270 raise ValueError("is no PDF")
6271 widget._validate()
6272 annot = self._addWidget(widget.field_type, widget.field_name)
6273 if not annot:
6274 return None
6275 annot.thisown = True
6276 annot.parent = weakref.proxy(self) # owning page object
6277 self._annot_refs[id(annot)] = annot
6278 widget.parent = annot.parent
6279 widget._annot = annot
6280 widget.update()
6281 return annot
6282 %}
6283
6284 FITZEXCEPTION(_addWidget, !result)
6285 struct Annot *_addWidget(int field_type, char *field_name)
6286 {
6287 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6288 pdf_document *pdf = page->doc;
6289 pdf_annot *annot = NULL;
6290 fz_var(annot);
6291 fz_try(gctx) {
6292 annot = JM_create_widget(gctx, pdf, page, field_type, field_name);
6293 if (!annot) {
6294 RAISEPY(gctx, "cannot create widget", PyExc_RuntimeError);
6295 }
6296 JM_add_annot_id(gctx, annot, "W");
6297 }
6298 fz_catch(gctx) {
6299 return NULL;
6300 }
6301 return (struct Annot *) annot;
6302 }
6303
6304 //----------------------------------------------------------------
6305 // Page.get_displaylist
6306 //----------------------------------------------------------------
6307 FITZEXCEPTION(get_displaylist, !result)
6308 %pythonprepend get_displaylist %{
6309 """Make a DisplayList from the page for Pixmap generation.
6310
6311 Include (default) or exclude annotations."""
6312
6313 CheckParent(self)
6314 %}
6315 %pythonappend get_displaylist %{val.thisown = True%}
6316 struct DisplayList *get_displaylist(int annots=1)
6317 {
6318 fz_display_list *dl = NULL;
6319 fz_try(gctx) {
6320 if (annots) {
6321 dl = fz_new_display_list_from_page(gctx, (fz_page *) $self);
6322 } else {
6323 dl = fz_new_display_list_from_page_contents(gctx, (fz_page *) $self);
6324 }
6325 }
6326 fz_catch(gctx) {
6327 return NULL;
6328 }
6329 return (struct DisplayList *) dl;
6330 }
6331
6332
6333 //----------------------------------------------------------------
6334 // Page.get_drawings
6335 //----------------------------------------------------------------
6336 %pythoncode %{
6337 def get_drawings(self, extended: bool = False) -> list:
6338 """Retrieve vector graphics. The extended version includes clips.
6339
6340 Note:
6341 For greater comfort, this method converts point-like, rect-like, quad-like
6342 tuples of the C version to respective Point / Rect / Quad objects.
6343 It also adds default items that are missing in original path types.
6344 """
6345 allkeys = (
6346 "closePath", "fill", "color", "width", "lineCap",
6347 "lineJoin", "dashes", "stroke_opacity", "fill_opacity", "even_odd",
6348 )
6349 val = self.get_cdrawings(extended=extended)
6350 for i in range(len(val)):
6351 npath = val[i]
6352 if not npath["type"].startswith("clip"):
6353 npath["rect"] = Rect(npath["rect"])
6354 else:
6355 npath["scissor"] = Rect(npath["scissor"])
6356 if npath["type"]!="group":
6357 items = npath["items"]
6358 newitems = []
6359 for item in items:
6360 cmd = item[0]
6361 rest = item[1:]
6362 if cmd == "re":
6363 item = ("re", Rect(rest[0]).normalize(), rest[1])
6364 elif cmd == "qu":
6365 item = ("qu", Quad(rest[0]))
6366 else:
6367 item = tuple([cmd] + [Point(i) for i in rest])
6368 newitems.append(item)
6369 npath["items"] = newitems
6370 if npath["type"] in ("f", "s"):
6371 for k in allkeys:
6372 npath[k] = npath.get(k)
6373 val[i] = npath
6374 return val
6375
6376 class Drawpath(object):
6377 """Reflects a path dictionary from get_cdrawings()."""
6378 def __init__(self, **args):
6379 self.__dict__.update(args)
6380
6381 class Drawpathlist(object):
6382 """List of Path objects representing get_cdrawings() output."""
6383 def __init__(self):
6384 self.paths = []
6385 self.path_count = 0
6386 self.group_count = 0
6387 self.clip_count = 0
6388 self.fill_count = 0
6389 self.stroke_count = 0
6390 self.fillstroke_count = 0
6391
6392 def append(self, path):
6393 self.paths.append(path)
6394 self.path_count += 1
6395 if path.type == "clip":
6396 self.clip_count += 1
6397 elif path.type == "group":
6398 self.group_count += 1
6399 elif path.type == "f":
6400 self.fill_count += 1
6401 elif path.type == "s":
6402 self.stroke_count += 1
6403 elif path.type == "fs":
6404 self.fillstroke_count += 1
6405
6406 def clip_parents(self, i):
6407 """Return list of parent clip paths.
6408
6409 Args:
6410 i: (int) return parents of this path.
6411 Returns:
6412 List of the clip parents."""
6413 if i >= self.path_count:
6414 raise IndexError("bad path index")
6415 while i < 0:
6416 i += self.path_count
6417 lvl = self.paths[i].level
6418 clips = list( # clip paths before identified one
6419 reversed(
6420 [
6421 p
6422 for p in self.paths[:i]
6423 if p.type == "clip" and p.level < lvl
6424 ]
6425 )
6426 )
6427 if clips == []: # none found: empty list
6428 return []
6429 nclips = [clips[0]] # init return list
6430 for p in clips[1:]:
6431 if p.level >= nclips[-1].level:
6432 continue # only accept smaller clip levels
6433 nclips.append(p)
6434 return nclips
6435
6436 def group_parents(self, i):
6437 """Return list of parent group paths.
6438
6439 Args:
6440 i: (int) return parents of this path.
6441 Returns:
6442 List of the group parents."""
6443 if i >= self.path_count:
6444 raise IndexError("bad path index")
6445 while i < 0:
6446 i += self.path_count
6447 lvl = self.paths[i].level
6448 groups = list( # group paths before identified one
6449 reversed(
6450 [
6451 p
6452 for p in self.paths[:i]
6453 if p.type == "group" and p.level < lvl
6454 ]
6455 )
6456 )
6457 if groups == []: # none found: empty list
6458 return []
6459 ngroups = [groups[0]] # init return list
6460 for p in groups[1:]:
6461 if p.level >= ngroups[-1].level:
6462 continue # only accept smaller group levels
6463 ngroups.append(p)
6464 return ngroups
6465
6466 def __getitem__(self, item):
6467 return self.paths.__getitem__(item)
6468
6469 def __len__(self):
6470 return self.paths.__len__()
6471
6472
6473 def get_lineart(self) -> object:
6474 """Get page drawings paths.
6475
6476 Note:
6477 For greater comfort, this method converts point-like, rect-like, quad-like
6478 tuples of the C version to respective Point / Rect / Quad objects.
6479 Also adds default items that are missing in original path types.
6480 In contrast to get_drawings(), this output is an object.
6481 """
6482
6483 val = self.get_cdrawings(extended=True)
6484 paths = self.Drawpathlist()
6485 for path in val:
6486 npath = self.Drawpath(**path)
6487 if npath.type != "clip":
6488 npath.rect = Rect(path["rect"])
6489 else:
6490 npath.scissor = Rect(path["scissor"])
6491 if npath.type != "group":
6492 items = path["items"]
6493 newitems = []
6494 for item in items:
6495 cmd = item[0]
6496 rest = item[1:]
6497 if cmd == "re":
6498 item = ("re", Rect(rest[0]).normalize(), rest[1])
6499 elif cmd == "qu":
6500 item = ("qu", Quad(rest[0]))
6501 else:
6502 item = tuple([cmd] + [Point(i) for i in rest])
6503 newitems.append(item)
6504 npath.items = newitems
6505
6506 if npath.type == "f":
6507 npath.stroke_opacity = None
6508 npath.dashes = None
6509 npath.lineJoin = None
6510 npath.lineCap = None
6511 npath.color = None
6512 npath.width = None
6513
6514 paths.append(npath)
6515
6516 val = None
6517 return paths
6518 %}
6519
6520
6521 FITZEXCEPTION(get_cdrawings, !result)
6522 %pythonprepend get_cdrawings %{
6523 """Extract vector graphics ("line art") from the page."""
6524 CheckParent(self)
6525 old_rotation = self.rotation
6526 if old_rotation != 0:
6527 self.set_rotation(0)
6528 %}
6529 %pythonappend get_cdrawings %{
6530 if old_rotation != 0:
6531 self.set_rotation(old_rotation)
6532 %}
6533 PyObject *
6534 get_cdrawings(PyObject *extended=NULL, PyObject *callback=NULL, PyObject *method=NULL)
6535 {
6536 fz_page *page = (fz_page *) $self;
6537 fz_device *dev = NULL;
6538 PyObject *rc = NULL;
6539 int clips = PyObject_IsTrue(extended);
6540 fz_var(rc);
6541 fz_try(gctx) {
6542 fz_rect prect = fz_bound_page(gctx, page);
6543 trace_device_ptm = fz_make_matrix(1, 0, 0, -1, 0, prect.y1);
6544 if (PyCallable_Check(callback) || method != Py_None) {
6545 dev = JM_new_lineart_device(gctx, callback, clips, method);
6546 } else {
6547 rc = PyList_New(0);
6548 dev = JM_new_lineart_device(gctx, rc, clips, method);
6549 }
6550 fz_run_page(gctx, page, dev, fz_identity, NULL);
6551 fz_close_device(gctx, dev);
6552 }
6553 fz_always(gctx) {
6554 fz_drop_device(gctx, dev);
6555 }
6556 fz_catch(gctx) {
6557 Py_CLEAR(rc);
6558 return NULL;
6559 }
6560 if (PyCallable_Check(callback) || method != Py_None) {
6561 Py_RETURN_NONE;
6562 }
6563 return rc;
6564 }
6565
6566
6567 FITZEXCEPTION(get_bboxlog, !result)
6568 %pythonprepend get_bboxlog %{
6569 CheckParent(self)
6570 old_rotation = self.rotation
6571 if old_rotation != 0:
6572 self.set_rotation(0)
6573 %}
6574 %pythonappend get_bboxlog %{
6575 if old_rotation != 0:
6576 self.set_rotation(old_rotation)
6577 %}
6578 PyObject *
6579 get_bboxlog(PyObject *layers=NULL)
6580 {
6581 fz_page *page = (fz_page *) $self;
6582 fz_device *dev = NULL;
6583 PyObject *rc = PyList_New(0);
6584 int inc_layers = PyObject_IsTrue(layers);
6585 fz_try(gctx) {
6586 dev = JM_new_bbox_device(gctx, rc, inc_layers);
6587 fz_run_page(gctx, page, dev, fz_identity, NULL);
6588 fz_close_device(gctx, dev);
6589 }
6590 fz_always(gctx) {
6591 fz_drop_device(gctx, dev);
6592 }
6593 fz_catch(gctx) {
6594 Py_CLEAR(rc);
6595 return NULL;
6596 }
6597 return rc;
6598 }
6599
6600
6601 FITZEXCEPTION(get_texttrace, !result)
6602 %pythonprepend get_texttrace %{
6603 CheckParent(self)
6604 old_rotation = self.rotation
6605 if old_rotation != 0:
6606 self.set_rotation(0)
6607 %}
6608 %pythonappend get_texttrace %{
6609 if old_rotation != 0:
6610 self.set_rotation(old_rotation)
6611 %}
6612 PyObject *
6613 get_texttrace()
6614 {
6615 fz_page *page = (fz_page *) $self;
6616 fz_device *dev = NULL;
6617 PyObject *rc = PyList_New(0);
6618 fz_try(gctx) {
6619 dev = JM_new_texttrace_device(gctx, rc);
6620 fz_rect prect = fz_bound_page(gctx, page);
6621 trace_device_rot = fz_identity;
6622 trace_device_ptm = fz_make_matrix(1, 0, 0, -1, 0, prect.y1);
6623 fz_run_page(gctx, page, dev, fz_identity, NULL);
6624 fz_close_device(gctx, dev);
6625 }
6626 fz_always(gctx) {
6627 fz_drop_device(gctx, dev);
6628 }
6629 fz_catch(gctx) {
6630 Py_CLEAR(rc);
6631 return NULL;
6632 }
6633 return rc;
6634 }
6635
6636
6637 //----------------------------------------------------------------
6638 // Page apply redactions
6639 //----------------------------------------------------------------
6640 FITZEXCEPTION(_apply_redactions, !result)
6641 PyObject *_apply_redactions(int images=PDF_REDACT_IMAGE_PIXELS)
6642 {
6643 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6644 int success = 0;
6645 pdf_redact_options opts = {0};
6646 opts.black_boxes = 0; // no black boxes
6647 opts.image_method = images; // how to treat images
6648 fz_try(gctx) {
6649 ASSERT_PDF(page);
6650 success = pdf_redact_page(gctx, page->doc, page, &opts);
6651 }
6652 fz_catch(gctx) {
6653 return NULL;
6654 }
6655 return JM_BOOL(success);
6656 }
6657
6658
6659 //----------------------------------------------------------------
6660 // Page._makePixmap
6661 //----------------------------------------------------------------
6662 FITZEXCEPTION(_makePixmap, !result)
6663 struct Pixmap *
6664 _makePixmap(struct Document *doc,
6665 PyObject *ctm,
6666 struct Colorspace *cs,
6667 int alpha=0,
6668 int annots=1,
6669 PyObject *clip=NULL)
6670 {
6671 fz_pixmap *pix = NULL;
6672 fz_try(gctx) {
6673 pix = JM_pixmap_from_page(gctx, (fz_document *) doc, (fz_page *) $self, ctm, (fz_colorspace *) cs, alpha, annots, clip);
6674 }
6675 fz_catch(gctx) {
6676 return NULL;
6677 }
6678 return (struct Pixmap *) pix;
6679 }
6680
6681
6682 //----------------------------------------------------------------
6683 // Page.set_mediabox
6684 //----------------------------------------------------------------
6685 FITZEXCEPTION(set_mediabox, !result)
6686 PARENTCHECK(set_mediabox, """Set the MediaBox.""")
6687 PyObject *set_mediabox(PyObject *rect)
6688 {
6689 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6690 fz_try(gctx) {
6691 ASSERT_PDF(page);
6692 fz_rect mediabox = JM_rect_from_py(rect);
6693 if (fz_is_empty_rect(mediabox) ||
6694 fz_is_infinite_rect(mediabox)) {
6695 RAISEPY(gctx, MSG_BAD_RECT, PyExc_ValueError);
6696 }
6697 pdf_dict_put_rect(gctx, page->obj, PDF_NAME(MediaBox), mediabox);
6698 pdf_dict_del(gctx, page->obj, PDF_NAME(CropBox));
6699 pdf_dict_del(gctx, page->obj, PDF_NAME(ArtBox));
6700 pdf_dict_del(gctx, page->obj, PDF_NAME(BleedBox));
6701 pdf_dict_del(gctx, page->obj, PDF_NAME(TrimBox));
6702 }
6703 fz_catch(gctx) {
6704 return NULL;
6705 }
6706 Py_RETURN_NONE;
6707 }
6708
6709
6710 //----------------------------------------------------------------
6711 // Page.load_links()
6712 //----------------------------------------------------------------
6713 PARENTCHECK(load_links, """Get first Link.""")
6714 %pythonappend load_links %{
6715 if val:
6716 val.thisown = True
6717 val.parent = weakref.proxy(self) # owning page object
6718 self._annot_refs[id(val)] = val
6719 if self.parent.is_pdf:
6720 link_id = [x for x in self.annot_xrefs() if x[1] == PDF_ANNOT_LINK][0]
6721 val.xref = link_id[0]
6722 val.id = link_id[2]
6723 else:
6724 val.xref = 0
6725 val.id = ""
6726 %}
6727 struct Link *load_links()
6728 {
6729 fz_link *l = NULL;
6730 fz_try(gctx) {
6731 l = fz_load_links(gctx, (fz_page *) $self);
6732 }
6733 fz_catch(gctx) {
6734 return NULL;
6735 }
6736 return (struct Link *) l;
6737 }
6738 %pythoncode %{first_link = property(load_links, doc="First link on page")%}
6739
6740 //----------------------------------------------------------------
6741 // Page.first_annot
6742 //----------------------------------------------------------------
6743 PARENTCHECK(first_annot, """First annotation.""")
6744 %pythonappend first_annot %{
6745 if val:
6746 val.thisown = True
6747 val.parent = weakref.proxy(self) # owning page object
6748 self._annot_refs[id(val)] = val
6749 %}
6750 %pythoncode %{@property%}
6751 struct Annot *first_annot()
6752 {
6753 pdf_annot *annot = NULL;
6754 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6755 if (page)
6756 {
6757 annot = pdf_first_annot(gctx, page);
6758 if (annot) pdf_keep_annot(gctx, annot);
6759 }
6760 return (struct Annot *) annot;
6761 }
6762
6763 //----------------------------------------------------------------
6764 // first_widget
6765 //----------------------------------------------------------------
6766 %pythoncode %{@property%}
6767 PARENTCHECK(first_widget, """First widget/field.""")
6768 %pythonappend first_widget %{
6769 if val:
6770 val.thisown = True
6771 val.parent = weakref.proxy(self) # owning page object
6772 self._annot_refs[id(val)] = val
6773 widget = Widget()
6774 TOOLS._fill_widget(val, widget)
6775 val = widget
6776 %}
6777 struct Annot *first_widget()
6778 {
6779 pdf_annot *annot = NULL;
6780 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6781 if (page) {
6782 annot = pdf_first_widget(gctx, page);
6783 if (annot) pdf_keep_annot(gctx, annot);
6784 }
6785 return (struct Annot *) annot;
6786 }
6787
6788
6789 //----------------------------------------------------------------
6790 // Page.delete_link() - delete link
6791 //----------------------------------------------------------------
6792 PARENTCHECK(delete_link, """Delete a Link.""")
6793 %pythonappend delete_link %{
6794 if linkdict["xref"] == 0: return
6795 try:
6796 linkid = linkdict["id"]
6797 linkobj = self._annot_refs[linkid]
6798 linkobj._erase()
6799 except:
6800 pass
6801 %}
6802 void delete_link(PyObject *linkdict)
6803 {
6804 if (!PyDict_Check(linkdict)) return; // have no dictionary
6805 fz_try(gctx) {
6806 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6807 if (!page) goto finished; // have no PDF
6808 int xref = (int) PyInt_AsLong(PyDict_GetItem(linkdict, dictkey_xref));
6809 if (xref < 1) goto finished; // invalid xref
6810 pdf_obj *annots = pdf_dict_get(gctx, page->obj, PDF_NAME(Annots));
6811 if (!annots) goto finished; // have no annotations
6812 int len = pdf_array_len(gctx, annots);
6813 if (len == 0) goto finished;
6814 int i, oxref = 0;
6815
6816 for (i = 0; i < len; i++) {
6817 oxref = pdf_to_num(gctx, pdf_array_get(gctx, annots, i));
6818 if (xref == oxref) break; // found xref in annotations
6819 }
6820
6821 if (xref != oxref) goto finished; // xref not in annotations
6822 pdf_array_delete(gctx, annots, i); // delete entry in annotations
6823 pdf_delete_object(gctx, page->doc, xref); // delete link obj
6824 pdf_dict_put(gctx, page->obj, PDF_NAME(Annots), annots);
6825 JM_refresh_links(gctx, page);
6826 finished:;
6827
6828 }
6829 fz_catch(gctx) {;}
6830 }
6831
6832 //----------------------------------------------------------------
6833 // Page.delete_annot() - delete annotation and return the next one
6834 //----------------------------------------------------------------
6835 %pythonprepend delete_annot %{
6836 """Delete annot and return next one."""
6837 CheckParent(self)
6838 CheckParent(annot)%}
6839
6840 %pythonappend delete_annot %{
6841 if val:
6842 val.thisown = True
6843 val.parent = weakref.proxy(self) # owning page object
6844 val.parent._annot_refs[id(val)] = val
6845 %}
6846
6847 struct Annot *delete_annot(struct Annot *annot)
6848 {
6849 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6850 pdf_annot *irt_annot = NULL;
6851 while (1) {
6852 // first loop through all /IRT annots and remove them
6853 irt_annot = JM_find_annot_irt(gctx, (pdf_annot *) annot);
6854 if (!irt_annot) // no more there
6855 break;
6856 pdf_delete_annot(gctx, page, irt_annot);
6857 }
6858 pdf_annot *nextannot = pdf_next_annot(gctx, (pdf_annot *) annot); // store next
6859 pdf_delete_annot(gctx, page, (pdf_annot *) annot);
6860 if (nextannot) {
6861 nextannot = pdf_keep_annot(gctx, nextannot);
6862 }
6863 return (struct Annot *) nextannot;
6864 }
6865
6866
6867 //----------------------------------------------------------------
6868 // mediabox: get the /MediaBox (PDF only)
6869 //----------------------------------------------------------------
6870 %pythoncode %{@property%}
6871 PARENTCHECK(mediabox, """The MediaBox.""")
6872 %pythonappend mediabox %{val = Rect(JM_TUPLE3(val))%}
6873 PyObject *mediabox()
6874 {
6875 fz_rect rect = fz_infinite_rect;
6876 fz_try(gctx) {
6877 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6878 if (!page) {
6879 rect = fz_bound_page(gctx, (fz_page *) $self);
6880 } else {
6881 rect = JM_mediabox(gctx, page->obj);
6882 }
6883 }
6884 fz_catch(gctx) {;}
6885 return JM_py_from_rect(rect);
6886 }
6887
6888
6889 //----------------------------------------------------------------
6890 // cropbox: get the /CropBox (PDF only)
6891 //----------------------------------------------------------------
6892 %pythoncode %{@property%}
6893 PARENTCHECK(cropbox, """The CropBox.""")
6894 %pythonappend cropbox %{val = Rect(JM_TUPLE3(val))%}
6895 PyObject *cropbox()
6896 {
6897 fz_rect rect = fz_infinite_rect;
6898 fz_try(gctx) {
6899 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6900 if (!page) {
6901 rect = fz_bound_page(gctx, (fz_page *) $self);
6902 } else {
6903 rect = JM_cropbox(gctx, page->obj);
6904 }
6905 }
6906 fz_catch(gctx) {;}
6907 return JM_py_from_rect(rect);
6908 }
6909
6910
6911 PyObject *_other_box(const char *boxtype)
6912 {
6913 fz_rect rect = fz_infinite_rect;
6914 fz_try(gctx) {
6915 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
6916 if (page) {
6917 pdf_obj *obj = pdf_dict_gets(gctx, page->obj, boxtype);
6918 if (pdf_is_array(gctx, obj)) {
6919 rect = pdf_to_rect(gctx, obj);
6920 }
6921 }
6922 }
6923 fz_catch(gctx) {;}
6924 if (fz_is_infinite_rect(rect)) {
6925 Py_RETURN_NONE;
6926 }
6927 return JM_py_from_rect(rect);
6928 }
6929
6930
6931 //----------------------------------------------------------------
6932 // CropBox position: x0, y0 of /CropBox
6933 //----------------------------------------------------------------
6934 %pythoncode %{
6935 @property
6936 def cropbox_position(self):
6937 return self.cropbox.tl
6938
6939 @property
6940 def artbox(self):
6941 """The ArtBox"""
6942 rect = self._other_box("ArtBox")
6943 if rect == None:
6944 return self.cropbox
6945 mb = self.mediabox
6946 return Rect(rect[0], mb.y1 - rect[3], rect[2], mb.y1 - rect[1])
6947
6948 @property
6949 def trimbox(self):
6950 """The TrimBox"""
6951 rect = self._other_box("TrimBox")
6952 if rect == None:
6953 return self.cropbox
6954 mb = self.mediabox
6955 return Rect(rect[0], mb.y1 - rect[3], rect[2], mb.y1 - rect[1])
6956
6957 @property
6958 def bleedbox(self):
6959 """The BleedBox"""
6960 rect = self._other_box("BleedBox")
6961 if rect == None:
6962 return self.cropbox
6963 mb = self.mediabox
6964 return Rect(rect[0], mb.y1 - rect[3], rect[2], mb.y1 - rect[1])
6965
6966 def _set_pagebox(self, boxtype, rect):
6967 doc = self.parent
6968 if doc == None:
6969 raise ValueError("orphaned object: parent is None")
6970
6971 if not doc.is_pdf:
6972 raise ValueError("is no PDF")
6973
6974 valid_boxes = ("CropBox", "BleedBox", "TrimBox", "ArtBox")
6975
6976 if boxtype not in valid_boxes:
6977 raise ValueError("bad boxtype")
6978
6979 rect = Rect(rect)
6980 mb = self.mediabox
6981 rect = Rect(rect[0], mb.y1 - rect[3], rect[2], mb.y1 - rect[1])
6982 if not (mb.x0 <= rect.x0 < rect.x1 <= mb.x1 and mb.y0 <= rect.y0 < rect.y1 <= mb.y1):
6983 raise ValueError(f"{boxtype} not in MediaBox")
6984
6985 doc.xref_set_key(self.xref, boxtype, "[%g %g %g %g]" % tuple(rect))
6986
6987
6988 def set_cropbox(self, rect):
6989 """Set the CropBox. Will also change Page.rect."""
6990 return self._set_pagebox("CropBox", rect)
6991
6992 def set_artbox(self, rect):
6993 """Set the ArtBox."""
6994 return self._set_pagebox("ArtBox", rect)
6995
6996 def set_bleedbox(self, rect):
6997 """Set the BleedBox."""
6998 return self._set_pagebox("BleedBox", rect)
6999
7000 def set_trimbox(self, rect):
7001 """Set the TrimBox."""
7002 return self._set_pagebox("TrimBox", rect)
7003 %}
7004
7005
7006 //----------------------------------------------------------------
7007 // rotation - return page rotation
7008 //----------------------------------------------------------------
7009 PARENTCHECK(rotation, """Page rotation.""")
7010 %pythoncode %{@property%}
7011 int rotation()
7012 {
7013 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
7014 if (!page) return 0;
7015 return JM_page_rotation(gctx, page);
7016 }
7017
7018 /*********************************************************************/
7019 // set_rotation() - set page rotation
7020 /*********************************************************************/
7021 FITZEXCEPTION(set_rotation, !result)
7022 PARENTCHECK(set_rotation, """Set page rotation.""")
7023 PyObject *set_rotation(int rotation)
7024 {
7025 fz_try(gctx) {
7026 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
7027 ASSERT_PDF(page);
7028 int rot = JM_norm_rotation(rotation);
7029 pdf_dict_put_int(gctx, page->obj, PDF_NAME(Rotate), (int64_t) rot);
7030 }
7031 fz_catch(gctx) {
7032 return NULL;
7033 }
7034 Py_RETURN_NONE;
7035 }
7036
7037 /*********************************************************************/
7038 // Page._addAnnot_FromString
7039 // Add new links provided as an array of string object definitions.
7040 /*********************************************************************/
7041 FITZEXCEPTION(_addAnnot_FromString, !result)
7042 PARENTCHECK(_addAnnot_FromString, """Add links from list of object sources.""")
7043 PyObject *_addAnnot_FromString(PyObject *linklist)
7044 {
7045 pdf_obj *annots, *annot, *ind_obj;
7046 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
7047 PyObject *txtpy = NULL;
7048 char *text = NULL;
7049 Py_ssize_t lcount = PyTuple_Size(linklist); // link count
7050 if (lcount < 1) Py_RETURN_NONE;
7051 Py_ssize_t i = -1;
7052 fz_var(text);
7053
7054 // insert links from the provided sources
7055 fz_try(gctx) {
7056 ASSERT_PDF(page);
7057 if (!PyTuple_Check(linklist)) {
7058 RAISEPY(gctx, "bad 'linklist' argument", PyExc_ValueError);
7059 }
7060 if (!pdf_dict_get(gctx, page->obj, PDF_NAME(Annots))) {
7061 pdf_dict_put_array(gctx, page->obj, PDF_NAME(Annots), lcount);
7062 }
7063 annots = pdf_dict_get(gctx, page->obj, PDF_NAME(Annots));
7064 for (i = 0; i < lcount; i++) {
7065 fz_try(gctx) {
7066 for (; i < lcount; i++) {
7067 text = JM_StrAsChar(PyTuple_GET_ITEM(linklist, i));
7068 if (!text) {
7069 PySys_WriteStderr("skipping bad link / annot item %zi.\n", i);
7070 continue;
7071 }
7072 annot = pdf_add_object_drop(gctx, page->doc,
7073 JM_pdf_obj_from_str(gctx, page->doc, text));
7074 ind_obj = pdf_new_indirect(gctx, page->doc, pdf_to_num(gctx, annot), 0);
7075 pdf_array_push_drop(gctx, annots, ind_obj);
7076 pdf_drop_obj(gctx, annot);
7077 }
7078 }
7079 fz_catch(gctx) {
7080 PySys_WriteStderr("skipping bad link / annot item %zi.\n", i);
7081 }
7082 }
7083 }
7084 fz_catch(gctx) {
7085 PyErr_Clear();
7086 return NULL;
7087 }
7088 Py_RETURN_NONE;
7089 }
7090
7091 //----------------------------------------------------------------
7092 // Page clean contents stream
7093 //----------------------------------------------------------------
7094 FITZEXCEPTION(clean_contents, !result)
7095 %pythonprepend clean_contents
7096 %{"""Clean page /Contents into one object."""
7097 CheckParent(self)
7098 if not sanitize and not self.is_wrapped:
7099 self.wrap_contents()%}
7100 PyObject *clean_contents(int sanitize=1)
7101 {
7102 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
7103 if (!page) {
7104 Py_RETURN_NONE;
7105 }
7106 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR >= 22
7107 pdf_filter_factory list[2] = { 0 };
7108 pdf_sanitize_filter_options sopts = { 0 };
7109 pdf_filter_options filter = {
7110 1, // recurse: true
7111 0, // instance forms
7112 0, // do not ascii-escape binary data
7113 0, // no_update
7114 NULL, // end_page_opaque
7115 NULL, // end page
7116 list, // filters
7117 };
7118 if (sanitize) {
7119 list[0].filter = pdf_new_sanitize_filter;
7120 list[0].options = &sopts;
7121 }
7122 #else
7123 pdf_filter_options filter = {
7124 NULL, // opaque
7125 NULL, // image filter
7126 NULL, // text filter
7127 NULL, // after text
7128 NULL, // end page
7129 1, // recurse: true
7130 1, // instance forms
7131 1, // sanitize plus filtering
7132 0 // do not ascii-escape binary data
7133 };
7134 filter.sanitize = sanitize;
7135 #endif
7136 fz_try(gctx) {
7137 pdf_filter_page_contents(gctx, page->doc, page, &filter);
7138 }
7139 fz_catch(gctx) {
7140 Py_RETURN_NONE;
7141 }
7142 Py_RETURN_NONE;
7143 }
7144
7145 //----------------------------------------------------------------
7146 // Show a PDF page
7147 //----------------------------------------------------------------
7148 FITZEXCEPTION(_show_pdf_page, !result)
7149 PyObject *_show_pdf_page(struct Page *fz_srcpage, int overlay=1, PyObject *matrix=NULL, int xref=0, int oc=0, PyObject *clip = NULL, struct Graftmap *graftmap = NULL, char *_imgname = NULL)
7150 {
7151 pdf_obj *xobj1=NULL, *xobj2=NULL, *resources;
7152 fz_buffer *res=NULL, *nres=NULL;
7153 fz_rect cropbox = JM_rect_from_py(clip);
7154 fz_matrix mat = JM_matrix_from_py(matrix);
7155 int rc_xref = xref;
7156 fz_var(xobj1);
7157 fz_var(xobj2);
7158 fz_try(gctx) {
7159 pdf_page *tpage = pdf_page_from_fz_page(gctx, (fz_page *) $self);
7160 pdf_obj *tpageref = tpage->obj;
7161 pdf_document *pdfout = tpage->doc; // target PDF
7162 ENSURE_OPERATION(gctx, pdfout);
7163 //-------------------------------------------------------------
7164 // convert the source page to a Form XObject
7165 //-------------------------------------------------------------
7166 xobj1 = JM_xobject_from_page(gctx, pdfout, (fz_page *) fz_srcpage,
7167 xref, (pdf_graft_map *) graftmap);
7168 if (!rc_xref) rc_xref = pdf_to_num(gctx, xobj1);
7169
7170 //-------------------------------------------------------------
7171 // create referencing XObject (controls display on target page)
7172 //-------------------------------------------------------------
7173 // fill reference to xobj1 into the /Resources
7174 //-------------------------------------------------------------
7175 pdf_obj *subres1 = pdf_new_dict(gctx, pdfout, 5);
7176 pdf_dict_puts(gctx, subres1, "fullpage", xobj1);
7177 pdf_obj *subres = pdf_new_dict(gctx, pdfout, 5);
7178 pdf_dict_put_drop(gctx, subres, PDF_NAME(XObject), subres1);
7179
7180 res = fz_new_buffer(gctx, 20);
7181 fz_append_string(gctx, res, "/fullpage Do");
7182
7183 xobj2 = pdf_new_xobject(gctx, pdfout, cropbox, mat, subres, res);
7184 if (oc > 0) {
7185 JM_add_oc_object(gctx, pdfout, pdf_resolve_indirect(gctx, xobj2), oc);
7186 }
7187 pdf_drop_obj(gctx, subres);
7188 fz_drop_buffer(gctx, res);
7189
7190 //-------------------------------------------------------------
7191 // update target page with xobj2:
7192 //-------------------------------------------------------------
7193 // 1. insert Xobject in Resources
7194 //-------------------------------------------------------------
7195 resources = pdf_dict_get_inheritable(gctx, tpageref, PDF_NAME(Resources));
7196 subres = pdf_dict_get(gctx, resources, PDF_NAME(XObject));
7197 if (!subres) {
7198 subres = pdf_dict_put_dict(gctx, resources, PDF_NAME(XObject), 5);
7199 }
7200
7201 pdf_dict_puts(gctx, subres, _imgname, xobj2);
7202
7203 //-------------------------------------------------------------
7204 // 2. make and insert new Contents object
7205 //-------------------------------------------------------------
7206 nres = fz_new_buffer(gctx, 50); // buffer for Do-command
7207 fz_append_string(gctx, nres, " q /"); // Do-command
7208 fz_append_string(gctx, nres, _imgname);
7209 fz_append_string(gctx, nres, " Do Q ");
7210
7211 JM_insert_contents(gctx, pdfout, tpageref, nres, overlay);
7212 fz_drop_buffer(gctx, nres);
7213 }
7214 fz_always(gctx) {
7215 pdf_drop_obj(gctx, xobj1);
7216 pdf_drop_obj(gctx, xobj2);
7217 }
7218 fz_catch(gctx) {
7219 return NULL;
7220 }
7221 return Py_BuildValue("i", rc_xref);
7222 }
7223
7224 //----------------------------------------------------------------
7225 // insert an image
7226 //----------------------------------------------------------------
7227 FITZEXCEPTION(_insert_image, !result)
7228 PyObject *
7229 _insert_image(char *filename=NULL,
7230 struct Pixmap *pixmap=NULL,
7231 PyObject *stream=NULL,
7232 PyObject *imask=NULL,
7233 PyObject *clip=NULL,
7234 int overlay=1,
7235 int rotate=0,
7236 int keep_proportion=1,
7237 int oc=0,
7238 int width=0,
7239 int height=0,
7240 int xref=0,
7241 int alpha=-1,
7242 const char *_imgname=NULL,
7243 PyObject *digests=NULL)
7244 {
7245 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
7246 pdf_document *pdf = page->doc;
7247 float w = width, h = height;
7248 fz_pixmap *pm = NULL;
7249 fz_pixmap *pix = NULL;
7250 fz_image *mask = NULL, *zimg = NULL, *image = NULL, *freethis = NULL;
7251 pdf_obj *resources, *xobject, *ref;
7252 fz_buffer *nres = NULL, *imgbuf = NULL, *maskbuf = NULL;
7253 fz_compressed_buffer *cbuf1 = NULL;
7254 int xres, yres, bpc, img_xref = xref, rc_digest = 0;
7255 unsigned char digest[16];
7256 PyObject *md5_py = NULL, *temp;
7257 const char *template = "\nq\n%g %g %g %g %g %g cm\n/%s Do\nQ\n";
7258
7259 fz_try(gctx) {
7260 if (xref > 0) {
7261 ref = pdf_new_indirect(gctx, pdf, xref, 0);
7262 w = pdf_to_int(gctx,
7263 pdf_dict_geta(gctx, ref,
7264 PDF_NAME(Width), PDF_NAME(W)));
7265 h = pdf_to_int(gctx,
7266 pdf_dict_geta(gctx, ref,
7267 PDF_NAME(Height), PDF_NAME(H)));
7268 if ((w + h) == 0) {
7269 RAISEPY(gctx, MSG_IS_NO_IMAGE, PyExc_ValueError);
7270 }
7271 goto have_xref;
7272 }
7273 if (EXISTS(stream)) {
7274 imgbuf = JM_BufferFromBytes(gctx, stream);
7275 goto have_stream;
7276 }
7277 if (filename) {
7278 imgbuf = fz_read_file(gctx, filename);
7279 goto have_stream;
7280 }
7281 // process pixmap ---------------------------------
7282 fz_pixmap *arg_pix = (fz_pixmap *) pixmap;
7283 w = arg_pix->w;
7284 h = arg_pix->h;
7285 fz_md5_pixmap(gctx, arg_pix, digest);
7286 md5_py = PyBytes_FromStringAndSize(digest, 16);
7287 temp = PyDict_GetItem(digests, md5_py);
7288 if (temp) {
7289 img_xref = (int) PyLong_AsLong(temp);
7290 ref = pdf_new_indirect(gctx, page->doc, img_xref, 0);
7291 goto have_xref;
7292 }
7293 if (arg_pix->alpha == 0) {
7294 image = fz_new_image_from_pixmap(gctx, arg_pix, NULL);
7295 } else {
7296 pm = fz_convert_pixmap(gctx, arg_pix, NULL, NULL, NULL,
7297 fz_default_color_params, 1);
7298 pm->alpha = 0;
7299 pm->colorspace = NULL;
7300 mask = fz_new_image_from_pixmap(gctx, pm, NULL);
7301 image = fz_new_image_from_pixmap(gctx, arg_pix, mask);
7302 }
7303 goto have_image;
7304
7305 // process stream ---------------------------------
7306 have_stream:;
7307 fz_md5 state;
7308 fz_md5_init(&state);
7309 fz_md5_update(&state, imgbuf->data, imgbuf->len);
7310 if (imask != Py_None) {
7311 maskbuf = JM_BufferFromBytes(gctx, imask);
7312 fz_md5_update(&state, maskbuf->data, maskbuf->len);
7313 }
7314 fz_md5_final(&state, digest);
7315 md5_py = PyBytes_FromStringAndSize(digest, 16);
7316 temp = PyDict_GetItem(digests, md5_py);
7317 if (temp) {
7318 img_xref = (int) PyLong_AsLong(temp);
7319 ref = pdf_new_indirect(gctx, page->doc, img_xref, 0);
7320 w = pdf_to_int(gctx,
7321 pdf_dict_geta(gctx, ref,
7322 PDF_NAME(Width), PDF_NAME(W)));
7323 h = pdf_to_int(gctx,
7324 pdf_dict_geta(gctx, ref,
7325 PDF_NAME(Height), PDF_NAME(H)));
7326 goto have_xref;
7327 }
7328 image = fz_new_image_from_buffer(gctx, imgbuf);
7329 w = image->w;
7330 h = image->h;
7331 if (imask == Py_None) {
7332 goto have_image;
7333 }
7334
7335 cbuf1 = fz_compressed_image_buffer(gctx, image);
7336 if (!cbuf1) {
7337 RAISEPY(gctx, "uncompressed image cannot have mask", PyExc_ValueError);
7338 }
7339 bpc = image->bpc;
7340 fz_colorspace *colorspace = image->colorspace;
7341 fz_image_resolution(image, &xres, &yres);
7342 mask = fz_new_image_from_buffer(gctx, maskbuf);
7343 zimg = fz_new_image_from_compressed_buffer(gctx, w, h,
7344 bpc, colorspace, xres, yres, 1, 0, NULL,
7345 NULL, cbuf1, mask);
7346 freethis = image;
7347 image = zimg;
7348 zimg = NULL;
7349 goto have_image;
7350
7351 have_image:;
7352 ref = pdf_add_image(gctx, pdf, image);
7353 if (oc) {
7354 JM_add_oc_object(gctx, pdf, ref, oc);
7355 }
7356 img_xref = pdf_to_num(gctx, ref);
7357 DICT_SETITEM_DROP(digests, md5_py, Py_BuildValue("i", img_xref));
7358 rc_digest = 1;
7359 have_xref:;
7360 resources = pdf_dict_get_inheritable(gctx, page->obj,
7361 PDF_NAME(Resources));
7362 if (!resources) {
7363 resources = pdf_dict_put_dict(gctx, page->obj,
7364 PDF_NAME(Resources), 2);
7365 }
7366 xobject = pdf_dict_get(gctx, resources, PDF_NAME(XObject));
7367 if (!xobject) {
7368 xobject = pdf_dict_put_dict(gctx, resources,
7369 PDF_NAME(XObject), 2);
7370 }
7371 fz_matrix mat = calc_image_matrix(w, h, clip, rotate, keep_proportion);
7372 pdf_dict_puts_drop(gctx, xobject, _imgname, ref);
7373 nres = fz_new_buffer(gctx, 50);
7374 fz_append_printf(gctx, nres, template,
7375 mat.a, mat.b, mat.c, mat.d, mat.e, mat.f, _imgname);
7376 JM_insert_contents(gctx, pdf, page->obj, nres, overlay);
7377 }
7378 fz_always(gctx) {
7379 if (freethis) {
7380 fz_drop_image(gctx, freethis);
7381 } else {
7382 fz_drop_image(gctx, image);
7383 }
7384 fz_drop_image(gctx, mask);
7385 fz_drop_image(gctx, zimg);
7386 fz_drop_pixmap(gctx, pix);
7387 fz_drop_pixmap(gctx, pm);
7388 fz_drop_buffer(gctx, imgbuf);
7389 fz_drop_buffer(gctx, maskbuf);
7390 fz_drop_buffer(gctx, nres);
7391 }
7392 fz_catch(gctx) {
7393 return NULL;
7394 }
7395
7396 if (rc_digest) {
7397 return Py_BuildValue("iO", img_xref, digests);
7398 } else {
7399 return Py_BuildValue("iO", img_xref, Py_None);
7400 }
7401 }
7402
7403
7404 //----------------------------------------------------------------
7405 // Page.refresh()
7406 //----------------------------------------------------------------
7407 %pythoncode %{
7408 def refresh(self):
7409 doc = self.parent
7410 page = doc.reload_page(self)
7411 self = page
7412 %}
7413
7414
7415 //----------------------------------------------------------------
7416 // insert font
7417 //----------------------------------------------------------------
7418 %pythoncode
7419 %{
7420 def insert_font(self, fontname="helv", fontfile=None, fontbuffer=None,
7421 set_simple=False, wmode=0, encoding=0):
7422 doc = self.parent
7423 if doc is None:
7424 raise ValueError("orphaned object: parent is None")
7425 idx = 0
7426
7427 if fontname.startswith("/"):
7428 fontname = fontname[1:]
7429 inv_chars = INVALID_NAME_CHARS.intersection(fontname)
7430 if inv_chars != set():
7431 raise ValueError(f"bad fontname chars {inv_chars}")
7432
7433 font = CheckFont(self, fontname)
7434 if font is not None: # font already in font list of page
7435 xref = font[0] # this is the xref
7436 if CheckFontInfo(doc, xref): # also in our document font list?
7437 return xref # yes: we are done
7438 # need to build the doc FontInfo entry - done via get_char_widths
7439 doc.get_char_widths(xref)
7440 return xref
7441
7442 #--------------------------------------------------------------------------
7443 # the font is not present for this page
7444 #--------------------------------------------------------------------------
7445
7446 bfname = Base14_fontdict.get(fontname.lower(), None) # BaseFont if Base-14 font
7447
7448 serif = 0
7449 CJK_number = -1
7450 CJK_list_n = ["china-t", "china-s", "japan", "korea"]
7451 CJK_list_s = ["china-ts", "china-ss", "japan-s", "korea-s"]
7452
7453 try:
7454 CJK_number = CJK_list_n.index(fontname)
7455 serif = 0
7456 except:
7457 pass
7458
7459 if CJK_number < 0:
7460 try:
7461 CJK_number = CJK_list_s.index(fontname)
7462 serif = 1
7463 except:
7464 pass
7465
7466 if fontname.lower() in fitz_fontdescriptors.keys():
7467 import pymupdf_fonts
7468 fontbuffer = pymupdf_fonts.myfont(fontname) # make a copy
7469 del pymupdf_fonts
7470
7471 # install the font for the page
7472 if fontfile != None:
7473 if type(fontfile) is str:
7474 fontfile_str = fontfile
7475 elif hasattr(fontfile, "absolute"):
7476 fontfile_str = str(fontfile)
7477 elif hasattr(fontfile, "name"):
7478 fontfile_str = fontfile.name
7479 else:
7480 raise ValueError("bad fontfile")
7481 else:
7482 fontfile_str = None
7483 val = self._insertFont(fontname, bfname, fontfile_str, fontbuffer, set_simple, idx,
7484 wmode, serif, encoding, CJK_number)
7485
7486 if not val: # did not work, error return
7487 return val
7488
7489 xref = val[0] # xref of installed font
7490 fontdict = val[1]
7491
7492 if CheckFontInfo(doc, xref): # check again: document already has this font
7493 return xref # we are done
7494
7495 # need to create document font info
7496 doc.get_char_widths(xref, fontdict=fontdict)
7497 return xref
7498
7499 %}
7500
7501 FITZEXCEPTION(_insertFont, !result)
7502 PyObject *_insertFont(char *fontname, char *bfname,
7503 char *fontfile,
7504 PyObject *fontbuffer,
7505 int set_simple, int idx,
7506 int wmode, int serif,
7507 int encoding, int ordering)
7508 {
7509 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
7510 pdf_document *pdf;
7511 pdf_obj *resources, *fonts, *font_obj;
7512 PyObject *value;
7513 fz_try(gctx) {
7514 ASSERT_PDF(page);
7515 pdf = page->doc;
7516
7517 value = JM_insert_font(gctx, pdf, bfname, fontfile,fontbuffer,
7518 set_simple, idx, wmode, serif, encoding, ordering);
7519
7520 // get the objects /Resources, /Resources/Font
7521 resources = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(Resources));
7522 fonts = pdf_dict_get(gctx, resources, PDF_NAME(Font));
7523 if (!fonts) { // page has no fonts yet
7524 fonts = pdf_new_dict(gctx, pdf, 5);
7525 pdf_dict_putl_drop(gctx, page->obj, fonts, PDF_NAME(Resources), PDF_NAME(Font), NULL);
7526 }
7527 // store font in resources and fonts objects will contain named reference to font
7528 int xref = 0;
7529 JM_INT_ITEM(value, 0, &xref);
7530 if (!xref) {
7531 RAISEPY(gctx, "cannot insert font", PyExc_RuntimeError);
7532 }
7533 font_obj = pdf_new_indirect(gctx, pdf, xref, 0);
7534 pdf_dict_puts_drop(gctx, fonts, fontname, font_obj);
7535 }
7536 fz_always(gctx) {
7537 ;
7538 }
7539 fz_catch(gctx) {
7540 return NULL;
7541 }
7542
7543 return value;
7544 }
7545
7546 //----------------------------------------------------------------
7547 // Get page transformation matrix
7548 //----------------------------------------------------------------
7549 %pythoncode %{@property%}
7550 PARENTCHECK(transformation_matrix, """Page transformation matrix.""")
7551 %pythonappend transformation_matrix %{
7552 if self.rotation % 360 == 0:
7553 val = Matrix(val)
7554 else:
7555 val = Matrix(1, 0, 0, -1, 0, self.cropbox.height)
7556 %}
7557 PyObject *transformation_matrix()
7558 {
7559 fz_matrix ctm = fz_identity;
7560 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
7561 if (!page) return JM_py_from_matrix(ctm);
7562 fz_try(gctx) {
7563 pdf_page_transform(gctx, page, NULL, &ctm);
7564 }
7565 fz_catch(gctx) {;}
7566 return JM_py_from_matrix(ctm);
7567 }
7568
7569 //----------------------------------------------------------------
7570 // Page Get list of contents objects
7571 //----------------------------------------------------------------
7572 FITZEXCEPTION(get_contents, !result)
7573 PARENTCHECK(get_contents, """Get xrefs of /Contents objects.""")
7574 PyObject *get_contents()
7575 {
7576 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self);
7577 PyObject *list = NULL;
7578 pdf_obj *contents = NULL, *icont = NULL;
7579 int i, xref;
7580 size_t n = 0;
7581 fz_try(gctx) {
7582 ASSERT_PDF(page);
7583 contents = pdf_dict_get(gctx, page->obj, PDF_NAME(Contents));
7584 if (pdf_is_array(gctx, contents)) {
7585 n = pdf_array_len(gctx, contents);
7586 list = PyList_New(n);
7587 for (i = 0; i < n; i++) {
7588 icont = pdf_array_get(gctx, contents, i);
7589 xref = pdf_to_num(gctx, icont);
7590 PyList_SET_ITEM(list, i, Py_BuildValue("i", xref));
7591 }
7592 }
7593 else if (contents) {
7594 list = PyList_New(1);
7595 xref = pdf_to_num(gctx, contents);
7596 PyList_SET_ITEM(list, 0, Py_BuildValue("i", xref));
7597 }
7598 }
7599 fz_catch(gctx) {
7600 return NULL;
7601 }
7602 if (list) {
7603 return list;
7604 }
7605 return PyList_New(0);
7606 }
7607
7608 //----------------------------------------------------------------
7609 //
7610 //----------------------------------------------------------------
7611 %pythoncode %{
7612 def set_contents(self, xref: int)->None:
7613 """Set object at 'xref' as the page's /Contents."""
7614 CheckParent(self)
7615 doc = self.parent
7616 if doc.is_closed:
7617 raise ValueError("document closed")
7618 if not doc.is_pdf:
7619 raise ValueError("is no PDF")
7620 if not xref in range(1, doc.xref_length()):
7621 raise ValueError("bad xref")
7622 if not doc.xref_is_stream(xref):
7623 raise ValueError("xref is no stream")
7624 doc.xref_set_key(self.xref, "Contents", "%i 0 R" % xref)
7625
7626
7627 @property
7628 def is_wrapped(self):
7629 """Check if /Contents is wrapped with string pair "q" / "Q"."""
7630 if getattr(self, "was_wrapped", False): # costly checks only once
7631 return True
7632 cont = self.read_contents().split()
7633 if cont == []: # no contents treated as okay
7634 self.was_wrapped = True
7635 return True
7636 if cont[0] != b"q" or cont[-1] != b"Q":
7637 return False # potential "geometry" issue
7638 self.was_wrapped = True # cheap check next time
7639 return True
7640
7641
7642 def wrap_contents(self):
7643 if self.is_wrapped: # avoid unnecessary wrapping
7644 return
7645 TOOLS._insert_contents(self, b"q\n", False)
7646 TOOLS._insert_contents(self, b"\nQ", True)
7647 self.was_wrapped = True # indicate not needed again
7648
7649
7650 def links(self, kinds=None):
7651 """ Generator over the links of a page.
7652
7653 Args:
7654 kinds: (list) link kinds to subselect from. If none,
7655 all links are returned. E.g. kinds=[LINK_URI]
7656 will only yield URI links.
7657 """
7658 all_links = self.get_links()
7659 for link in all_links:
7660 if kinds is None or link["kind"] in kinds:
7661 yield (link)
7662
7663
7664 def annots(self, types=None):
7665 """ Generator over the annotations of a page.
7666
7667 Args:
7668 types: (list) annotation types to subselect from. If none,
7669 all annotations are returned. E.g. types=[PDF_ANNOT_LINE]
7670 will only yield line annotations.
7671 """
7672 skip_types = (PDF_ANNOT_LINK, PDF_ANNOT_POPUP, PDF_ANNOT_WIDGET)
7673 if not hasattr(types, "__getitem__"):
7674 annot_xrefs = [a[0] for a in self.annot_xrefs() if a[1] not in skip_types]
7675 else:
7676 annot_xrefs = [a[0] for a in self.annot_xrefs() if a[1] in types and a[1] not in skip_types]
7677 for xref in annot_xrefs:
7678 annot = self.load_annot(xref)
7679 annot._yielded=True
7680 yield annot
7681
7682
7683 def widgets(self, types=None):
7684 """ Generator over the widgets of a page.
7685
7686 Args:
7687 types: (list) field types to subselect from. If none,
7688 all fields are returned. E.g. types=[PDF_WIDGET_TYPE_TEXT]
7689 will only yield text fields.
7690 """
7691 widget_xrefs = [a[0] for a in self.annot_xrefs() if a[1] == PDF_ANNOT_WIDGET]
7692 for xref in widget_xrefs:
7693 widget = self.load_widget(xref)
7694 if types == None or widget.field_type in types:
7695 yield (widget)
7696
7697
7698 def __str__(self):
7699 CheckParent(self)
7700 x = self.parent.name
7701 if self.parent.stream is not None:
7702 x = "<memory, doc# %i>" % (self.parent._graft_id,)
7703 if x == "":
7704 x = "<new PDF, doc# %i>" % self.parent._graft_id
7705 return "page %s of %s" % (self.number, x)
7706
7707 def __repr__(self):
7708 CheckParent(self)
7709 x = self.parent.name
7710 if self.parent.stream is not None:
7711 x = "<memory, doc# %i>" % (self.parent._graft_id,)
7712 if x == "":
7713 x = "<new PDF, doc# %i>" % self.parent._graft_id
7714 return "page %s of %s" % (self.number, x)
7715
7716 def _reset_annot_refs(self):
7717 """Invalidate / delete all annots of this page."""
7718 for annot in self._annot_refs.values():
7719 if annot:
7720 annot._erase()
7721 self._annot_refs.clear()
7722
7723 @property
7724 def xref(self):
7725 """PDF xref number of page."""
7726 CheckParent(self)
7727 return self.parent.page_xref(self.number)
7728
7729 def _erase(self):
7730 self._reset_annot_refs()
7731 self._image_infos = None
7732 try:
7733 self.parent._forget_page(self)
7734 except:
7735 pass
7736 if getattr(self, "thisown", False):
7737 self.__swig_destroy__(self)
7738 self.parent = None
7739 self.number = None
7740
7741
7742 def __del__(self):
7743 self._erase()
7744
7745
7746 def get_fonts(self, full=False):
7747 """List of fonts defined in the page object."""
7748 CheckParent(self)
7749 return self.parent.get_page_fonts(self.number, full=full)
7750
7751
7752 def get_images(self, full=False):
7753 """List of images defined in the page object."""
7754 CheckParent(self)
7755 ret = self.parent.get_page_images(self.number, full=full)
7756 return ret
7757
7758
7759 def get_xobjects(self):
7760 """List of xobjects defined in the page object."""
7761 CheckParent(self)
7762 return self.parent.get_page_xobjects(self.number)
7763
7764
7765 def read_contents(self):
7766 """All /Contents streams concatenated to one bytes object."""
7767 return TOOLS._get_all_contents(self)
7768
7769
7770 @property
7771 def mediabox_size(self):
7772 return Point(self.mediabox.x1, self.mediabox.y1)
7773 %}
7774 }
7775 };
7776 %clearnodefaultctor;
7777
7778 //------------------------------------------------------------------------
7779 // Pixmap
7780 //------------------------------------------------------------------------
7781 struct Pixmap
7782 {
7783 %extend {
7784 ~Pixmap() {
7785 DEBUGMSG1("Pixmap");
7786 fz_pixmap *this_pix = (fz_pixmap *) $self;
7787 fz_drop_pixmap(gctx, this_pix);
7788 DEBUGMSG2;
7789 }
7790 FITZEXCEPTION(Pixmap, !result)
7791 %pythonprepend Pixmap
7792 %{"""Pixmap(colorspace, irect, alpha) - empty pixmap.
7793 Pixmap(colorspace, src) - copy changing colorspace.
7794 Pixmap(src, width, height,[clip]) - scaled copy, float dimensions.
7795 Pixmap(src, alpha=True) - copy adding / dropping alpha.
7796 Pixmap(source, mask) - from a non-alpha and a mask pixmap.
7797 Pixmap(file) - from an image file.
7798 Pixmap(memory) - from an image in memory (bytes).
7799 Pixmap(colorspace, width, height, samples, alpha) - from samples data.
7800 Pixmap(PDFdoc, xref) - from an image xref in a PDF document.
7801 """%}
7802 //----------------------------------------------------------------
7803 // create empty pixmap with colorspace and IRect
7804 //----------------------------------------------------------------
7805 Pixmap(struct Colorspace *cs, PyObject *bbox, int alpha = 0)
7806 {
7807 fz_pixmap *pm = NULL;
7808 fz_try(gctx) {
7809 pm = fz_new_pixmap_with_bbox(gctx, (fz_colorspace *) cs, JM_irect_from_py(bbox), NULL, alpha);
7810 }
7811 fz_catch(gctx) {
7812 return NULL;
7813 }
7814 return (struct Pixmap *) pm;
7815 }
7816
7817 //----------------------------------------------------------------
7818 // copy pixmap, converting colorspace
7819 //----------------------------------------------------------------
7820 Pixmap(struct Colorspace *cs, struct Pixmap *spix)
7821 {
7822 fz_pixmap *pm = NULL;
7823 fz_try(gctx) {
7824 if (!fz_pixmap_colorspace(gctx, (fz_pixmap *) spix)) {
7825 RAISEPY(gctx, "source colorspace must not be None", PyExc_ValueError);
7826 }
7827 fz_colorspace *cspace = NULL;
7828 if (cs) {
7829 cspace = (fz_colorspace *) cs;
7830 }
7831 if (cspace) {
7832 pm = fz_convert_pixmap(gctx, (fz_pixmap *) spix, cspace, NULL, NULL, fz_default_color_params, 1);
7833 } else {
7834 pm = fz_new_pixmap_from_alpha_channel(gctx, (fz_pixmap *) spix);
7835 if (!pm) {
7836 RAISEPY(gctx, MSG_PIX_NOALPHA, PyExc_RuntimeError);
7837 }
7838 }
7839 }
7840 fz_catch(gctx) {
7841 return NULL;
7842 }
7843 return (struct Pixmap *) pm;
7844 }
7845
7846
7847 //----------------------------------------------------------------
7848 // add mask to a pixmap w/o alpha channel
7849 //----------------------------------------------------------------
7850 Pixmap(struct Pixmap *spix, struct Pixmap *mpix)
7851 {
7852 fz_pixmap *dst = NULL;
7853 fz_pixmap *spm = (fz_pixmap *) spix;
7854 fz_pixmap *mpm = (fz_pixmap *) mpix;
7855 fz_try(gctx) {
7856 if (!spix) { // intercept NULL for spix: make alpha only pix
7857 dst = fz_new_pixmap_from_alpha_channel(gctx, mpm);
7858 if (!dst) {
7859 RAISEPY(gctx, MSG_PIX_NOALPHA, PyExc_RuntimeError);
7860 }
7861 } else {
7862 dst = fz_new_pixmap_from_color_and_mask(gctx, spm, mpm);
7863 }
7864 }
7865 fz_catch(gctx) {
7866 return NULL;
7867 }
7868 return (struct Pixmap *) dst;
7869 }
7870
7871
7872 //----------------------------------------------------------------
7873 // create pixmap as scaled copy of another one
7874 //----------------------------------------------------------------
7875 Pixmap(struct Pixmap *spix, float w, float h, PyObject *clip=NULL)
7876 {
7877 fz_pixmap *pm = NULL;
7878 fz_pixmap *src_pix = (fz_pixmap *) spix;
7879 fz_try(gctx) {
7880 fz_irect bbox = JM_irect_from_py(clip);
7881 if (clip != Py_None && (fz_is_infinite_irect(bbox) || fz_is_empty_irect(bbox))) {
7882 RAISEPY(gctx, "bad clip parameter", PyExc_ValueError);
7883 }
7884 if (!fz_is_infinite_irect(bbox)) {
7885 pm = fz_scale_pixmap(gctx, src_pix, src_pix->x, src_pix->y, w, h, &bbox);
7886 } else {
7887 pm = fz_scale_pixmap(gctx, src_pix, src_pix->x, src_pix->y, w, h, NULL);
7888 }
7889 }
7890 fz_catch(gctx) {
7891 return NULL;
7892 }
7893 return (struct Pixmap *) pm;
7894 }
7895
7896
7897 //----------------------------------------------------------------
7898 // copy pixmap & add / drop the alpha channel
7899 //----------------------------------------------------------------
7900 Pixmap(struct Pixmap *spix, int alpha=1)
7901 {
7902 fz_pixmap *pm = NULL, *src_pix = (fz_pixmap *) spix;
7903 int n, w, h, i;
7904 fz_separations *seps = NULL;
7905 fz_try(gctx) {
7906 if (!INRANGE(alpha, 0, 1)) {
7907 RAISEPY(gctx, "bad alpha value", PyExc_ValueError);
7908 }
7909 fz_colorspace *cs = fz_pixmap_colorspace(gctx, src_pix);
7910 if (!cs && !alpha) {
7911 RAISEPY(gctx, "cannot drop alpha for 'NULL' colorspace", PyExc_ValueError);
7912 }
7913 n = fz_pixmap_colorants(gctx, src_pix);
7914 w = fz_pixmap_width(gctx, src_pix);
7915 h = fz_pixmap_height(gctx, src_pix);
7916 pm = fz_new_pixmap(gctx, cs, w, h, seps, alpha);
7917 pm->x = src_pix->x;
7918 pm->y = src_pix->y;
7919 pm->xres = src_pix->xres;
7920 pm->yres = src_pix->yres;
7921
7922 // copy samples data ------------------------------------------
7923 unsigned char *sptr = src_pix->samples;
7924 unsigned char *tptr = pm->samples;
7925 if (src_pix->alpha == pm->alpha) { // identical samples
7926 memcpy(tptr, sptr, w * h * (n + alpha));
7927 } else {
7928 for (i = 0; i < w * h; i++) {
7929 memcpy(tptr, sptr, n);
7930 tptr += n;
7931 if (pm->alpha) {
7932 tptr[0] = 255;
7933 tptr++;
7934 }
7935 sptr += n + src_pix->alpha;
7936 }
7937 }
7938 }
7939 fz_catch(gctx) {
7940 return NULL;
7941 }
7942 return (struct Pixmap *) pm;
7943 }
7944
7945 //----------------------------------------------------------------
7946 // create pixmap from samples data
7947 //----------------------------------------------------------------
7948 Pixmap(struct Colorspace *cs, int w, int h, PyObject *samples, int alpha=0)
7949 {
7950 int n = fz_colorspace_n(gctx, (fz_colorspace *) cs);
7951 int stride = (n + alpha) * w;
7952 fz_separations *seps = NULL;
7953 fz_buffer *res = NULL;
7954 fz_pixmap *pm = NULL;
7955 fz_try(gctx) {
7956 size_t size = 0;
7957 unsigned char *c = NULL;
7958 res = JM_BufferFromBytes(gctx, samples);
7959 if (!res) {
7960 RAISEPY(gctx, "bad samples data", PyExc_ValueError);
7961 }
7962 size = fz_buffer_storage(gctx, res, &c);
7963 if (stride * h != size) {
7964 RAISEPY(gctx, "bad samples length", PyExc_ValueError);
7965 }
7966 pm = fz_new_pixmap(gctx, (fz_colorspace *) cs, w, h, seps, alpha);
7967 memcpy(pm->samples, c, size);
7968 }
7969 fz_always(gctx) {
7970 fz_drop_buffer(gctx, res);
7971 }
7972 fz_catch(gctx) {
7973 return NULL;
7974 }
7975 return (struct Pixmap *) pm;
7976 }
7977
7978
7979 //----------------------------------------------------------------
7980 // create pixmap from filename, file object, pathlib.Path or memory
7981 //----------------------------------------------------------------
7982 Pixmap(PyObject *imagedata)
7983 {
7984 fz_buffer *res = NULL;
7985 fz_image *img = NULL;
7986 fz_pixmap *pm = NULL;
7987 PyObject *fname = NULL;
7988 PyObject *name = PyUnicode_FromString("name");
7989 fz_try(gctx) {
7990 if (PyObject_HasAttrString(imagedata, "resolve")) {
7991 fname = PyObject_CallMethod(imagedata, "__str__", NULL);
7992 if (fname) {
7993 img = fz_new_image_from_file(gctx, JM_StrAsChar(fname));
7994 }
7995 } else if (PyObject_HasAttr(imagedata, name)) {
7996 fname = PyObject_GetAttr(imagedata, name);
7997 if (fname) {
7998 img = fz_new_image_from_file(gctx, JM_StrAsChar(fname));
7999 }
8000 } else if (PyUnicode_Check(imagedata)) {
8001 img = fz_new_image_from_file(gctx, JM_StrAsChar(imagedata));
8002 } else {
8003 res = JM_BufferFromBytes(gctx, imagedata);
8004 if (!res || !fz_buffer_storage(gctx, res, NULL)) {
8005 RAISEPY(gctx, "bad image data", PyExc_ValueError);
8006 }
8007 img = fz_new_image_from_buffer(gctx, res);
8008 }
8009 pm = fz_get_pixmap_from_image(gctx, img, NULL, NULL, NULL, NULL);
8010 int xres, yres;
8011 fz_image_resolution(img, &xres, &yres);
8012 pm->xres = xres;
8013 pm->yres = yres;
8014 }
8015 fz_always(gctx) {
8016 Py_CLEAR(fname);
8017 Py_CLEAR(name);
8018 fz_drop_image(gctx, img);
8019 fz_drop_buffer(gctx, res);
8020 }
8021 fz_catch(gctx) {
8022 return NULL;
8023 }
8024 return (struct Pixmap *) pm;
8025 }
8026
8027
8028 //----------------------------------------------------------------
8029 // Create pixmap from PDF image identified by XREF number
8030 //----------------------------------------------------------------
8031 Pixmap(struct Document *doc, int xref)
8032 {
8033 fz_image *img = NULL;
8034 fz_pixmap *pix = NULL;
8035 pdf_obj *ref = NULL;
8036 pdf_obj *type;
8037 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) doc);
8038 fz_try(gctx) {
8039 ASSERT_PDF(pdf);
8040 int xreflen = pdf_xref_len(gctx, pdf);
8041 if (!INRANGE(xref, 1, xreflen-1)) {
8042 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
8043 }
8044 ref = pdf_new_indirect(gctx, pdf, xref, 0);
8045 type = pdf_dict_get(gctx, ref, PDF_NAME(Subtype));
8046 if (!pdf_name_eq(gctx, type, PDF_NAME(Image)) &&
8047 !pdf_name_eq(gctx, type, PDF_NAME(Alpha)) &&
8048 !pdf_name_eq(gctx, type, PDF_NAME(Luminosity))) {
8049 RAISEPY(gctx, MSG_IS_NO_IMAGE, PyExc_ValueError);
8050 }
8051 img = pdf_load_image(gctx, pdf, ref);
8052 pix = fz_get_pixmap_from_image(gctx, img, NULL, NULL, NULL, NULL);
8053 }
8054 fz_always(gctx) {
8055 fz_drop_image(gctx, img);
8056 pdf_drop_obj(gctx, ref);
8057 }
8058 fz_catch(gctx) {
8059 fz_drop_pixmap(gctx, pix);
8060 return NULL;
8061 }
8062 return (struct Pixmap *) pix;
8063 }
8064
8065
8066 //----------------------------------------------------------------
8067 // warp
8068 //----------------------------------------------------------------
8069 FITZEXCEPTION(warp, !result)
8070 %pythonprepend warp %{
8071 """Return pixmap from a warped quad."""
8072 EnsureOwnership(self)
8073 if not quad.is_convex: raise ValueError("quad must be convex")%}
8074 struct Pixmap *warp(PyObject *quad, int width, int height)
8075 {
8076 fz_point points[4];
8077 fz_quad q = JM_quad_from_py(quad);
8078 fz_pixmap *dst = NULL;
8079 points[0] = q.ul;
8080 points[1] = q.ur;
8081 points[2] = q.lr;
8082 points[3] = q.ll;
8083
8084 fz_try(gctx) {
8085 dst = fz_warp_pixmap(gctx, (fz_pixmap *) $self, points, width, height);
8086 }
8087 fz_catch(gctx) {
8088 return NULL;
8089 }
8090 return (struct Pixmap *) dst;
8091 }
8092
8093
8094 //----------------------------------------------------------------
8095 // shrink
8096 //----------------------------------------------------------------
8097 ENSURE_OWNERSHIP(shrink, """Divide width and height by 2**factor.
8098 E.g. factor=1 shrinks to 25% of original size (in place).""")
8099 void shrink(int factor)
8100 {
8101 if (factor < 1)
8102 {
8103 JM_Warning("ignoring shrink factor < 1");
8104 return;
8105 }
8106 fz_subsample_pixmap(gctx, (fz_pixmap *) $self, factor);
8107 }
8108
8109 //----------------------------------------------------------------
8110 // apply gamma correction
8111 //----------------------------------------------------------------
8112 ENSURE_OWNERSHIP(gamma_with, """Apply correction with some float.
8113 gamma=1 is a no-op.""")
8114 void gamma_with(float gamma)
8115 {
8116 if (!fz_pixmap_colorspace(gctx, (fz_pixmap *) $self))
8117 {
8118 JM_Warning("colorspace invalid for function");
8119 return;
8120 }
8121 fz_gamma_pixmap(gctx, (fz_pixmap *) $self, gamma);
8122 }
8123
8124 //----------------------------------------------------------------
8125 // tint pixmap with color
8126 //----------------------------------------------------------------
8127 %pythonprepend tint_with
8128 %{"""Tint colors with modifiers for black and white."""
8129 EnsureOwnership(self)
8130 if not self.colorspace or self.colorspace.n > 3:
8131 print("warning: colorspace invalid for function")
8132 return%}
8133 void tint_with(int black, int white)
8134 {
8135 fz_tint_pixmap(gctx, (fz_pixmap *) $self, black, white);
8136 }
8137
8138 //-----------------------------------------------------------------
8139 // clear all of pixmap samples to 0x00 */
8140 //-----------------------------------------------------------------
8141 ENSURE_OWNERSHIP(clear_with, """Fill all color components with same value.""")
8142 void clear_with()
8143 {
8144 fz_clear_pixmap(gctx, (fz_pixmap *) $self);
8145 }
8146
8147 //-----------------------------------------------------------------
8148 // clear total pixmap with value */
8149 //-----------------------------------------------------------------
8150 void clear_with(int value)
8151 {
8152 fz_clear_pixmap_with_value(gctx, (fz_pixmap *) $self, value);
8153 }
8154
8155 //-----------------------------------------------------------------
8156 // clear pixmap rectangle with value
8157 //-----------------------------------------------------------------
8158 void clear_with(int value, PyObject *bbox)
8159 {
8160 JM_clear_pixmap_rect_with_value(gctx, (fz_pixmap *) $self, value, JM_irect_from_py(bbox));
8161 }
8162
8163 //-----------------------------------------------------------------
8164 // copy pixmaps
8165 //-----------------------------------------------------------------
8166 FITZEXCEPTION(copy, !result)
8167 ENSURE_OWNERSHIP(copy, """Copy bbox from another Pixmap.""")
8168 PyObject *copy(struct Pixmap *src, PyObject *bbox)
8169 {
8170 fz_try(gctx) {
8171 fz_pixmap *pm = (fz_pixmap *) $self, *src_pix = (fz_pixmap *) src;
8172 if (!fz_pixmap_colorspace(gctx, src_pix)) {
8173 RAISEPY(gctx, "cannot copy pixmap with NULL colorspace", PyExc_ValueError);
8174 }
8175 if (pm->alpha != src_pix->alpha) {
8176 RAISEPY(gctx, "source and target alpha must be equal", PyExc_ValueError);
8177 }
8178 fz_copy_pixmap_rect(gctx, pm, src_pix, JM_irect_from_py(bbox), NULL);
8179 }
8180 fz_catch(gctx) {
8181 return NULL;
8182 }
8183 Py_RETURN_NONE;
8184 }
8185
8186 //-----------------------------------------------------------------
8187 // set alpha values
8188 //-----------------------------------------------------------------
8189 FITZEXCEPTION(set_alpha, !result)
8190 ENSURE_OWNERSHIP(set_alpha, """Set alpha channel to values contained in a byte array.
8191 If None, all alphas are 255.
8192
8193 Args:
8194 alphavalues: (bytes) with length (width * height) or 'None'.
8195 premultiply: (bool, True) premultiply colors with alpha values.
8196 opaque: (tuple, length colorspace.n) this color receives opacity 0.
8197 matte: (tuple, length colorspace.n) preblending background color.
8198 """)
8199 PyObject *set_alpha(PyObject *alphavalues=NULL, int premultiply=1, PyObject *opaque=NULL, PyObject *matte=NULL)
8200 {
8201 fz_buffer *res = NULL;
8202 fz_pixmap *pix = (fz_pixmap *) $self;
8203 unsigned char alpha = 0, m = 0;
8204 fz_try(gctx) {
8205 if (pix->alpha == 0) {
8206 RAISEPY(gctx, MSG_PIX_NOALPHA, PyExc_ValueError);
8207 }
8208 size_t i, k, j;
8209 size_t n = fz_pixmap_colorants(gctx, pix);
8210 size_t w = (size_t) fz_pixmap_width(gctx, pix);
8211 size_t h = (size_t) fz_pixmap_height(gctx, pix);
8212 size_t balen = w * h * (n+1);
8213 int colors[4]; // make this color opaque
8214 int bgcolor[4]; // preblending background color
8215 int zero_out = 0, bground = 0;
8216 if (opaque && PySequence_Check(opaque) && PySequence_Size(opaque) == n) {
8217 for (i = 0; i < n; i++) {
8218 if (JM_INT_ITEM(opaque, i, &colors[i]) == 1) {
8219 RAISEPY(gctx, "bad opaque components", PyExc_ValueError);
8220 }
8221 }
8222 zero_out = 1;
8223 }
8224 if (matte && PySequence_Check(matte) && PySequence_Size(matte) == n) {
8225 for (i = 0; i < n; i++) {
8226 if (JM_INT_ITEM(matte, i, &bgcolor[i]) == 1) {
8227 RAISEPY(gctx, "bad matte components", PyExc_ValueError);
8228 }
8229 }
8230 bground = 1;
8231 }
8232 unsigned char *data = NULL;
8233 size_t data_len = 0;
8234 if (alphavalues && PyObject_IsTrue(alphavalues)) {
8235 res = JM_BufferFromBytes(gctx, alphavalues);
8236 data_len = fz_buffer_storage(gctx, res, &data);
8237 if (data_len < w * h) {
8238 RAISEPY(gctx, "bad alpha values", PyExc_ValueError);
8239 }
8240 }
8241 i = k = j = 0;
8242 int data_fix = 255;
8243 while (i < balen) {
8244 alpha = data[k];
8245 if (zero_out) {
8246 for (j = i; j < i+n; j++) {
8247 if (pix->samples[j] != (unsigned char) colors[j - i]) {
8248 data_fix = 255;
8249 break;
8250 } else {
8251 data_fix = 0;
8252 }
8253 }
8254 }
8255 if (data_len) {
8256 if (data_fix == 0) {
8257 pix->samples[i+n] = 0;
8258 } else {
8259 pix->samples[i+n] = alpha;
8260 }
8261 if (premultiply && !bground) {
8262 for (j = i; j < i+n; j++) {
8263 pix->samples[j] = fz_mul255(pix->samples[j], alpha);
8264 }
8265 } else if (bground) {
8266 for (j = i; j < i+n; j++) {
8267 m = (unsigned char) bgcolor[j - i];
8268 pix->samples[j] = m + fz_mul255((pix->samples[j] - m), alpha);
8269 }
8270 }
8271 } else {
8272 pix->samples[i+n] = data_fix;
8273 }
8274 i += n+1;
8275 k += 1;
8276 }
8277 }
8278 fz_always(gctx) {
8279 fz_drop_buffer(gctx, res);
8280 }
8281 fz_catch(gctx) {
8282 return NULL;
8283 }
8284 Py_RETURN_NONE;
8285 }
8286
8287 //-----------------------------------------------------------------
8288 // Pixmap._tobytes
8289 //-----------------------------------------------------------------
8290 FITZEXCEPTION(_tobytes, !result)
8291 PyObject *_tobytes(int format, int jpg_quality)
8292 {
8293 fz_output *out = NULL;
8294 fz_buffer *res = NULL;
8295 PyObject *barray = NULL;
8296 fz_pixmap *pm = (fz_pixmap *) $self;
8297 fz_try(gctx) {
8298 size_t size = fz_pixmap_stride(gctx, pm) * pm->h;
8299 res = fz_new_buffer(gctx, size);
8300 out = fz_new_output_with_buffer(gctx, res);
8301
8302 switch(format) {
8303 case(1):
8304 fz_write_pixmap_as_png(gctx, out, pm);
8305 break;
8306 case(2):
8307 fz_write_pixmap_as_pnm(gctx, out, pm);
8308 break;
8309 case(3):
8310 fz_write_pixmap_as_pam(gctx, out, pm);
8311 break;
8312 case(5): // Adobe Photoshop Document
8313 fz_write_pixmap_as_psd(gctx, out, pm);
8314 break;
8315 case(6): // Postscript format
8316 fz_write_pixmap_as_ps(gctx, out, pm);
8317 break;
8318 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR >= 22
8319 case(7): // JPEG format
8320 #if FZ_VERSION_MINOR < 24
8321 fz_write_pixmap_as_jpeg(gctx, out, pm, jpg_quality);
8322 #else
8323 fz_write_pixmap_as_jpeg(gctx, out, pm, jpg_quality, 0 /*invert_cmyk*/);
8324 #endif
8325 break;
8326 #endif
8327 default:
8328 fz_write_pixmap_as_png(gctx, out, pm);
8329 break;
8330 }
8331 barray = JM_BinFromBuffer(gctx, res);
8332 }
8333 fz_always(gctx) {
8334 fz_drop_output(gctx, out);
8335 fz_drop_buffer(gctx, res);
8336 }
8337
8338 fz_catch(gctx) {
8339 return NULL;
8340 }
8341 return barray;
8342 }
8343
8344 %pythoncode %{
8345 def tobytes(self, output="png", jpg_quality=95):
8346 """Convert to binary image stream of desired type.
8347
8348 Can be used as input to GUI packages like tkinter.
8349
8350 Args:
8351 output: (str) image type, default is PNG. Others are JPG, JPEG, PNM, PGM, PPM,
8352 PBM, PAM, PSD, PS.
8353 Returns:
8354 Bytes object.
8355 """
8356 EnsureOwnership(self)
8357 valid_formats = {"png": 1, "pnm": 2, "pgm": 2, "ppm": 2, "pbm": 2,
8358 "pam": 3, "psd": 5, "ps": 6, "jpg": 7, "jpeg": 7}
8359
8360 idx = valid_formats.get(output.lower(), None)
8361 if idx==None:
8362 raise ValueError(f"Image format {output} not in {tuple(valid_formats.keys())}")
8363 if self.alpha and idx in (2, 6, 7):
8364 raise ValueError("'%s' cannot have alpha" % output)
8365 if self.colorspace and self.colorspace.n > 3 and idx in (1, 2, 4):
8366 raise ValueError("unsupported colorspace for '%s'" % output)
8367 if idx == 7:
8368 self.set_dpi(self.xres, self.yres)
8369 barray = self._tobytes(idx, jpg_quality)
8370 return barray
8371 %}
8372
8373
8374 //-----------------------------------------------------------------
8375 // output as PDF-OCR
8376 //-----------------------------------------------------------------
8377 FITZEXCEPTION(pdfocr_save, !result)
8378 %pythonprepend pdfocr_save %{
8379 """Save pixmap as an OCR-ed PDF page."""
8380 EnsureOwnership(self)
8381 if not os.getenv("TESSDATA_PREFIX") and not tessdata:
8382 raise RuntimeError("No OCR support: TESSDATA_PREFIX not set")
8383 %}
8384 ENSURE_OWNERSHIP(pdfocr_save, )
8385 PyObject *pdfocr_save(PyObject *filename, int compress=1, char *language=NULL, char *tessdata=NULL)
8386 {
8387 fz_pdfocr_options opts;
8388 memset(&opts, 0, sizeof opts);
8389 opts.compress = compress;
8390 if (language) {
8391 fz_strlcpy(opts.language, language, sizeof(opts.language));
8392 }
8393 if (tessdata) {
8394 fz_strlcpy(opts.datadir, tessdata, sizeof(opts.language));
8395 }
8396 fz_output *out = NULL;
8397 fz_pixmap *pix = (fz_pixmap *) $self;
8398 fz_try(gctx) {
8399 if (PyUnicode_Check(filename)) {
8400 fz_save_pixmap_as_pdfocr(gctx, pix, (char *) PyUnicode_AsUTF8(filename), 0, &opts);
8401 } else {
8402 out = JM_new_output_fileptr(gctx, filename);
8403 fz_write_pixmap_as_pdfocr(gctx, out, pix, &opts);
8404 }
8405 }
8406 fz_always(gctx) {
8407 fz_drop_output(gctx, out);
8408 }
8409 fz_catch(gctx) {
8410 return NULL;
8411 }
8412 Py_RETURN_NONE;
8413 }
8414
8415 %pythoncode %{
8416 def pdfocr_tobytes(self, compress=True, language="eng", tessdata=None):
8417 """Save pixmap as an OCR-ed PDF page.
8418
8419 Args:
8420 compress: (bool) compress, default 1 (True).
8421 language: (str) language(s) occurring on page, default "eng" (English),
8422 multiples like "eng+ger" for English and German.
8423 tessdata: (str) folder name of Tesseract's language support. Must be
8424 given if environment variable TESSDATA_PREFIX is not set.
8425 Notes:
8426 On failure, make sure Tesseract is installed and you have set the
8427 environment variable "TESSDATA_PREFIX" to the folder containing your
8428 Tesseract's language support data.
8429 """
8430 if not os.getenv("TESSDATA_PREFIX") and not tessdata:
8431 raise RuntimeError("No OCR support: TESSDATA_PREFIX not set")
8432 EnsureOwnership(self)
8433 from io import BytesIO
8434 bio = BytesIO()
8435 self.pdfocr_save(bio, compress=compress, language=language, tessdata=tessdata)
8436 return bio.getvalue()
8437 %}
8438
8439
8440 //-----------------------------------------------------------------
8441 // _writeIMG
8442 //-----------------------------------------------------------------
8443 FITZEXCEPTION(_writeIMG, !result)
8444 PyObject *_writeIMG(char *filename, int format, int jpg_quality)
8445 {
8446 fz_try(gctx) {
8447 fz_pixmap *pm = (fz_pixmap *) $self;
8448 switch(format) {
8449 case(1):
8450 fz_save_pixmap_as_png(gctx, pm, filename);
8451 break;
8452 case(2):
8453 fz_save_pixmap_as_pnm(gctx, pm, filename);
8454 break;
8455 case(3):
8456 fz_save_pixmap_as_pam(gctx, pm, filename);
8457 break;
8458 case(5): // Adobe Photoshop Document
8459 fz_save_pixmap_as_psd(gctx, pm, filename);
8460 break;
8461 case(6): // Postscript
8462 fz_save_pixmap_as_ps(gctx, pm, filename, 0);
8463 break;
8464 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR >= 22
8465 case(7): // JPEG
8466 fz_save_pixmap_as_jpeg(gctx, pm, filename, jpg_quality);
8467 break;
8468 #endif
8469 default:
8470 fz_save_pixmap_as_png(gctx, pm, filename);
8471 break;
8472 }
8473 }
8474 fz_catch(gctx) {
8475 return NULL;
8476 }
8477 Py_RETURN_NONE;
8478 }
8479 %pythoncode %{
8480 def save(self, filename, output=None, jpg_quality=95):
8481 """Output as image in format determined by filename extension.
8482
8483 Args:
8484 output: (str) only use to overrule filename extension. Default is PNG.
8485 Others are JPEG, JPG, PNM, PGM, PPM, PBM, PAM, PSD, PS.
8486 """
8487 EnsureOwnership(self)
8488 valid_formats = {"png": 1, "pnm": 2, "pgm": 2, "ppm": 2, "pbm": 2,
8489 "pam": 3, "psd": 5, "ps": 6, "jpg": 7, "jpeg": 7}
8490
8491 if type(filename) is str:
8492 pass
8493 elif hasattr(filename, "absolute"):
8494 filename = str(filename)
8495 elif hasattr(filename, "name"):
8496 filename = filename.name
8497 if output is None:
8498 _, ext = os.path.splitext(filename)
8499 output = ext[1:]
8500
8501 idx = valid_formats.get(output.lower(), None)
8502 if idx == None:
8503 raise ValueError(f"Image format {output} not in {tuple(valid_formats.keys())}")
8504 if self.alpha and idx in (2, 6, 7):
8505 raise ValueError("'%s' cannot have alpha" % output)
8506 if self.colorspace and self.colorspace.n > 3 and idx in (1, 2, 4):
8507 raise ValueError("unsupported colorspace for '%s'" % output)
8508 if idx == 7:
8509 self.set_dpi(self.xres, self.yres)
8510 return self._writeIMG(filename, idx, jpg_quality)
8511
8512 def pil_save(self, *args, unmultiply=False, **kwargs):
8513 """Write to image file using Pillow.
8514
8515 Args are passed to Pillow's Image.save method, see their documentation.
8516 Use instead of save when other output formats are desired.
8517
8518 :arg bool unmultiply: generates Pillow mode "RGBa" instead of "RGBA".
8519 Relevant for colorspace RGB with alpha only.
8520 """
8521 EnsureOwnership(self)
8522 try:
8523 from PIL import Image
8524 except ImportError:
8525 print("Pillow not installed")
8526 raise
8527
8528 cspace = self.colorspace
8529 if cspace is None:
8530 mode = "L"
8531 elif cspace.n == 1:
8532 mode = "L" if self.alpha == 0 else "LA"
8533 elif cspace.n == 3:
8534 mode = "RGB" if self.alpha == 0 else "RGBA"
8535 if mode == "RGBA" and unmultiply:
8536 mode = "RGBa"
8537 else:
8538 mode = "CMYK"
8539
8540 img = Image.frombytes(mode, (self.width, self.height), self.samples)
8541
8542 if "dpi" not in kwargs.keys():
8543 kwargs["dpi"] = (self.xres, self.yres)
8544
8545 img.save(*args, **kwargs)
8546
8547 def pil_tobytes(self, *args, unmultiply=False, **kwargs):
8548 """Convert to binary image stream using pillow.
8549
8550 Args are passed to Pillow's Image.save method, see their documentation.
8551 Use instead of 'tobytes' when other output formats are needed.
8552 """
8553 EnsureOwnership(self)
8554 from io import BytesIO
8555 bytes_out = BytesIO()
8556 self.pil_save(bytes_out, *args, unmultiply=unmultiply, **kwargs)
8557 return bytes_out.getvalue()
8558
8559 %}
8560 //-----------------------------------------------------------------
8561 // invert_irect
8562 //-----------------------------------------------------------------
8563 %pythonprepend invert_irect
8564 %{"""Invert the colors inside a bbox."""%}
8565 PyObject *invert_irect(PyObject *bbox = NULL)
8566 {
8567 fz_pixmap *pm = (fz_pixmap *) $self;
8568 if (!fz_pixmap_colorspace(gctx, pm))
8569 {
8570 JM_Warning("ignored for stencil pixmap");
8571 return JM_BOOL(0);
8572 }
8573
8574 fz_irect r = JM_irect_from_py(bbox);
8575 if (fz_is_infinite_irect(r))
8576 r = fz_pixmap_bbox(gctx, pm);
8577
8578 return JM_BOOL(JM_invert_pixmap_rect(gctx, pm, r));
8579 }
8580
8581 //-----------------------------------------------------------------
8582 // get one pixel as a list
8583 //-----------------------------------------------------------------
8584 FITZEXCEPTION(pixel, !result)
8585 ENSURE_OWNERSHIP(pixel, """Get color tuple of pixel (x, y).
8586 Includes alpha byte if applicable.""")
8587 PyObject *pixel(int x, int y)
8588 {
8589 PyObject *p = NULL;
8590 fz_try(gctx) {
8591 fz_pixmap *pm = (fz_pixmap *) $self;
8592 if (!INRANGE(x, 0, pm->w - 1) || !INRANGE(y, 0, pm->h - 1)) {
8593 RAISEPY(gctx, MSG_PIXEL_OUTSIDE, PyExc_ValueError);
8594 }
8595 int n = pm->n;
8596 int stride = fz_pixmap_stride(gctx, pm);
8597 int j, i = stride * y + n * x;
8598 p = PyTuple_New(n);
8599 for (j = 0; j < n; j++) {
8600 PyTuple_SET_ITEM(p, j, Py_BuildValue("i", pm->samples[i + j]));
8601 }
8602 }
8603 fz_catch(gctx) {
8604 return NULL;
8605 }
8606 return p;
8607 }
8608
8609 //-----------------------------------------------------------------
8610 // Set one pixel to a given color tuple
8611 //-----------------------------------------------------------------
8612 FITZEXCEPTION(set_pixel, !result)
8613 ENSURE_OWNERSHIP(set_pixel, """Set color of pixel (x, y).""")
8614 PyObject *set_pixel(int x, int y, PyObject *color)
8615 {
8616 fz_try(gctx) {
8617 fz_pixmap *pm = (fz_pixmap *) $self;
8618 if (!INRANGE(x, 0, pm->w - 1) || !INRANGE(y, 0, pm->h - 1)) {
8619 RAISEPY(gctx, MSG_PIXEL_OUTSIDE, PyExc_ValueError);
8620 }
8621 int n = pm->n;
8622 if (!PySequence_Check(color) || PySequence_Size(color) != n) {
8623 RAISEPY(gctx, MSG_BAD_COLOR_SEQ, PyExc_ValueError);
8624 }
8625 int i, j;
8626 unsigned char c[5];
8627 for (j = 0; j < n; j++) {
8628 if (JM_INT_ITEM(color, j, &i) == 1) {
8629 RAISEPY(gctx, MSG_BAD_COLOR_SEQ, PyExc_ValueError);
8630 }
8631 if (!INRANGE(i, 0, 255)) {
8632 RAISEPY(gctx, MSG_BAD_COLOR_SEQ, PyExc_ValueError);
8633 }
8634 c[j] = (unsigned char) i;
8635 }
8636 int stride = fz_pixmap_stride(gctx, pm);
8637 i = stride * y + n * x;
8638 for (j = 0; j < n; j++) {
8639 pm->samples[i + j] = c[j];
8640 }
8641 }
8642 fz_catch(gctx) {
8643 PyErr_Clear();
8644 return NULL;
8645 }
8646 Py_RETURN_NONE;
8647 }
8648
8649
8650 //-----------------------------------------------------------------
8651 // Set Pixmap origin
8652 //-----------------------------------------------------------------
8653 ENSURE_OWNERSHIP(set_origin, """Set top-left coordinates.""")
8654 PyObject *set_origin(int x, int y)
8655 {
8656 fz_pixmap *pm = (fz_pixmap *) $self;
8657 pm->x = x;
8658 pm->y = y;
8659 Py_RETURN_NONE;
8660 }
8661
8662 ENSURE_OWNERSHIP(set_dpi, """Set resolution in both dimensions.""")
8663 PyObject *set_dpi(int xres, int yres)
8664 {
8665 fz_pixmap *pm = (fz_pixmap *) $self;
8666 pm->xres = xres;
8667 pm->yres = yres;
8668 Py_RETURN_NONE;
8669 }
8670
8671 //-----------------------------------------------------------------
8672 // Set a rect to a given color tuple
8673 //-----------------------------------------------------------------
8674 FITZEXCEPTION(set_rect, !result)
8675 ENSURE_OWNERSHIP(set_rect, """Set color of all pixels in bbox.""")
8676 PyObject *set_rect(PyObject *bbox, PyObject *color)
8677 {
8678 PyObject *rc = NULL;
8679 fz_try(gctx) {
8680 fz_pixmap *pm = (fz_pixmap *) $self;
8681 Py_ssize_t j, n = (Py_ssize_t) pm->n;
8682 if (!PySequence_Check(color) || PySequence_Size(color) != n) {
8683 RAISEPY(gctx, MSG_BAD_COLOR_SEQ, PyExc_ValueError);
8684 }
8685 unsigned char c[5];
8686 int i;
8687 for (j = 0; j < n; j++) {
8688 if (JM_INT_ITEM(color, j, &i) == 1) {
8689 RAISEPY(gctx, MSG_BAD_COLOR_SEQ, PyExc_ValueError);
8690 }
8691 if (!INRANGE(i, 0, 255)) {
8692 RAISEPY(gctx, MSG_BAD_COLOR_SEQ, PyExc_ValueError);
8693 }
8694 c[j] = (unsigned char) i;
8695 }
8696 i = JM_fill_pixmap_rect_with_color(gctx, pm, c, JM_irect_from_py(bbox));
8697 rc = JM_BOOL(i);
8698 }
8699 fz_catch(gctx) {
8700 PyErr_Clear();
8701 return NULL;
8702 }
8703 return rc;
8704 }
8705
8706 //-----------------------------------------------------------------
8707 // check if monochrome
8708 //-----------------------------------------------------------------
8709 %pythoncode %{@property%}
8710 ENSURE_OWNERSHIP(is_monochrome, """Check if pixmap is monochrome.""")
8711 PyObject *is_monochrome()
8712 {
8713 return JM_BOOL(fz_is_pixmap_monochrome(gctx, (fz_pixmap *) $self));
8714 }
8715
8716 //-----------------------------------------------------------------
8717 // check if unicolor (only one color there)
8718 //-----------------------------------------------------------------
8719 %pythoncode %{@property%}
8720 ENSURE_OWNERSHIP(is_unicolor, """Check if pixmap has only one color.""")
8721 PyObject *is_unicolor()
8722 {
8723 fz_pixmap *pm = (fz_pixmap *) $self;
8724 size_t i, n = pm->n, count = pm->w * pm->h * n;
8725 unsigned char *s = pm->samples;
8726 for (i = n; i < count; i += n) {
8727 if (memcmp(s, s + i, n) != 0) {
8728 Py_RETURN_FALSE;
8729 }
8730 }
8731 Py_RETURN_TRUE;
8732 }
8733
8734
8735 //-----------------------------------------------------------------
8736 // count each pixmap color
8737 //-----------------------------------------------------------------
8738 FITZEXCEPTION(color_count, !result)
8739 ENSURE_OWNERSHIP(color_count, """Return count of each color.""")
8740 PyObject *color_count(int colors=0, PyObject *clip=NULL)
8741 {
8742 fz_pixmap *pm = (fz_pixmap *) $self;
8743 PyObject *rc = NULL;
8744 fz_try(gctx) {
8745 rc = JM_color_count(gctx, pm, clip);
8746 if (!rc) {
8747 RAISEPY(gctx, MSG_COLOR_COUNT_FAILED, PyExc_RuntimeError);
8748 }
8749 }
8750 fz_catch(gctx) {
8751 return NULL;
8752 }
8753 if (!colors) {
8754 Py_ssize_t len = PyDict_Size(rc);
8755 Py_DECREF(rc);
8756 return PyLong_FromSsize_t(len);
8757 }
8758 return rc;
8759 }
8760
8761 %pythoncode %{
8762 def color_topusage(self, clip=None):
8763 """Return most frequent color and its usage ratio."""
8764 EnsureOwnership(self)
8765 allpixels = 0
8766 cnt = 0
8767 if clip != None and self.irect in Rect(clip):
8768 clip = self.irect
8769 for pixel, count in self.color_count(colors=True,clip=clip).items():
8770 allpixels += count
8771 if count > cnt:
8772 cnt = count
8773 maxpixel = pixel
8774 if not allpixels:
8775 return (1, bytes([255] * self.n))
8776 return (cnt / allpixels, maxpixel)
8777
8778 %}
8779
8780 //-----------------------------------------------------------------
8781 // MD5 digest of pixmap
8782 //-----------------------------------------------------------------
8783 %pythoncode %{@property%}
8784 ENSURE_OWNERSHIP(digest, """MD5 digest of pixmap (bytes).""")
8785 PyObject *digest()
8786 {
8787 unsigned char digest[16];
8788 fz_md5_pixmap(gctx, (fz_pixmap *) $self, digest);
8789 return PyBytes_FromStringAndSize(digest, 16);
8790 }
8791
8792 //-----------------------------------------------------------------
8793 // get length of one image row
8794 //-----------------------------------------------------------------
8795 %pythoncode %{@property%}
8796 ENSURE_OWNERSHIP(stride, """Length of one image line (width * n).""")
8797 PyObject *stride()
8798 {
8799 return PyLong_FromSize_t((size_t) fz_pixmap_stride(gctx, (fz_pixmap *) $self));
8800 }
8801
8802 //-----------------------------------------------------------------
8803 // x, y, width, height, xres, yres, n
8804 //-----------------------------------------------------------------
8805 %pythoncode %{@property%}
8806 ENSURE_OWNERSHIP(xres, """Resolution in x direction.""")
8807 int xres()
8808 {
8809 fz_pixmap *this_pix = (fz_pixmap *) $self;
8810 return this_pix->xres;
8811 }
8812
8813 %pythoncode %{@property%}
8814 ENSURE_OWNERSHIP(yres, """Resolution in y direction.""")
8815 int yres()
8816 {
8817 fz_pixmap *this_pix = (fz_pixmap *) $self;
8818 return this_pix->yres;
8819 }
8820
8821 %pythoncode %{@property%}
8822 ENSURE_OWNERSHIP(w, """The width.""")
8823 PyObject *w()
8824 {
8825 return PyLong_FromSize_t((size_t) fz_pixmap_width(gctx, (fz_pixmap *) $self));
8826 }
8827
8828 %pythoncode %{@property%}
8829 ENSURE_OWNERSHIP(h, """The height.""")
8830 PyObject *h()
8831 {
8832 return PyLong_FromSize_t((size_t) fz_pixmap_height(gctx, (fz_pixmap *) $self));
8833 }
8834
8835 %pythoncode %{@property%}
8836 ENSURE_OWNERSHIP(x, """x component of Pixmap origin.""")
8837 int x()
8838 {
8839 return fz_pixmap_x(gctx, (fz_pixmap *) $self);
8840 }
8841
8842 %pythoncode %{@property%}
8843 ENSURE_OWNERSHIP(y, """y component of Pixmap origin.""")
8844 int y()
8845 {
8846 return fz_pixmap_y(gctx, (fz_pixmap *) $self);
8847 }
8848
8849 %pythoncode %{@property%}
8850 ENSURE_OWNERSHIP(n, """The size of one pixel.""")
8851 int n()
8852 {
8853 return fz_pixmap_components(gctx, (fz_pixmap *) $self);
8854 }
8855
8856 //-----------------------------------------------------------------
8857 // check alpha channel
8858 //-----------------------------------------------------------------
8859 %pythoncode %{@property%}
8860 ENSURE_OWNERSHIP(alpha, """Indicates presence of alpha channel.""")
8861 int alpha()
8862 {
8863 return fz_pixmap_alpha(gctx, (fz_pixmap *) $self);
8864 }
8865
8866 //-----------------------------------------------------------------
8867 // get colorspace of pixmap
8868 //-----------------------------------------------------------------
8869 %pythoncode %{@property%}
8870 ENSURE_OWNERSHIP(colorspace, """Pixmap Colorspace.""")
8871 struct Colorspace *colorspace()
8872 {
8873 return (struct Colorspace *) fz_pixmap_colorspace(gctx, (fz_pixmap *) $self);
8874 }
8875
8876 //-----------------------------------------------------------------
8877 // return irect of pixmap
8878 //-----------------------------------------------------------------
8879 %pythoncode %{@property%}
8880 ENSURE_OWNERSHIP(irect, """Pixmap bbox - an IRect object.""")
8881 %pythonappend irect %{val = IRect(val)%}
8882 PyObject *irect()
8883 {
8884 return JM_py_from_irect(fz_pixmap_bbox(gctx, (fz_pixmap *) $self));
8885 }
8886
8887 //-----------------------------------------------------------------
8888 // return size of pixmap
8889 //-----------------------------------------------------------------
8890 %pythoncode %{@property%}
8891 ENSURE_OWNERSHIP(size, """Pixmap size.""")
8892 PyObject *size()
8893 {
8894 return PyLong_FromSize_t(fz_pixmap_size(gctx, (fz_pixmap *) $self));
8895 }
8896
8897 //-----------------------------------------------------------------
8898 // samples
8899 //-----------------------------------------------------------------
8900 %pythoncode %{@property%}
8901 ENSURE_OWNERSHIP(samples_mv, """Pixmap samples memoryview.""")
8902 PyObject *samples_mv()
8903 {
8904 fz_pixmap *pm = (fz_pixmap *) $self;
8905 Py_ssize_t s = (Py_ssize_t) pm->w;
8906 s *= pm->h;
8907 s *= pm->n;
8908 return PyMemoryView_FromMemory((char *) pm->samples, s, PyBUF_READ);
8909 }
8910
8911
8912 %pythoncode %{@property%}
8913 ENSURE_OWNERSHIP(samples_ptr, """Pixmap samples pointer.""")
8914 PyObject *samples_ptr()
8915 {
8916 fz_pixmap *pm = (fz_pixmap *) $self;
8917 return PyLong_FromVoidPtr((void *) pm->samples);
8918 }
8919
8920 %pythoncode %{
8921 @property
8922 def samples(self)->bytes:
8923 return bytes(self.samples_mv)
8924
8925 width = w
8926 height = h
8927
8928 def __len__(self):
8929 return self.size
8930
8931 def __repr__(self):
8932 EnsureOwnership(self)
8933 if not type(self) is Pixmap: return
8934 if self.colorspace:
8935 return "Pixmap(%s, %s, %s)" % (self.colorspace.name, self.irect, self.alpha)
8936 else:
8937 return "Pixmap(%s, %s, %s)" % ('None', self.irect, self.alpha)
8938
8939 def __enter__(self):
8940 return self
8941
8942 def __exit__(self, *args):
8943 if getattr(self, "thisown", False):
8944 self.__swig_destroy__(self)
8945
8946 def __del__(self):
8947 if not type(self) is Pixmap:
8948 return
8949 if getattr(self, "thisown", False):
8950 self.__swig_destroy__(self)
8951
8952 %}
8953 }
8954 };
8955
8956 /* fz_colorspace */
8957 struct Colorspace
8958 {
8959 %extend {
8960 ~Colorspace()
8961 {
8962 DEBUGMSG1("Colorspace");
8963 fz_colorspace *this_cs = (fz_colorspace *) $self;
8964 fz_drop_colorspace(gctx, this_cs);
8965 DEBUGMSG2;
8966 }
8967
8968 %pythonprepend Colorspace
8969 %{"""Supported are GRAY, RGB and CMYK."""%}
8970 Colorspace(int type)
8971 {
8972 fz_colorspace *cs = NULL;
8973 switch(type) {
8974 case CS_GRAY:
8975 cs = fz_device_gray(gctx);
8976 break;
8977 case CS_CMYK:
8978 cs = fz_device_cmyk(gctx);
8979 break;
8980 case CS_RGB:
8981 default:
8982 cs = fz_device_rgb(gctx);
8983 break;
8984 }
8985 fz_keep_colorspace(gctx, cs);
8986 return (struct Colorspace *) cs;
8987 }
8988 //-----------------------------------------------------------------
8989 // number of bytes to define color of one pixel
8990 //-----------------------------------------------------------------
8991 %pythoncode %{@property%}
8992 %pythonprepend n %{"""Size of one pixel."""%}
8993 PyObject *n()
8994 {
8995 return Py_BuildValue("i", fz_colorspace_n(gctx, (fz_colorspace *) $self));
8996 }
8997
8998 //-----------------------------------------------------------------
8999 // name of colorspace
9000 //-----------------------------------------------------------------
9001 PyObject *_name()
9002 {
9003 return JM_UnicodeFromStr(fz_colorspace_name(gctx, (fz_colorspace *) $self));
9004 }
9005
9006 %pythoncode %{
9007 @property
9008 def name(self):
9009 """Name of the Colorspace."""
9010
9011 if self.n == 1:
9012 return csGRAY._name()
9013 elif self.n == 3:
9014 return csRGB._name()
9015 elif self.n == 4:
9016 return csCMYK._name()
9017 return self._name()
9018
9019 def __repr__(self):
9020 x = ("", "GRAY", "", "RGB", "CMYK")[self.n]
9021 return "Colorspace(CS_%s) - %s" % (x, self.name)
9022 %}
9023 }
9024 };
9025
9026
9027 /* fz_device wrapper */
9028 %rename(Device) DeviceWrapper;
9029 struct DeviceWrapper
9030 {
9031 %extend {
9032 FITZEXCEPTION(DeviceWrapper, !result)
9033 DeviceWrapper(struct Pixmap *pm, PyObject *clip) {
9034 struct DeviceWrapper *dw = NULL;
9035 fz_try(gctx) {
9036 dw = (struct DeviceWrapper *)calloc(1, sizeof(struct DeviceWrapper));
9037 fz_irect bbox = JM_irect_from_py(clip);
9038 if (fz_is_infinite_irect(bbox))
9039 dw->device = fz_new_draw_device(gctx, fz_identity, (fz_pixmap *) pm);
9040 else
9041 dw->device = fz_new_draw_device_with_bbox(gctx, fz_identity, (fz_pixmap *) pm, &bbox);
9042 }
9043 fz_catch(gctx) {
9044 return NULL;
9045 }
9046 return dw;
9047 }
9048 DeviceWrapper(struct DisplayList *dl) {
9049 struct DeviceWrapper *dw = NULL;
9050 fz_try(gctx) {
9051 dw = (struct DeviceWrapper *)calloc(1, sizeof(struct DeviceWrapper));
9052 dw->device = fz_new_list_device(gctx, (fz_display_list *) dl);
9053 dw->list = (fz_display_list *) dl;
9054 fz_keep_display_list(gctx, (fz_display_list *) dl);
9055 }
9056 fz_catch(gctx) {
9057 return NULL;
9058 }
9059 return dw;
9060 }
9061 DeviceWrapper(struct TextPage *tp, int flags = 0) {
9062 struct DeviceWrapper *dw = NULL;
9063 fz_try(gctx) {
9064 dw = (struct DeviceWrapper *)calloc(1, sizeof(struct DeviceWrapper));
9065 fz_stext_options opts = { 0 };
9066 opts.flags = flags;
9067 dw->device = fz_new_stext_device(gctx, (fz_stext_page *) tp, &opts);
9068 }
9069 fz_catch(gctx) {
9070 return NULL;
9071 }
9072 return dw;
9073 }
9074 ~DeviceWrapper() {
9075 fz_display_list *list = $self->list;
9076 DEBUGMSG1("Device");
9077 fz_close_device(gctx, $self->device);
9078 fz_drop_device(gctx, $self->device);
9079 DEBUGMSG2;
9080 if(list)
9081 {
9082 DEBUGMSG1("DisplayList after Device");
9083 fz_drop_display_list(gctx, list);
9084 DEBUGMSG2;
9085 }
9086 }
9087 }
9088 };
9089
9090 //------------------------------------------------------------------------
9091 // fz_outline
9092 //------------------------------------------------------------------------
9093 %nodefaultctor;
9094 struct Outline {
9095 %immutable;
9096 %extend {
9097 ~Outline()
9098 {
9099 DEBUGMSG1("Outline");
9100 fz_outline *this_ol = (fz_outline *) $self;
9101 fz_drop_outline(gctx, this_ol);
9102 DEBUGMSG2;
9103 }
9104
9105 %pythoncode %{@property%}
9106 PyObject *uri()
9107 {
9108 fz_outline *ol = (fz_outline *) $self;
9109 return JM_UnicodeFromStr(ol->uri);
9110 }
9111
9112 /* `%newobject foo;` is equivalent to wrapping C fn in python like:
9113 ret = _foo()
9114 ret.thisown=true
9115 return ret.
9116 */
9117 %newobject next;
9118 %pythoncode %{@property%}
9119 struct Outline *next()
9120 {
9121 fz_outline *ol = (fz_outline *) $self;
9122 fz_outline *next_ol = ol->next;
9123 if (!next_ol) return NULL;
9124 next_ol = fz_keep_outline(gctx, next_ol);
9125 return (struct Outline *) next_ol;
9126 }
9127
9128 %newobject down;
9129 %pythoncode %{@property%}
9130 struct Outline *down()
9131 {
9132 fz_outline *ol = (fz_outline *) $self;
9133 fz_outline *down_ol = ol->down;
9134 if (!down_ol) return NULL;
9135 down_ol = fz_keep_outline(gctx, down_ol);
9136 return (struct Outline *) down_ol;
9137 }
9138
9139 %pythoncode %{@property%}
9140 PyObject *is_external()
9141 {
9142 fz_outline *ol = (fz_outline *) $self;
9143 if (!ol->uri) Py_RETURN_FALSE;
9144 return JM_BOOL(fz_is_external_link(gctx, ol->uri));
9145 }
9146
9147 %pythoncode %{@property%}
9148 int page()
9149 {
9150 fz_outline *ol = (fz_outline *) $self;
9151 return ol->page.page;
9152 }
9153
9154 %pythoncode %{@property%}
9155 float x()
9156 {
9157 fz_outline *ol = (fz_outline *) $self;
9158 return ol->x;
9159 }
9160
9161 %pythoncode %{@property%}
9162 float y()
9163 {
9164 fz_outline *ol = (fz_outline *) $self;
9165 return ol->y;
9166 }
9167
9168 %pythoncode %{@property%}
9169 PyObject *title()
9170 {
9171 fz_outline *ol = (fz_outline *) $self;
9172 return JM_UnicodeFromStr(ol->title);
9173 }
9174
9175 %pythoncode %{@property%}
9176 PyObject *is_open()
9177 {
9178 fz_outline *ol = (fz_outline *) $self;
9179 return JM_BOOL(ol->is_open);
9180 }
9181
9182 %pythoncode %{
9183 @property
9184 def dest(self):
9185 '''outline destination details'''
9186 return linkDest(self, None)
9187
9188 def __del__(self):
9189 if not isinstance(self, Outline):
9190 return
9191 if getattr(self, "thisown", False):
9192 self.__swig_destroy__(self)
9193 %}
9194 }
9195 };
9196 %clearnodefaultctor;
9197
9198
9199 //------------------------------------------------------------------------
9200 // Annotation
9201 //------------------------------------------------------------------------
9202 %nodefaultctor;
9203 struct Annot
9204 {
9205 %extend
9206 {
9207 ~Annot()
9208 {
9209 DEBUGMSG1("Annot");
9210 pdf_annot *this_annot = (pdf_annot *) $self;
9211 pdf_drop_annot(gctx, this_annot);
9212 DEBUGMSG2;
9213 }
9214 //----------------------------------------------------------------
9215 // annotation rectangle
9216 //----------------------------------------------------------------
9217 %pythoncode %{@property%}
9218 PARENTCHECK(rect, """annotation rectangle""")
9219 %pythonappend rect %{
9220 val = Rect(val)
9221 val *= self.parent.derotation_matrix
9222 %}
9223 PyObject *
9224 rect()
9225 {
9226 fz_rect r = pdf_bound_annot(gctx, (pdf_annot *) $self);
9227 return JM_py_from_rect(r);
9228 }
9229
9230 %pythoncode %{@property%}
9231 PARENTCHECK(rect_delta, """annotation delta values to rectangle""")
9232 PyObject *
9233 rect_delta()
9234 {
9235 PyObject *rc=NULL;
9236 float d;
9237 fz_try(gctx) {
9238 pdf_obj *annot_obj = pdf_annot_obj(gctx, (pdf_annot *) $self);
9239 pdf_obj *arr = pdf_dict_get(gctx, annot_obj, PDF_NAME(RD));
9240 int i, n = pdf_array_len(gctx, arr);
9241 if (n != 4) {
9242 rc = Py_BuildValue("s", NULL);
9243 } else {
9244 rc = PyTuple_New(4);
9245 for (i = 0; i < n; i++) {
9246 d = pdf_to_real(gctx, pdf_array_get(gctx, arr, i));
9247 if (i == 2 || i == 3) d *= -1;
9248 PyTuple_SET_ITEM(rc, i, Py_BuildValue("f", d));
9249 }
9250 }
9251 }
9252 fz_catch(gctx) {
9253 Py_RETURN_NONE;
9254 }
9255 return rc;
9256 }
9257
9258 //----------------------------------------------------------------
9259 // annotation xref number
9260 //----------------------------------------------------------------
9261 PARENTCHECK(xref, """annotation xref""")
9262 %pythoncode %{@property%}
9263 PyObject *xref()
9264 {
9265 pdf_annot *annot = (pdf_annot *) $self;
9266 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9267 return Py_BuildValue("i", pdf_to_num(gctx, annot_obj));
9268 }
9269
9270 //----------------------------------------------------------------
9271 // annotation get IRT xref number
9272 //----------------------------------------------------------------
9273 PARENTCHECK(irt_xref, """annotation IRT xref""")
9274 %pythoncode %{@property%}
9275 PyObject *irt_xref()
9276 {
9277 pdf_annot *annot = (pdf_annot *) $self;
9278 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9279 pdf_obj *irt = pdf_dict_get(gctx, annot_obj, PDF_NAME(IRT));
9280 if (!irt) return PyLong_FromLong(0);
9281 return PyLong_FromLong((long) pdf_to_num(gctx, irt));
9282 }
9283
9284 //----------------------------------------------------------------
9285 // annotation set IRT xref number
9286 //----------------------------------------------------------------
9287 FITZEXCEPTION(set_irt_xref, !result)
9288 PARENTCHECK(set_irt_xref, """Set annotation IRT xref""")
9289 PyObject *set_irt_xref(int xref)
9290 {
9291 fz_try(gctx) {
9292 pdf_annot *annot = (pdf_annot *) $self;
9293 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9294 pdf_page *page = pdf_annot_page(gctx, annot);
9295 if (!INRANGE(xref, 1, pdf_xref_len(gctx, page->doc) - 1)) {
9296 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
9297 }
9298 pdf_obj *irt = pdf_new_indirect(gctx, page->doc, xref, 0);
9299 pdf_obj *subt = pdf_dict_get(gctx, irt, PDF_NAME(Subtype));
9300 int irt_subt = pdf_annot_type_from_string(gctx, pdf_to_name(gctx, subt));
9301 if (irt_subt < 0) {
9302 pdf_drop_obj(gctx, irt);
9303 RAISEPY(gctx, MSG_IS_NO_ANNOT, PyExc_ValueError);
9304 }
9305 pdf_dict_put_drop(gctx, annot_obj, PDF_NAME(IRT), irt);
9306 }
9307 fz_catch(gctx) {
9308 return NULL;
9309 }
9310 Py_RETURN_NONE;
9311 }
9312
9313 //----------------------------------------------------------------
9314 // annotation get AP/N Matrix
9315 //----------------------------------------------------------------
9316 PARENTCHECK(apn_matrix, """annotation appearance matrix""")
9317 %pythonappend apn_matrix %{val = Matrix(val)%}
9318 %pythoncode %{@property%}
9319 PyObject *
9320 apn_matrix()
9321 {
9322 pdf_annot *annot = (pdf_annot *) $self;
9323 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9324 pdf_obj *ap = pdf_dict_getl(gctx, annot_obj, PDF_NAME(AP),
9325 PDF_NAME(N), NULL);
9326 if (!ap)
9327 return JM_py_from_matrix(fz_identity);
9328 fz_matrix mat = pdf_dict_get_matrix(gctx, ap, PDF_NAME(Matrix));
9329 return JM_py_from_matrix(mat);
9330 }
9331
9332
9333 //----------------------------------------------------------------
9334 // annotation get AP/N BBox
9335 //----------------------------------------------------------------
9336 PARENTCHECK(apn_bbox, """annotation appearance bbox""")
9337 %pythonappend apn_bbox %{
9338 val = Rect(val) * self.parent.transformation_matrix
9339 val *= self.parent.derotation_matrix%}
9340 %pythoncode %{@property%}
9341 PyObject *
9342 apn_bbox()
9343 {
9344 pdf_annot *annot = (pdf_annot *) $self;
9345 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9346 pdf_obj *ap = pdf_dict_getl(gctx, annot_obj, PDF_NAME(AP),
9347 PDF_NAME(N), NULL);
9348 if (!ap)
9349 return JM_py_from_rect(fz_infinite_rect);
9350 fz_rect rect = pdf_dict_get_rect(gctx, ap, PDF_NAME(BBox));
9351 return JM_py_from_rect(rect);
9352 }
9353
9354
9355 //----------------------------------------------------------------
9356 // annotation set AP/N Matrix
9357 //----------------------------------------------------------------
9358 FITZEXCEPTION(set_apn_matrix, !result)
9359 PARENTCHECK(set_apn_matrix, """Set annotation appearance matrix.""")
9360 PyObject *
9361 set_apn_matrix(PyObject *matrix)
9362 {
9363 pdf_annot *annot = (pdf_annot *) $self;
9364 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9365 fz_try(gctx) {
9366 pdf_obj *ap = pdf_dict_getl(gctx, annot_obj, PDF_NAME(AP),
9367 PDF_NAME(N), NULL);
9368 if (!ap) {
9369 RAISEPY(gctx, MSG_BAD_APN, PyExc_RuntimeError);
9370 }
9371 fz_matrix mat = JM_matrix_from_py(matrix);
9372 pdf_dict_put_matrix(gctx, ap, PDF_NAME(Matrix), mat);
9373 }
9374 fz_catch(gctx) {
9375 return NULL;
9376 }
9377 Py_RETURN_NONE;
9378 }
9379
9380
9381 //----------------------------------------------------------------
9382 // annotation set AP/N BBox
9383 //----------------------------------------------------------------
9384 FITZEXCEPTION(set_apn_bbox, !result)
9385 %pythonprepend set_apn_bbox %{
9386 """Set annotation appearance bbox."""
9387
9388 CheckParent(self)
9389 page = self.parent
9390 rot = page.rotation_matrix
9391 mat = page.transformation_matrix
9392 bbox *= rot * ~mat
9393 %}
9394 PyObject *
9395 set_apn_bbox(PyObject *bbox)
9396 {
9397 pdf_annot *annot = (pdf_annot *) $self;
9398 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9399 fz_try(gctx) {
9400 pdf_obj *ap = pdf_dict_getl(gctx, annot_obj, PDF_NAME(AP),
9401 PDF_NAME(N), NULL);
9402 if (!ap) {
9403 RAISEPY(gctx, MSG_BAD_APN, PyExc_RuntimeError);
9404 }
9405 fz_rect rect = JM_rect_from_py(bbox);
9406 pdf_dict_put_rect(gctx, ap, PDF_NAME(BBox), rect);
9407 }
9408 fz_catch(gctx) {
9409 return NULL;
9410 }
9411 Py_RETURN_NONE;
9412 }
9413
9414
9415 //----------------------------------------------------------------
9416 // annotation show blend mode (/BM)
9417 //----------------------------------------------------------------
9418 %pythoncode %{@property%}
9419 PARENTCHECK(blendmode, """annotation BlendMode""")
9420 PyObject *blendmode()
9421 {
9422 PyObject *blend_mode = NULL;
9423 fz_try(gctx) {
9424 pdf_annot *annot = (pdf_annot *) $self;
9425 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9426 pdf_obj *obj, *obj1, *obj2;
9427 obj = pdf_dict_get(gctx, annot_obj, PDF_NAME(BM));
9428 if (obj) {
9429 blend_mode = JM_UnicodeFromStr(pdf_to_name(gctx, obj));
9430 goto finished;
9431 }
9432 // loop through the /AP/N/Resources/ExtGState objects
9433 obj = pdf_dict_getl(gctx, annot_obj, PDF_NAME(AP),
9434 PDF_NAME(N),
9435 PDF_NAME(Resources),
9436 PDF_NAME(ExtGState),
9437 NULL);
9438
9439 if (pdf_is_dict(gctx, obj)) {
9440 int i, j, m, n = pdf_dict_len(gctx, obj);
9441 for (i = 0; i < n; i++) {
9442 obj1 = pdf_dict_get_val(gctx, obj, i);
9443 if (pdf_is_dict(gctx, obj1)) {
9444 m = pdf_dict_len(gctx, obj1);
9445 for (j = 0; j < m; j++) {
9446 obj2 = pdf_dict_get_key(gctx, obj1, j);
9447 if (pdf_objcmp(gctx, obj2, PDF_NAME(BM)) == 0) {
9448 blend_mode = JM_UnicodeFromStr(pdf_to_name(gctx, pdf_dict_get_val(gctx, obj1, j)));
9449 goto finished;
9450 }
9451 }
9452 }
9453 }
9454 }
9455 finished:;
9456 }
9457 fz_catch(gctx) {
9458 Py_RETURN_NONE;
9459 }
9460 if (blend_mode) return blend_mode;
9461 Py_RETURN_NONE;
9462 }
9463
9464
9465 //----------------------------------------------------------------
9466 // annotation set blend mode (/BM)
9467 //----------------------------------------------------------------
9468 FITZEXCEPTION(set_blendmode, !result)
9469 PARENTCHECK(set_blendmode, """Set annotation BlendMode.""")
9470 PyObject *
9471 set_blendmode(char *blend_mode)
9472 {
9473 fz_try(gctx) {
9474 pdf_annot *annot = (pdf_annot *) $self;
9475 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9476 pdf_dict_put_name(gctx, annot_obj, PDF_NAME(BM), blend_mode);
9477 }
9478 fz_catch(gctx) {
9479 return NULL;
9480 }
9481 Py_RETURN_NONE;
9482 }
9483
9484
9485 //----------------------------------------------------------------
9486 // annotation get optional content
9487 //----------------------------------------------------------------
9488 FITZEXCEPTION(get_oc, !result)
9489 PARENTCHECK(get_oc, """Get annotation optional content reference.""")
9490 PyObject *get_oc()
9491 {
9492 int oc = 0;
9493 fz_try(gctx) {
9494 pdf_annot *annot = (pdf_annot *) $self;
9495 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9496 pdf_obj *obj = pdf_dict_get(gctx, annot_obj, PDF_NAME(OC));
9497 if (obj) {
9498 oc = pdf_to_num(gctx, obj);
9499 }
9500 }
9501 fz_catch(gctx) {
9502 return NULL;
9503 }
9504 return Py_BuildValue("i", oc);
9505 }
9506
9507
9508 //----------------------------------------------------------------
9509 // annotation set open
9510 //----------------------------------------------------------------
9511 FITZEXCEPTION(set_open, !result)
9512 PARENTCHECK(set_open, """Set 'open' status of annotation or its Popup.""")
9513 PyObject *set_open(int is_open)
9514 {
9515 fz_try(gctx) {
9516 pdf_annot *annot = (pdf_annot *) $self;
9517 pdf_set_annot_is_open(gctx, annot, is_open);
9518 }
9519 fz_catch(gctx) {
9520 return NULL;
9521 }
9522 Py_RETURN_NONE;
9523 }
9524
9525
9526 //----------------------------------------------------------------
9527 // annotation inquiry: is open
9528 //----------------------------------------------------------------
9529 FITZEXCEPTION(is_open, !result)
9530 PARENTCHECK(is_open, """Get 'open' status of annotation or its Popup.""")
9531 %pythoncode %{@property%}
9532 PyObject *
9533 is_open()
9534 {
9535 int is_open;
9536 fz_try(gctx) {
9537 pdf_annot *annot = (pdf_annot *) $self;
9538 is_open = pdf_annot_is_open(gctx, annot);
9539 }
9540 fz_catch(gctx) {
9541 return NULL;
9542 }
9543 return JM_BOOL(is_open);
9544 }
9545
9546
9547 //----------------------------------------------------------------
9548 // annotation inquiry: has Popup
9549 //----------------------------------------------------------------
9550 FITZEXCEPTION(has_popup, !result)
9551 PARENTCHECK(has_popup, """Check if annotation has a Popup.""")
9552 %pythoncode %{@property%}
9553 PyObject *
9554 has_popup()
9555 {
9556 int has_popup = 0;
9557 fz_try(gctx) {
9558 pdf_annot *annot = (pdf_annot *) $self;
9559 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9560 pdf_obj *obj = pdf_dict_get(gctx, annot_obj, PDF_NAME(Popup));
9561 if (obj) has_popup = 1;
9562 }
9563 fz_catch(gctx) {
9564 return NULL;
9565 }
9566 return JM_BOOL(has_popup);
9567 }
9568
9569
9570 //----------------------------------------------------------------
9571 // annotation set Popup
9572 //----------------------------------------------------------------
9573 FITZEXCEPTION(set_popup, !result)
9574 PARENTCHECK(set_popup, """Create annotation 'Popup' or update rectangle.""")
9575 PyObject *
9576 set_popup(PyObject *rect)
9577 {
9578 fz_try(gctx) {
9579 pdf_annot *annot = (pdf_annot *) $self;
9580 pdf_page *pdfpage = pdf_annot_page(gctx, annot);
9581 fz_matrix rot = JM_rotate_page_matrix(gctx, pdfpage);
9582 fz_rect r = fz_transform_rect(JM_rect_from_py(rect), rot);
9583 pdf_set_annot_popup(gctx, annot, r);
9584 }
9585 fz_catch(gctx) {
9586 return NULL;
9587 }
9588 Py_RETURN_NONE;
9589 }
9590
9591 //----------------------------------------------------------------
9592 // annotation Popup rectangle
9593 //----------------------------------------------------------------
9594 FITZEXCEPTION(popup_rect, !result)
9595 PARENTCHECK(popup_rect, """annotation 'Popup' rectangle""")
9596 %pythoncode %{@property%}
9597 %pythonappend popup_rect %{
9598 val = Rect(val) * self.parent.transformation_matrix
9599 val *= self.parent.derotation_matrix%}
9600 PyObject *
9601 popup_rect()
9602 {
9603 fz_rect rect = fz_infinite_rect;
9604 fz_try(gctx) {
9605 pdf_annot *annot = (pdf_annot *) $self;
9606 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9607 pdf_obj *obj = pdf_dict_get(gctx, annot_obj, PDF_NAME(Popup));
9608 if (obj) {
9609 rect = pdf_dict_get_rect(gctx, obj, PDF_NAME(Rect));
9610 }
9611 }
9612 fz_catch(gctx) {
9613 return NULL;
9614 }
9615 return JM_py_from_rect(rect);
9616 }
9617
9618
9619 //----------------------------------------------------------------
9620 // annotation Popup xref
9621 //----------------------------------------------------------------
9622 FITZEXCEPTION(popup_xref, !result)
9623 PARENTCHECK(popup_xref, """annotation 'Popup' xref""")
9624 %pythoncode %{@property%}
9625 PyObject *
9626 popup_xref()
9627 {
9628 int xref = 0;
9629 fz_try(gctx) {
9630 pdf_annot *annot = (pdf_annot *) $self;
9631 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9632 pdf_obj *obj = pdf_dict_get(gctx, annot_obj, PDF_NAME(Popup));
9633 if (obj) {
9634 xref = pdf_to_num(gctx, obj);
9635 }
9636 }
9637 fz_catch(gctx) {
9638 return NULL;
9639 }
9640 return Py_BuildValue("i", xref);
9641 }
9642
9643
9644 //----------------------------------------------------------------
9645 // annotation set optional content
9646 //----------------------------------------------------------------
9647 FITZEXCEPTION(set_oc, !result)
9648 PARENTCHECK(set_oc, """Set / remove annotation OC xref.""")
9649 PyObject *
9650 set_oc(int oc=0)
9651 {
9652 fz_try(gctx) {
9653 pdf_annot *annot = (pdf_annot *) $self;
9654 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9655 if (!oc) {
9656 pdf_dict_del(gctx, annot_obj, PDF_NAME(OC));
9657 } else {
9658 JM_add_oc_object(gctx, pdf_get_bound_document(gctx, annot_obj), annot_obj, oc);
9659 }
9660 }
9661 fz_catch(gctx) {
9662 return NULL;
9663 }
9664 Py_RETURN_NONE;
9665 }
9666
9667
9668 %pythoncode%{@property%}
9669 %pythonprepend language %{"""annotation language"""%}
9670 PyObject *language()
9671 {
9672 pdf_annot *this_annot = (pdf_annot *) $self;
9673 fz_text_language lang = pdf_annot_language(gctx, this_annot);
9674 char buf[8];
9675 if (lang == FZ_LANG_UNSET) Py_RETURN_NONE;
9676 return Py_BuildValue("s", fz_string_from_text_language(buf, lang));
9677 }
9678
9679 //----------------------------------------------------------------
9680 // annotation set language (/Lang)
9681 //----------------------------------------------------------------
9682 FITZEXCEPTION(set_language, !result)
9683 PARENTCHECK(set_language, """Set annotation language.""")
9684 PyObject *set_language(char *language=NULL)
9685 {
9686 pdf_annot *this_annot = (pdf_annot *) $self;
9687 fz_try(gctx) {
9688 fz_text_language lang;
9689 if (!language)
9690 lang = FZ_LANG_UNSET;
9691 else
9692 lang = fz_text_language_from_string(language);
9693 pdf_set_annot_language(gctx, this_annot, lang);
9694 }
9695 fz_catch(gctx) {
9696 return NULL;
9697 }
9698 Py_RETURN_NONE;
9699 }
9700
9701
9702 //----------------------------------------------------------------
9703 // annotation get decompressed appearance stream source
9704 //----------------------------------------------------------------
9705 FITZEXCEPTION(_getAP, !result)
9706 PyObject *
9707 _getAP()
9708 {
9709 PyObject *r = NULL;
9710 fz_buffer *res = NULL;
9711 fz_var(res);
9712 fz_try(gctx) {
9713 pdf_annot *annot = (pdf_annot *) $self;
9714 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9715 pdf_obj *ap = pdf_dict_getl(gctx, annot_obj, PDF_NAME(AP),
9716 PDF_NAME(N), NULL);
9717
9718 if (pdf_is_stream(gctx, ap)) res = pdf_load_stream(gctx, ap);
9719 if (res) {
9720 r = JM_BinFromBuffer(gctx, res);
9721 }
9722 }
9723 fz_always(gctx) {
9724 fz_drop_buffer(gctx, res);
9725 }
9726 fz_catch(gctx) {
9727 Py_RETURN_NONE;
9728 }
9729 if (!r) Py_RETURN_NONE;
9730 return r;
9731 }
9732
9733 //----------------------------------------------------------------
9734 // annotation update /AP stream
9735 //----------------------------------------------------------------
9736 FITZEXCEPTION(_setAP, !result)
9737 PyObject *
9738 _setAP(PyObject *buffer, int rect=0)
9739 {
9740 fz_buffer *res = NULL;
9741 fz_var(res);
9742 fz_try(gctx) {
9743 pdf_annot *annot = (pdf_annot *) $self;
9744 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9745 pdf_page *page = pdf_annot_page(gctx, annot);
9746 pdf_obj *apobj = pdf_dict_getl(gctx, annot_obj, PDF_NAME(AP),
9747 PDF_NAME(N), NULL);
9748 if (!apobj) {
9749 RAISEPY(gctx, MSG_BAD_APN, PyExc_RuntimeError);
9750 }
9751 if (!pdf_is_stream(gctx, apobj)) {
9752 RAISEPY(gctx, MSG_BAD_APN, PyExc_RuntimeError);
9753 }
9754 res = JM_BufferFromBytes(gctx, buffer);
9755 if (!res) {
9756 RAISEPY(gctx, MSG_BAD_BUFFER, PyExc_ValueError);
9757 }
9758 JM_update_stream(gctx, page->doc, apobj, res, 1);
9759 if (rect) {
9760 fz_rect bbox = pdf_dict_get_rect(gctx, annot_obj, PDF_NAME(Rect));
9761 pdf_dict_put_rect(gctx, apobj, PDF_NAME(BBox), bbox);
9762 }
9763 }
9764 fz_always(gctx) {
9765 fz_drop_buffer(gctx, res);
9766 }
9767 fz_catch(gctx) {
9768 return NULL;
9769 }
9770 Py_RETURN_NONE;
9771 }
9772
9773
9774 //----------------------------------------------------------------
9775 // redaction annotation get values
9776 //----------------------------------------------------------------
9777 FITZEXCEPTION(_get_redact_values, !result)
9778 %pythonappend _get_redact_values %{
9779 if not val:
9780 return val
9781 val["rect"] = self.rect
9782 text_color, fontname, fontsize = TOOLS._parse_da(self)
9783 val["text_color"] = text_color
9784 val["fontname"] = fontname
9785 val["fontsize"] = fontsize
9786 fill = self.colors["fill"]
9787 val["fill"] = fill
9788
9789 %}
9790 PyObject *
9791 _get_redact_values()
9792 {
9793 pdf_annot *annot = (pdf_annot *) $self;
9794 if (pdf_annot_type(gctx, annot) != PDF_ANNOT_REDACT)
9795 Py_RETURN_NONE;
9796 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9797 PyObject *values = PyDict_New();
9798 pdf_obj *obj = NULL;
9799 const char *text = NULL;
9800 fz_try(gctx) {
9801 obj = pdf_dict_gets(gctx, annot_obj, "RO");
9802 if (obj) {
9803 JM_Warning("Ignoring redaction key '/RO'.");
9804 int xref = pdf_to_num(gctx, obj);
9805 DICT_SETITEM_DROP(values, dictkey_xref, Py_BuildValue("i", xref));
9806 }
9807 obj = pdf_dict_gets(gctx, annot_obj, "OverlayText");
9808 if (obj) {
9809 text = pdf_to_text_string(gctx, obj);
9810 DICT_SETITEM_DROP(values, dictkey_text, JM_UnicodeFromStr(text));
9811 } else {
9812 DICT_SETITEM_DROP(values, dictkey_text, Py_BuildValue("s", ""));
9813 }
9814 obj = pdf_dict_get(gctx, annot_obj, PDF_NAME(Q));
9815 int align = 0;
9816 if (obj) {
9817 align = pdf_to_int(gctx, obj);
9818 }
9819 DICT_SETITEM_DROP(values, dictkey_align, Py_BuildValue("i", align));
9820 }
9821 fz_catch(gctx) {
9822 Py_DECREF(values);
9823 return NULL;
9824 }
9825 return values;
9826 }
9827
9828 //----------------------------------------------------------------
9829 // annotation get TextPage
9830 //----------------------------------------------------------------
9831 %pythonappend get_textpage %{
9832 if val:
9833 val.thisown = True
9834 %}
9835 FITZEXCEPTION(get_textpage, !result)
9836 PARENTCHECK(get_textpage, """Make annotation TextPage.""")
9837 struct TextPage *
9838 get_textpage(PyObject *clip=NULL, int flags = 0)
9839 {
9840 fz_stext_page *textpage=NULL;
9841 fz_stext_options options = { 0 };
9842 options.flags = flags;
9843 fz_try(gctx) {
9844 pdf_annot *annot = (pdf_annot *) $self;
9845 textpage = pdf_new_stext_page_from_annot(gctx, annot, &options);
9846 }
9847 fz_catch(gctx) {
9848 return NULL;
9849 }
9850 return (struct TextPage *) textpage;
9851 }
9852
9853
9854 //----------------------------------------------------------------
9855 // annotation set name
9856 //----------------------------------------------------------------
9857 FITZEXCEPTION(set_name, !result)
9858 PARENTCHECK(set_name, """Set /Name (icon) of annotation.""")
9859 PyObject *
9860 set_name(char *name)
9861 {
9862 fz_try(gctx) {
9863 pdf_annot *annot = (pdf_annot *) $self;
9864 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9865 pdf_dict_put_name(gctx, annot_obj, PDF_NAME(Name), name);
9866 }
9867 fz_catch(gctx) {
9868 return NULL;
9869 }
9870 Py_RETURN_NONE;
9871 }
9872
9873
9874 //----------------------------------------------------------------
9875 // annotation set rectangle
9876 //----------------------------------------------------------------
9877 PARENTCHECK(set_rect, """Set annotation rectangle.""")
9878 FITZEXCEPTION(set_rect, !result)
9879 PyObject *
9880 set_rect(PyObject *rect)
9881 {
9882 pdf_annot *annot = (pdf_annot *) $self;
9883 int type = pdf_annot_type(gctx, annot);
9884 int err_source = 0; // what raised the error
9885 fz_var(err_source);
9886 fz_try(gctx) {
9887 pdf_page *pdfpage = pdf_annot_page(gctx, annot);
9888 fz_matrix rot = JM_rotate_page_matrix(gctx, pdfpage);
9889 fz_rect r = fz_transform_rect(JM_rect_from_py(rect), rot);
9890 if (fz_is_empty_rect(r) || fz_is_infinite_rect(r)) {
9891 RAISEPY(gctx, MSG_BAD_RECT, PyExc_ValueError);
9892 }
9893 err_source = 1; // indicate that error was from MuPDF
9894 pdf_set_annot_rect(gctx, annot, r);
9895 }
9896 fz_catch(gctx) {
9897 if (err_source == 0) {
9898 return NULL;
9899 }
9900 PySys_WriteStderr("cannot set rect: '%s'\n", fz_caught_message(gctx));
9901 Py_RETURN_FALSE;
9902 }
9903 Py_RETURN_NONE;
9904 }
9905
9906
9907 //----------------------------------------------------------------
9908 // annotation set rotation
9909 //----------------------------------------------------------------
9910 PARENTCHECK(set_rotation, """Set annotation rotation.""")
9911 PyObject *
9912 set_rotation(int rotate=0)
9913 {
9914 pdf_annot *annot = (pdf_annot *) $self;
9915 int type = pdf_annot_type(gctx, annot);
9916 switch (type)
9917 {
9918 case PDF_ANNOT_CARET: break;
9919 case PDF_ANNOT_CIRCLE: break;
9920 case PDF_ANNOT_FREE_TEXT: break;
9921 case PDF_ANNOT_FILE_ATTACHMENT: break;
9922 case PDF_ANNOT_INK: break;
9923 case PDF_ANNOT_LINE: break;
9924 case PDF_ANNOT_POLY_LINE: break;
9925 case PDF_ANNOT_POLYGON: break;
9926 case PDF_ANNOT_SQUARE: break;
9927 case PDF_ANNOT_STAMP: break;
9928 case PDF_ANNOT_TEXT: break;
9929 default: Py_RETURN_NONE;
9930 }
9931 int rot = rotate;
9932 while (rot < 0) rot += 360;
9933 while (rot >= 360) rot -= 360;
9934 if (type == PDF_ANNOT_FREE_TEXT && rot % 90 != 0)
9935 rot = 0;
9936 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9937 pdf_dict_put_int(gctx, annot_obj, PDF_NAME(Rotate), rot);
9938 Py_RETURN_NONE;
9939 }
9940
9941
9942 //----------------------------------------------------------------
9943 // annotation get rotation
9944 //----------------------------------------------------------------
9945 %pythoncode %{@property%}
9946 PARENTCHECK(rotation, """annotation rotation""")
9947 int rotation()
9948 {
9949 pdf_annot *annot = (pdf_annot *) $self;
9950 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9951 pdf_obj *rotation = pdf_dict_get(gctx, annot_obj, PDF_NAME(Rotate));
9952 if (!rotation) return -1;
9953 return pdf_to_int(gctx, rotation);
9954 }
9955
9956
9957 //----------------------------------------------------------------
9958 // annotation vertices (for "Line", "Polgon", "Ink", etc.
9959 //----------------------------------------------------------------
9960 PARENTCHECK(vertices, """annotation vertex points""")
9961 %pythoncode %{@property%}
9962 PyObject *vertices()
9963 {
9964 PyObject *res = NULL, *res1 = NULL;
9965 pdf_obj *o, *o1;
9966 pdf_annot *annot = (pdf_annot *) $self;
9967 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
9968 pdf_page *page = pdf_annot_page(gctx, annot);
9969 int i, j;
9970 fz_point point; // point object to work with
9971 fz_matrix page_ctm; // page transformation matrix
9972 pdf_page_transform(gctx, page, NULL, &page_ctm);
9973 fz_matrix derot = JM_derotate_page_matrix(gctx, page);
9974 page_ctm = fz_concat(page_ctm, derot);
9975
9976 //----------------------------------------------------------------
9977 // The following objects occur in different annotation types.
9978 // So we are sure that (!o) occurs at most once.
9979 // Every pair of floats is one point, that needs to be separately
9980 // transformed with the page transformation matrix.
9981 //----------------------------------------------------------------
9982 o = pdf_dict_get(gctx, annot_obj, PDF_NAME(Vertices));
9983 if (o) goto weiter;
9984 o = pdf_dict_get(gctx, annot_obj, PDF_NAME(L));
9985 if (o) goto weiter;
9986 o = pdf_dict_get(gctx, annot_obj, PDF_NAME(QuadPoints));
9987 if (o) goto weiter;
9988 o = pdf_dict_gets(gctx, annot_obj, "CL");
9989 if (o) goto weiter;
9990 o = pdf_dict_get(gctx, annot_obj, PDF_NAME(InkList));
9991 if (o) goto inklist;
9992 Py_RETURN_NONE;
9993
9994 // handle lists with 1-level depth --------------------------------
9995 weiter:;
9996 res = PyList_New(0); // create Python list
9997 for (i = 0; i < pdf_array_len(gctx, o); i += 2)
9998 {
9999 point.x = pdf_to_real(gctx, pdf_array_get(gctx, o, i));
10000 point.y = pdf_to_real(gctx, pdf_array_get(gctx, o, i+1));
10001 point = fz_transform_point(point, page_ctm);
10002 LIST_APPEND_DROP(res, Py_BuildValue("ff", point.x, point.y));
10003 }
10004 return res;
10005
10006 // InkList has 2-level lists --------------------------------------
10007 inklist:;
10008 res = PyList_New(0);
10009 for (i = 0; i < pdf_array_len(gctx, o); i++)
10010 {
10011 res1 = PyList_New(0);
10012 o1 = pdf_array_get(gctx, o, i);
10013 for (j = 0; j < pdf_array_len(gctx, o1); j += 2)
10014 {
10015 point.x = pdf_to_real(gctx, pdf_array_get(gctx, o1, j));
10016 point.y = pdf_to_real(gctx, pdf_array_get(gctx, o1, j+1));
10017 point = fz_transform_point(point, page_ctm);
10018 LIST_APPEND_DROP(res1, Py_BuildValue("ff", point.x, point.y));
10019 }
10020 LIST_APPEND_DROP(res, res1);
10021 }
10022 return res;
10023 }
10024
10025 //----------------------------------------------------------------
10026 // annotation colors
10027 //----------------------------------------------------------------
10028 %pythoncode %{@property%}
10029 PARENTCHECK(colors, """Color definitions.""")
10030 PyObject *colors()
10031 {
10032 pdf_annot *annot = (pdf_annot *) $self;
10033 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10034 return JM_annot_colors(gctx, annot_obj);
10035 }
10036
10037 //----------------------------------------------------------------
10038 // annotation update appearance
10039 //----------------------------------------------------------------
10040 PyObject *_update_appearance(float opacity=-1,
10041 char *blend_mode=NULL,
10042 PyObject *fill_color=NULL,
10043 int rotate = -1)
10044 {
10045 pdf_annot *annot = (pdf_annot *) $self;
10046 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10047 pdf_page *page = pdf_annot_page(gctx, annot);
10048 pdf_document *pdf = page->doc;
10049 int type = pdf_annot_type(gctx, annot);
10050 float fcol[4] = {1,1,1,1}; // std fill color: white
10051 int i, nfcol = 0; // number of color components
10052 JM_color_FromSequence(fill_color, &nfcol, fcol);
10053 fz_try(gctx) {
10054 // remove fill color from unsupported annots
10055 // or if so requested
10056 if ((type != PDF_ANNOT_SQUARE
10057 && type != PDF_ANNOT_CIRCLE
10058 && type != PDF_ANNOT_LINE
10059 && type != PDF_ANNOT_POLY_LINE
10060 && type != PDF_ANNOT_POLYGON
10061 )
10062 || nfcol == 0
10063 ) {
10064 pdf_dict_del(gctx, annot_obj, PDF_NAME(IC));
10065 } else if (nfcol > 0) {
10066 pdf_set_annot_interior_color(gctx, annot, nfcol, fcol);
10067 }
10068
10069 int insert_rot = (rotate >= 0) ? 1 : 0;
10070 switch (type) {
10071 case PDF_ANNOT_CARET:
10072 case PDF_ANNOT_CIRCLE:
10073 case PDF_ANNOT_FREE_TEXT:
10074 case PDF_ANNOT_FILE_ATTACHMENT:
10075 case PDF_ANNOT_INK:
10076 case PDF_ANNOT_LINE:
10077 case PDF_ANNOT_POLY_LINE:
10078 case PDF_ANNOT_POLYGON:
10079 case PDF_ANNOT_SQUARE:
10080 case PDF_ANNOT_STAMP:
10081 case PDF_ANNOT_TEXT: break;
10082 default: insert_rot = 0;
10083 }
10084
10085 if (insert_rot) {
10086 pdf_dict_put_int(gctx, annot_obj, PDF_NAME(Rotate), rotate);
10087 }
10088
10089 pdf_dirty_annot(gctx, annot);
10090 pdf_update_annot(gctx, annot); // let MuPDF update
10091 pdf->resynth_required = 0;
10092 // insert fill color
10093 if (type == PDF_ANNOT_FREE_TEXT) {
10094 if (nfcol > 0) {
10095 pdf_set_annot_color(gctx, annot, nfcol, fcol);
10096 }
10097 } else if (nfcol > 0) {
10098 pdf_obj *col = pdf_new_array(gctx, page->doc, nfcol);
10099 for (i = 0; i < nfcol; i++) {
10100 pdf_array_push_real(gctx, col, fcol[i]);
10101 }
10102 pdf_dict_put_drop(gctx,annot_obj, PDF_NAME(IC), col);
10103 }
10104 }
10105 fz_catch(gctx) {
10106 PySys_WriteStderr("cannot update annot: '%s'\n", fz_caught_message(gctx));
10107 Py_RETURN_FALSE;
10108 }
10109
10110 if ((opacity < 0 || opacity >= 1) && !blend_mode) // no opacity, no blend_mode
10111 goto normal_exit;
10112
10113 fz_try(gctx) { // create or update /ExtGState
10114 pdf_obj *ap = pdf_dict_getl(gctx, annot_obj, PDF_NAME(AP),
10115 PDF_NAME(N), NULL);
10116 if (!ap) { // should never happen
10117 RAISEPY(gctx, MSG_BAD_APN, PyExc_RuntimeError);
10118 }
10119
10120 pdf_obj *resources = pdf_dict_get(gctx, ap, PDF_NAME(Resources));
10121 if (!resources) { // no Resources yet: make one
10122 resources = pdf_dict_put_dict(gctx, ap, PDF_NAME(Resources), 2);
10123 }
10124 pdf_obj *alp0 = pdf_new_dict(gctx, page->doc, 3);
10125 if (opacity >= 0 && opacity < 1) {
10126 pdf_dict_put_real(gctx, alp0, PDF_NAME(CA), (double) opacity);
10127 pdf_dict_put_real(gctx, alp0, PDF_NAME(ca), (double) opacity);
10128 pdf_dict_put_real(gctx, annot_obj, PDF_NAME(CA), (double) opacity);
10129 }
10130 if (blend_mode) {
10131 pdf_dict_put_name(gctx, alp0, PDF_NAME(BM), blend_mode);
10132 pdf_dict_put_name(gctx, annot_obj, PDF_NAME(BM), blend_mode);
10133 }
10134 pdf_obj *extg = pdf_dict_get(gctx, resources, PDF_NAME(ExtGState));
10135 if (!extg) { // no ExtGState yet: make one
10136 extg = pdf_dict_put_dict(gctx, resources, PDF_NAME(ExtGState), 2);
10137 }
10138 pdf_dict_put_drop(gctx, extg, PDF_NAME(H), alp0);
10139 }
10140
10141 fz_catch(gctx) {
10142 PySys_WriteStderr("cannot set opacity or blend mode\n");
10143 Py_RETURN_FALSE;
10144 }
10145 normal_exit:;
10146 Py_RETURN_TRUE;
10147 }
10148
10149
10150 %pythoncode %{
10151 def update(self,
10152 blend_mode: OptStr =None,
10153 opacity: OptFloat =None,
10154 fontsize: float =0,
10155 fontname: OptStr =None,
10156 text_color: OptSeq =None,
10157 border_color: OptSeq =None,
10158 fill_color: OptSeq =None,
10159 cross_out: bool =True,
10160 rotate: int =-1,
10161 ):
10162
10163 """Update annot appearance.
10164
10165 Notes:
10166 Depending on the annot type, some parameters make no sense,
10167 while others are only available in this method to achieve the
10168 desired result. This is especially true for 'FreeText' annots.
10169 Args:
10170 blend_mode: set the blend mode, all annotations.
10171 opacity: set the opacity, all annotations.
10172 fontsize: set fontsize, 'FreeText' only.
10173 fontname: set the font, 'FreeText' only.
10174 border_color: set border color, 'FreeText' only.
10175 text_color: set text color, 'FreeText' only.
10176 fill_color: set fill color, all annotations.
10177 cross_out: draw diagonal lines, 'Redact' only.
10178 rotate: set rotation, 'FreeText' and some others.
10179 """
10180 CheckParent(self)
10181 def color_string(cs, code):
10182 """Return valid PDF color operator for a given color sequence.
10183 """
10184 cc = ColorCode(cs, code)
10185 if not cc:
10186 return b""
10187 return (cc + "\n").encode()
10188
10189 annot_type = self.type[0] # get the annot type
10190 dt = self.border.get("dashes", None) # get the dashes spec
10191 bwidth = self.border.get("width", -1) # get border line width
10192 stroke = self.colors["stroke"] # get the stroke color
10193 if fill_color != None: # change of fill color requested
10194 fill = fill_color
10195 else: # put in current annot value
10196 fill = self.colors["fill"]
10197
10198 rect = None # self.rect # prevent MuPDF fiddling with it
10199 apnmat = self.apn_matrix # prevent MuPDF fiddling with it
10200 if rotate != -1: # sanitize rotation value
10201 while rotate < 0:
10202 rotate += 360
10203 while rotate >= 360:
10204 rotate -= 360
10205 if annot_type == PDF_ANNOT_FREE_TEXT and rotate % 90 != 0:
10206 rotate = 0
10207
10208 #------------------------------------------------------------------
10209 # handle opacity and blend mode
10210 #------------------------------------------------------------------
10211 if blend_mode is None:
10212 blend_mode = self.blendmode
10213 if not hasattr(opacity, "__float__"):
10214 opacity = self.opacity
10215
10216 if 0 <= opacity < 1 or blend_mode is not None:
10217 opa_code = "/H gs\n" # then we must reference this 'gs'
10218 else:
10219 opa_code = ""
10220
10221 if annot_type == PDF_ANNOT_FREE_TEXT:
10222 CheckColor(border_color)
10223 CheckColor(text_color)
10224 CheckColor(fill_color)
10225 tcol, fname, fsize = TOOLS._parse_da(self)
10226
10227 # read and update default appearance as necessary
10228 update_default_appearance = False
10229 if fsize <= 0:
10230 fsize = 12
10231 update_default_appearance = True
10232 if text_color is not None:
10233 tcol = text_color
10234 update_default_appearance = True
10235 if fontname is not None:
10236 fname = fontname
10237 update_default_appearance = True
10238 if fontsize > 0:
10239 fsize = fontsize
10240 update_default_appearance = True
10241
10242 if update_default_appearance:
10243 da_str = ""
10244 if len(tcol) == 3:
10245 fmt = "{:g} {:g} {:g} rg /{f:s} {s:g} Tf"
10246 elif len(tcol) == 1:
10247 fmt = "{:g} g /{f:s} {s:g} Tf"
10248 elif len(tcol) == 4:
10249 fmt = "{:g} {:g} {:g} {:g} k /{f:s} {s:g} Tf"
10250 da_str = fmt.format(*tcol, f=fname, s=fsize)
10251 TOOLS._update_da(self, da_str)
10252
10253 #------------------------------------------------------------------
10254 # now invoke MuPDF to update the annot appearance
10255 #------------------------------------------------------------------
10256 val = self._update_appearance(
10257 opacity=opacity,
10258 blend_mode=blend_mode,
10259 fill_color=fill,
10260 rotate=rotate,
10261 )
10262 if val == False:
10263 raise RuntimeError("Error updating annotation.")
10264
10265 bfill = color_string(fill, "f")
10266 bstroke = color_string(stroke, "c")
10267
10268 p_ctm = self.parent.transformation_matrix
10269 imat = ~p_ctm # inverse page transf. matrix
10270
10271 if dt:
10272 dashes = "[" + " ".join(map(str, dt)) + "] 0 d\n"
10273 dashes = dashes.encode("utf-8")
10274 else:
10275 dashes = None
10276
10277 if self.line_ends:
10278 line_end_le, line_end_ri = self.line_ends
10279 else:
10280 line_end_le, line_end_ri = 0, 0 # init line end codes
10281
10282 # read contents as created by MuPDF
10283 ap = self._getAP()
10284 ap_tab = ap.splitlines() # split in single lines
10285 ap_updated = False # assume we did nothing
10286
10287 if annot_type == PDF_ANNOT_REDACT:
10288 if cross_out: # create crossed-out rect
10289 ap_updated = True
10290 ap_tab = ap_tab[:-1]
10291 _, LL, LR, UR, UL = ap_tab
10292 ap_tab.append(LR)
10293 ap_tab.append(LL)
10294 ap_tab.append(UR)
10295 ap_tab.append(LL)
10296 ap_tab.append(UL)
10297 ap_tab.append(b"S")
10298
10299 if bwidth > 0 or bstroke != b"":
10300 ap_updated = True
10301 ntab = [b"%g w" % bwidth] if bwidth > 0 else []
10302 for line in ap_tab:
10303 if line.endswith(b"w"):
10304 continue
10305 if line.endswith(b"RG") and bstroke != b"":
10306 line = bstroke[:-1]
10307 ntab.append(line)
10308 ap_tab = ntab
10309
10310 ap = b"\n".join(ap_tab)
10311
10312 if annot_type == PDF_ANNOT_FREE_TEXT:
10313 BT = ap.find(b"BT")
10314 ET = ap.find(b"ET") + 2
10315 ap = ap[BT:ET]
10316 w, h = self.rect.width, self.rect.height
10317 if rotate in (90, 270) or not (apnmat.b == apnmat.c == 0):
10318 w, h = h, w
10319 re = b"0 0 %g %g re" % (w, h)
10320 ap = re + b"\nW\nn\n" + ap
10321 ope = None
10322 fill_string = color_string(fill, "f")
10323 if fill_string:
10324 ope = b"f"
10325 stroke_string = color_string(border_color, "c")
10326 if stroke_string and bwidth > 0:
10327 ope = b"S"
10328 bwidth = b"%g w\n" % bwidth
10329 else:
10330 bwidth = stroke_string = b""
10331 if fill_string and stroke_string:
10332 ope = b"B"
10333 if ope != None:
10334 ap = bwidth + fill_string + stroke_string + re + b"\n" + ope + b"\n" + ap
10335
10336 if dashes != None: # handle dashes
10337 ap = dashes + b"\n" + ap
10338 dashes = None
10339
10340 ap_updated = True
10341
10342 if annot_type in (PDF_ANNOT_POLYGON, PDF_ANNOT_POLY_LINE):
10343 ap = b"\n".join(ap_tab[:-1]) + b"\n"
10344 ap_updated = True
10345 if bfill != b"":
10346 if annot_type == PDF_ANNOT_POLYGON:
10347 ap = ap + bfill + b"b" # close, fill, and stroke
10348 elif annot_type == PDF_ANNOT_POLY_LINE:
10349 ap = ap + b"S" # stroke
10350 else:
10351 if annot_type == PDF_ANNOT_POLYGON:
10352 ap = ap + b"s" # close and stroke
10353 elif annot_type == PDF_ANNOT_POLY_LINE:
10354 ap = ap + b"S" # stroke
10355
10356 if dashes is not None: # handle dashes
10357 ap = dashes + ap
10358 # reset dashing - only applies for LINE annots with line ends given
10359 ap = ap.replace(b"\nS\n", b"\nS\n[] 0 d\n", 1)
10360 ap_updated = True
10361
10362 if opa_code:
10363 ap = opa_code.encode("utf-8") + ap
10364 ap_updated = True
10365
10366 ap = b"q\n" + ap + b"\nQ\n"
10367 #----------------------------------------------------------------------
10368 # the following handles line end symbols for 'Polygon' and 'Polyline'
10369 #----------------------------------------------------------------------
10370 if line_end_le + line_end_ri > 0 and annot_type in (PDF_ANNOT_POLYGON, PDF_ANNOT_POLY_LINE):
10371
10372 le_funcs = (None, TOOLS._le_square, TOOLS._le_circle,
10373 TOOLS._le_diamond, TOOLS._le_openarrow,
10374 TOOLS._le_closedarrow, TOOLS._le_butt,
10375 TOOLS._le_ropenarrow, TOOLS._le_rclosedarrow,
10376 TOOLS._le_slash)
10377 le_funcs_range = range(1, len(le_funcs))
10378 d = 2 * max(1, self.border["width"])
10379 rect = self.rect + (-d, -d, d, d)
10380 ap_updated = True
10381 points = self.vertices
10382 if line_end_le in le_funcs_range:
10383 p1 = Point(points[0]) * imat
10384 p2 = Point(points[1]) * imat
10385 left = le_funcs[line_end_le](self, p1, p2, False, fill_color)
10386 ap += left.encode()
10387 if line_end_ri in le_funcs_range:
10388 p1 = Point(points[-2]) * imat
10389 p2 = Point(points[-1]) * imat
10390 left = le_funcs[line_end_ri](self, p1, p2, True, fill_color)
10391 ap += left.encode()
10392
10393 if ap_updated:
10394 if rect: # rect modified here?
10395 self.set_rect(rect)
10396 self._setAP(ap, rect=1)
10397 else:
10398 self._setAP(ap, rect=0)
10399
10400 #-------------------------------
10401 # handle annotation rotations
10402 #-------------------------------
10403 if annot_type not in ( # only these types are supported
10404 PDF_ANNOT_CARET,
10405 PDF_ANNOT_CIRCLE,
10406 PDF_ANNOT_FILE_ATTACHMENT,
10407 PDF_ANNOT_INK,
10408 PDF_ANNOT_LINE,
10409 PDF_ANNOT_POLY_LINE,
10410 PDF_ANNOT_POLYGON,
10411 PDF_ANNOT_SQUARE,
10412 PDF_ANNOT_STAMP,
10413 PDF_ANNOT_TEXT,
10414 ):
10415 return
10416
10417 rot = self.rotation # get value from annot object
10418 if rot == -1: # nothing to change
10419 return
10420
10421 M = (self.rect.tl + self.rect.br) / 2 # center of annot rect
10422
10423 if rot == 0: # undo rotations
10424 if abs(apnmat - Matrix(1, 1)) < 1e-5:
10425 return # matrix already is a no-op
10426 quad = self.rect.morph(M, ~apnmat) # derotate rect
10427 self.set_rect(quad.rect)
10428 self.set_apn_matrix(Matrix(1, 1)) # appearance matrix = no-op
10429 return
10430
10431 mat = Matrix(rot)
10432 quad = self.rect.morph(M, mat)
10433 self.set_rect(quad.rect)
10434 self.set_apn_matrix(apnmat * mat)
10435 %}
10436
10437 //----------------------------------------------------------------
10438 // annotation set colors
10439 //----------------------------------------------------------------
10440 %pythoncode %{
10441 def set_colors(self, colors=None, stroke=None, fill=None):
10442 """Set 'stroke' and 'fill' colors.
10443
10444 Use either a dict or the direct arguments.
10445 """
10446 CheckParent(self)
10447 doc = self.parent.parent
10448 if type(colors) is not dict:
10449 colors = {"fill": fill, "stroke": stroke}
10450 fill = colors.get("fill")
10451 stroke = colors.get("stroke")
10452 fill_annots = (PDF_ANNOT_CIRCLE, PDF_ANNOT_SQUARE, PDF_ANNOT_LINE, PDF_ANNOT_POLY_LINE, PDF_ANNOT_POLYGON,
10453 PDF_ANNOT_REDACT,)
10454 if stroke in ([], ()):
10455 doc.xref_set_key(self.xref, "C", "[]")
10456 elif stroke is not None:
10457 if hasattr(stroke, "__float__"):
10458 stroke = [float(stroke)]
10459 CheckColor(stroke)
10460 if len(stroke) == 1:
10461 s = "[%g]" % stroke[0]
10462 elif len(stroke) == 3:
10463 s = "[%g %g %g]" % tuple(stroke)
10464 else:
10465 s = "[%g %g %g %g]" % tuple(stroke)
10466 doc.xref_set_key(self.xref, "C", s)
10467
10468 if fill and self.type[0] not in fill_annots:
10469 print("Warning: fill color ignored for annot type '%s'." % self.type[1])
10470 return
10471 if fill in ([], ()):
10472 doc.xref_set_key(self.xref, "IC", "[]")
10473 elif fill is not None:
10474 if hasattr(fill, "__float__"):
10475 fill = [float(fill)]
10476 CheckColor(fill)
10477 if len(fill) == 1:
10478 s = "[%g]" % fill[0]
10479 elif len(fill) == 3:
10480 s = "[%g %g %g]" % tuple(fill)
10481 else:
10482 s = "[%g %g %g %g]" % tuple(fill)
10483 doc.xref_set_key(self.xref, "IC", s)
10484 %}
10485
10486
10487 //----------------------------------------------------------------
10488 // annotation line_ends
10489 //----------------------------------------------------------------
10490 %pythoncode %{@property%}
10491 PARENTCHECK(line_ends, """Line end codes.""")
10492 PyObject *
10493 line_ends()
10494 {
10495 pdf_annot *annot = (pdf_annot *) $self;
10496
10497 // return nothing for invalid annot types
10498 if (!pdf_annot_has_line_ending_styles(gctx, annot))
10499 Py_RETURN_NONE;
10500
10501 int lstart = (int) pdf_annot_line_start_style(gctx, annot);
10502 int lend = (int) pdf_annot_line_end_style(gctx, annot);
10503 return Py_BuildValue("ii", lstart, lend);
10504 }
10505
10506
10507 //----------------------------------------------------------------
10508 // annotation set line ends
10509 //----------------------------------------------------------------
10510 PARENTCHECK(set_line_ends, """Set line end codes.""")
10511 void set_line_ends(int start, int end)
10512 {
10513 pdf_annot *annot = (pdf_annot *) $self;
10514 if (pdf_annot_has_line_ending_styles(gctx, annot))
10515 pdf_set_annot_line_ending_styles(gctx, annot, start, end);
10516 else
10517 JM_Warning("bad annot type for line ends");
10518 }
10519
10520
10521 //----------------------------------------------------------------
10522 // annotation type
10523 //----------------------------------------------------------------
10524 PARENTCHECK(type, """annotation type""")
10525 %pythoncode %{@property%}
10526 PyObject *type()
10527 {
10528 pdf_annot *annot = (pdf_annot *) $self;
10529 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10530 int type = pdf_annot_type(gctx, annot);
10531 const char *c = pdf_string_from_annot_type(gctx, type);
10532 pdf_obj *o = pdf_dict_gets(gctx, annot_obj, "IT");
10533 if (!o || !pdf_is_name(gctx, o))
10534 return Py_BuildValue("is", type, c); // no IT entry
10535 const char *it = pdf_to_name(gctx, o);
10536 return Py_BuildValue("iss", type, c, it);
10537 }
10538
10539 //----------------------------------------------------------------
10540 // annotation opacity
10541 //----------------------------------------------------------------
10542 PARENTCHECK(opacity, """Opacity.""")
10543 %pythoncode %{@property%}
10544 PyObject *opacity()
10545 {
10546 pdf_annot *annot = (pdf_annot *) $self;
10547 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10548 double opy = -1;
10549 pdf_obj *ca = pdf_dict_get(gctx, annot_obj, PDF_NAME(CA));
10550 if (pdf_is_number(gctx, ca))
10551 opy = pdf_to_real(gctx, ca);
10552 return Py_BuildValue("f", opy);
10553 }
10554
10555 //----------------------------------------------------------------
10556 // annotation set opacity
10557 //----------------------------------------------------------------
10558 PARENTCHECK(set_opacity, """Set opacity.""")
10559 void set_opacity(float opacity)
10560 {
10561 pdf_annot *annot = (pdf_annot *) $self;
10562 if (!INRANGE(opacity, 0.0f, 1.0f))
10563 {
10564 pdf_set_annot_opacity(gctx, annot, 1);
10565 return;
10566 }
10567 pdf_set_annot_opacity(gctx, annot, opacity);
10568 if (opacity < 1.0f)
10569 {
10570 pdf_page *page = pdf_annot_page(gctx, annot);
10571 page->transparency = 1;
10572 }
10573 }
10574
10575
10576 //----------------------------------------------------------------
10577 // annotation get attached file info
10578 //----------------------------------------------------------------
10579 %pythoncode %{@property%}
10580 FITZEXCEPTION(file_info, !result)
10581 PARENTCHECK(file_info, """Attached file information.""")
10582 PyObject *file_info()
10583 {
10584 PyObject *res = PyDict_New(); // create Python dict
10585 char *filename = NULL;
10586 char *desc = NULL;
10587 int length = -1, size = -1;
10588 pdf_obj *stream = NULL, *o = NULL, *fs = NULL;
10589 pdf_annot *annot = (pdf_annot *) $self;
10590 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10591 fz_try(gctx) {
10592 int type = (int) pdf_annot_type(gctx, annot);
10593 if (type != PDF_ANNOT_FILE_ATTACHMENT) {
10594 RAISEPY(gctx, MSG_BAD_ANNOT_TYPE, PyExc_TypeError);
10595 }
10596 stream = pdf_dict_getl(gctx, annot_obj, PDF_NAME(FS),
10597 PDF_NAME(EF), PDF_NAME(F), NULL);
10598 if (!stream) {
10599 RAISEPY(gctx, "bad PDF: file entry not found", JM_Exc_FileDataError);
10600 }
10601 }
10602 fz_catch(gctx) {
10603 return NULL;
10604 }
10605
10606 fs = pdf_dict_get(gctx, annot_obj, PDF_NAME(FS));
10607
10608 o = pdf_dict_get(gctx, fs, PDF_NAME(UF));
10609 if (o) {
10610 filename = (char *) pdf_to_text_string(gctx, o);
10611 } else {
10612 o = pdf_dict_get(gctx, fs, PDF_NAME(F));
10613 if (o) filename = (char *) pdf_to_text_string(gctx, o);
10614 }
10615
10616 o = pdf_dict_get(gctx, fs, PDF_NAME(Desc));
10617 if (o) desc = (char *) pdf_to_text_string(gctx, o);
10618
10619 o = pdf_dict_get(gctx, stream, PDF_NAME(Length));
10620 if (o) length = pdf_to_int(gctx, o);
10621
10622 o = pdf_dict_getl(gctx, stream, PDF_NAME(Params),
10623 PDF_NAME(Size), NULL);
10624 if (o) size = pdf_to_int(gctx, o);
10625
10626 DICT_SETITEM_DROP(res, dictkey_filename, JM_EscapeStrFromStr(filename));
10627 DICT_SETITEM_DROP(res, dictkey_desc, JM_UnicodeFromStr(desc));
10628 DICT_SETITEM_DROP(res, dictkey_length, Py_BuildValue("i", length));
10629 DICT_SETITEM_DROP(res, dictkey_size, Py_BuildValue("i", size));
10630 return res;
10631 }
10632
10633
10634 //----------------------------------------------------------------
10635 // annotation get attached file content
10636 //----------------------------------------------------------------
10637 FITZEXCEPTION(get_file, !result)
10638 PARENTCHECK(get_file, """Retrieve attached file content.""")
10639 PyObject *
10640 get_file()
10641 {
10642 PyObject *res = NULL;
10643 pdf_obj *stream = NULL;
10644 fz_buffer *buf = NULL;
10645 pdf_annot *annot = (pdf_annot *) $self;
10646 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10647 fz_var(buf);
10648 fz_try(gctx) {
10649 int type = (int) pdf_annot_type(gctx, annot);
10650 if (type != PDF_ANNOT_FILE_ATTACHMENT) {
10651 RAISEPY(gctx, MSG_BAD_ANNOT_TYPE, PyExc_TypeError);
10652 }
10653 stream = pdf_dict_getl(gctx, annot_obj, PDF_NAME(FS),
10654 PDF_NAME(EF), PDF_NAME(F), NULL);
10655 if (!stream) {
10656 RAISEPY(gctx, "bad PDF: file entry not found", JM_Exc_FileDataError);
10657 }
10658 buf = pdf_load_stream(gctx, stream);
10659 res = JM_BinFromBuffer(gctx, buf);
10660 }
10661 fz_always(gctx) {
10662 fz_drop_buffer(gctx, buf);
10663 }
10664 fz_catch(gctx) {
10665 return NULL;
10666 }
10667 return res;
10668 }
10669
10670
10671 //----------------------------------------------------------------
10672 // annotation get attached sound stream
10673 //----------------------------------------------------------------
10674 FITZEXCEPTION(get_sound, !result)
10675 PARENTCHECK(get_sound, """Retrieve sound stream.""")
10676 PyObject *
10677 get_sound()
10678 {
10679 PyObject *res = NULL;
10680 PyObject *stream = NULL;
10681 fz_buffer *buf = NULL;
10682 pdf_obj *obj = NULL;
10683 pdf_annot *annot = (pdf_annot *) $self;
10684 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10685 fz_var(buf);
10686 fz_try(gctx) {
10687 int type = (int) pdf_annot_type(gctx, annot);
10688 pdf_obj *sound = pdf_dict_get(gctx, annot_obj, PDF_NAME(Sound));
10689 if (type != PDF_ANNOT_SOUND || !sound) {
10690 RAISEPY(gctx, MSG_BAD_ANNOT_TYPE, PyExc_TypeError);
10691 }
10692 if (pdf_dict_get(gctx, sound, PDF_NAME(F))) {
10693 RAISEPY(gctx, "unsupported sound stream", JM_Exc_FileDataError);
10694 }
10695 res = PyDict_New();
10696 obj = pdf_dict_get(gctx, sound, PDF_NAME(R));
10697 if (obj) {
10698 DICT_SETITEMSTR_DROP(res, "rate",
10699 Py_BuildValue("f", pdf_to_real(gctx, obj)));
10700 }
10701 obj = pdf_dict_get(gctx, sound, PDF_NAME(C));
10702 if (obj) {
10703 DICT_SETITEMSTR_DROP(res, "channels",
10704 Py_BuildValue("i", pdf_to_int(gctx, obj)));
10705 }
10706 obj = pdf_dict_get(gctx, sound, PDF_NAME(B));
10707 if (obj) {
10708 DICT_SETITEMSTR_DROP(res, "bps",
10709 Py_BuildValue("i", pdf_to_int(gctx, obj)));
10710 }
10711 obj = pdf_dict_get(gctx, sound, PDF_NAME(E));
10712 if (obj) {
10713 DICT_SETITEMSTR_DROP(res, "encoding",
10714 Py_BuildValue("s", pdf_to_name(gctx, obj)));
10715 }
10716 obj = pdf_dict_gets(gctx, sound, "CO");
10717 if (obj) {
10718 DICT_SETITEMSTR_DROP(res, "compression",
10719 Py_BuildValue("s", pdf_to_name(gctx, obj)));
10720 }
10721 buf = pdf_load_stream(gctx, sound);
10722 stream = JM_BinFromBuffer(gctx, buf);
10723 DICT_SETITEMSTR_DROP(res, "stream", stream);
10724 }
10725 fz_always(gctx) {
10726 fz_drop_buffer(gctx, buf);
10727 }
10728 fz_catch(gctx) {
10729 Py_CLEAR(res);
10730 return NULL;
10731 }
10732 return res;
10733 }
10734
10735
10736 //----------------------------------------------------------------
10737 // annotation update attached file
10738 //----------------------------------------------------------------
10739 FITZEXCEPTION(update_file, !result)
10740 %pythonprepend update_file
10741 %{"""Update attached file."""
10742 CheckParent(self)%}
10743
10744 PyObject *
10745 update_file(PyObject *buffer=NULL, char *filename=NULL, char *ufilename=NULL, char *desc=NULL)
10746 {
10747 pdf_document *pdf = NULL; // to be filled in
10748 fz_buffer *res = NULL; // for compressed content
10749 pdf_obj *stream = NULL, *fs = NULL;
10750 pdf_annot *annot = (pdf_annot *) $self;
10751 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10752 fz_try(gctx) {
10753 pdf = pdf_get_bound_document(gctx, annot_obj); // the owning PDF
10754 int type = (int) pdf_annot_type(gctx, annot);
10755 if (type != PDF_ANNOT_FILE_ATTACHMENT) {
10756 RAISEPY(gctx, MSG_BAD_ANNOT_TYPE, PyExc_TypeError);
10757 }
10758 stream = pdf_dict_getl(gctx, annot_obj, PDF_NAME(FS),
10759 PDF_NAME(EF), PDF_NAME(F), NULL);
10760 // the object for file content
10761 if (!stream) {
10762 RAISEPY(gctx, "bad PDF: no /EF object", JM_Exc_FileDataError);
10763 }
10764
10765 fs = pdf_dict_get(gctx, annot_obj, PDF_NAME(FS));
10766
10767 // file content given
10768 res = JM_BufferFromBytes(gctx, buffer);
10769 if (buffer && !res) {
10770 RAISEPY(gctx, MSG_BAD_BUFFER, PyExc_ValueError);
10771 }
10772 if (res) {
10773 JM_update_stream(gctx, pdf, stream, res, 1);
10774 // adjust /DL and /Size parameters
10775 int64_t len = (int64_t) fz_buffer_storage(gctx, res, NULL);
10776 pdf_obj *l = pdf_new_int(gctx, len);
10777 pdf_dict_put(gctx, stream, PDF_NAME(DL), l);
10778 pdf_dict_putl(gctx, stream, l, PDF_NAME(Params), PDF_NAME(Size), NULL);
10779 }
10780
10781 if (filename) {
10782 pdf_dict_put_text_string(gctx, stream, PDF_NAME(F), filename);
10783 pdf_dict_put_text_string(gctx, fs, PDF_NAME(F), filename);
10784 pdf_dict_put_text_string(gctx, stream, PDF_NAME(UF), filename);
10785 pdf_dict_put_text_string(gctx, fs, PDF_NAME(UF), filename);
10786 pdf_dict_put_text_string(gctx, annot_obj, PDF_NAME(Contents), filename);
10787 }
10788
10789 if (ufilename) {
10790 pdf_dict_put_text_string(gctx, stream, PDF_NAME(UF), ufilename);
10791 pdf_dict_put_text_string(gctx, fs, PDF_NAME(UF), ufilename);
10792 }
10793
10794 if (desc) {
10795 pdf_dict_put_text_string(gctx, stream, PDF_NAME(Desc), desc);
10796 pdf_dict_put_text_string(gctx, fs, PDF_NAME(Desc), desc);
10797 }
10798 }
10799 fz_always(gctx) {
10800 fz_drop_buffer(gctx, res);
10801 }
10802 fz_catch(gctx) {
10803 return NULL;
10804 }
10805
10806 Py_RETURN_NONE;
10807 }
10808
10809
10810 //----------------------------------------------------------------
10811 // annotation info
10812 //----------------------------------------------------------------
10813 %pythoncode %{@property%}
10814 PARENTCHECK(info, """Various information details.""")
10815 PyObject *info()
10816 {
10817 pdf_annot *annot = (pdf_annot *) $self;
10818 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10819 PyObject *res = PyDict_New();
10820 pdf_obj *o;
10821
10822 DICT_SETITEM_DROP(res, dictkey_content,
10823 JM_UnicodeFromStr(pdf_annot_contents(gctx, annot)));
10824
10825 o = pdf_dict_get(gctx, annot_obj, PDF_NAME(Name));
10826 DICT_SETITEM_DROP(res, dictkey_name, JM_UnicodeFromStr(pdf_to_name(gctx, o)));
10827
10828 // Title (= author)
10829 o = pdf_dict_get(gctx, annot_obj, PDF_NAME(T));
10830 DICT_SETITEM_DROP(res, dictkey_title, JM_UnicodeFromStr(pdf_to_text_string(gctx, o)));
10831
10832 // CreationDate
10833 o = pdf_dict_gets(gctx, annot_obj, "CreationDate");
10834 DICT_SETITEM_DROP(res, dictkey_creationDate,
10835 JM_UnicodeFromStr(pdf_to_text_string(gctx, o)));
10836
10837 // ModDate
10838 o = pdf_dict_get(gctx, annot_obj, PDF_NAME(M));
10839 DICT_SETITEM_DROP(res, dictkey_modDate, JM_UnicodeFromStr(pdf_to_text_string(gctx, o)));
10840
10841 // Subj
10842 o = pdf_dict_gets(gctx, annot_obj, "Subj");
10843 DICT_SETITEM_DROP(res, dictkey_subject,
10844 Py_BuildValue("s",pdf_to_text_string(gctx, o)));
10845
10846 // Identification (PDF key /NM)
10847 o = pdf_dict_gets(gctx, annot_obj, "NM");
10848 DICT_SETITEM_DROP(res, dictkey_id,
10849 JM_UnicodeFromStr(pdf_to_text_string(gctx, o)));
10850
10851 return res;
10852 }
10853
10854 //----------------------------------------------------------------
10855 // annotation set information
10856 //----------------------------------------------------------------
10857 FITZEXCEPTION(set_info, !result)
10858 %pythonprepend set_info %{
10859 """Set various properties."""
10860 CheckParent(self)
10861 if type(info) is dict: # build the args from the dictionary
10862 content = info.get("content", None)
10863 title = info.get("title", None)
10864 creationDate = info.get("creationDate", None)
10865 modDate = info.get("modDate", None)
10866 subject = info.get("subject", None)
10867 info = None
10868 %}
10869 PyObject *
10870 set_info(PyObject *info=NULL, char *content=NULL, char *title=NULL,
10871 char *creationDate=NULL, char *modDate=NULL, char *subject=NULL)
10872 {
10873 pdf_annot *annot = (pdf_annot *) $self;
10874 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10875 // use this to indicate a 'markup' annot type
10876 int is_markup = pdf_annot_has_author(gctx, annot);
10877 fz_try(gctx) {
10878 // contents
10879 if (content)
10880 pdf_set_annot_contents(gctx, annot, content);
10881
10882 if (is_markup) {
10883 // title (= author)
10884 if (title)
10885 pdf_set_annot_author(gctx, annot, title);
10886
10887 // creation date
10888 if (creationDate)
10889 pdf_dict_put_text_string(gctx, annot_obj,
10890 PDF_NAME(CreationDate), creationDate);
10891
10892 // mod date
10893 if (modDate)
10894 pdf_dict_put_text_string(gctx, annot_obj,
10895 PDF_NAME(M), modDate);
10896
10897 // subject
10898 if (subject)
10899 pdf_dict_puts_drop(gctx, annot_obj, "Subj",
10900 pdf_new_text_string(gctx, subject));
10901 }
10902 }
10903 fz_catch(gctx) {
10904 return NULL;
10905 }
10906 Py_RETURN_NONE;
10907 }
10908
10909
10910 //----------------------------------------------------------------
10911 // annotation border
10912 //----------------------------------------------------------------
10913 %pythoncode %{@property%}
10914 %pythonprepend border %{
10915 """Border information."""
10916 CheckParent(self)
10917 atype = self.type[0]
10918 if atype not in (PDF_ANNOT_CIRCLE, PDF_ANNOT_FREE_TEXT, PDF_ANNOT_INK, PDF_ANNOT_LINE, PDF_ANNOT_POLY_LINE,PDF_ANNOT_POLYGON, PDF_ANNOT_SQUARE):
10919 return {}
10920 %}
10921 PyObject *border()
10922 {
10923 pdf_annot *annot = (pdf_annot *) $self;
10924 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10925 return JM_annot_border(gctx, annot_obj);
10926 }
10927
10928 //----------------------------------------------------------------
10929 // set annotation border
10930 //----------------------------------------------------------------
10931 %pythonprepend set_border %{
10932 """Set border properties.
10933
10934 Either a dict, or direct arguments width, style, dashes or clouds."""
10935
10936 CheckParent(self)
10937 atype, atname = self.type[:2] # annotation type
10938 if atype not in (PDF_ANNOT_CIRCLE, PDF_ANNOT_FREE_TEXT, PDF_ANNOT_INK, PDF_ANNOT_LINE, PDF_ANNOT_POLY_LINE,PDF_ANNOT_POLYGON, PDF_ANNOT_SQUARE):
10939 print(f"Cannot set border for '{atname}'.")
10940 return None
10941 if not atype in (PDF_ANNOT_CIRCLE, PDF_ANNOT_FREE_TEXT,PDF_ANNOT_POLYGON, PDF_ANNOT_SQUARE):
10942 if clouds > 0:
10943 print(f"Cannot set cloudy border for '{atname}'.")
10944 clouds = -1 # do not set border effect
10945 if type(border) is not dict:
10946 border = {"width": width, "style": style, "dashes": dashes, "clouds": clouds}
10947 border.setdefault("width", -1)
10948 border.setdefault("style", None)
10949 border.setdefault("dashes", None)
10950 border.setdefault("clouds", -1)
10951 if border["width"] == None:
10952 border["width"] = -1
10953 if border["clouds"] == None:
10954 border["clouds"] = -1
10955 if hasattr(border["dashes"], "__getitem__"): # ensure sequence items are integers
10956 border["dashes"] = tuple(border["dashes"])
10957 for item in border["dashes"]:
10958 if not isinstance(item, int):
10959 border["dashes"] = None
10960 break
10961 %}
10962 PyObject *
10963 set_border(PyObject *border=NULL, float width=-1, char *style=NULL, PyObject *dashes=NULL, int clouds=-1)
10964 {
10965 pdf_annot *annot = (pdf_annot *) $self;
10966 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
10967 pdf_document *pdf = pdf_get_bound_document(gctx, annot_obj);
10968 return JM_annot_set_border(gctx, border, pdf, annot_obj);
10969 }
10970
10971
10972 //----------------------------------------------------------------
10973 // annotation flags
10974 //----------------------------------------------------------------
10975 %pythoncode %{@property%}
10976 PARENTCHECK(flags, """Flags field.""")
10977 int flags()
10978 {
10979 pdf_annot *annot = (pdf_annot *) $self;
10980 return pdf_annot_flags(gctx, annot);
10981 }
10982
10983 //----------------------------------------------------------------
10984 // annotation clean contents
10985 //----------------------------------------------------------------
10986 FITZEXCEPTION(clean_contents, !result)
10987 PARENTCHECK(clean_contents, """Clean appearance contents stream.""")
10988 PyObject *clean_contents(int sanitize=1)
10989 {
10990 pdf_annot *annot = (pdf_annot *) $self;
10991 pdf_document *pdf = pdf_get_bound_document(gctx, pdf_annot_obj(gctx, annot));
10992 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR >= 22
10993 pdf_filter_factory list[2] = { 0 };
10994 pdf_sanitize_filter_options sopts = { 0 };
10995 pdf_filter_options filter = {
10996 1, // recurse: true
10997 0, // instance forms
10998 0, // do not ascii-escape binary data
10999 0, // no_update
11000 NULL, // end_page_opaque
11001 NULL, // end page
11002 list, // filters
11003 };
11004 if (sanitize) {
11005 list[0].filter = pdf_new_sanitize_filter;
11006 list[0].options = &sopts;
11007 }
11008 #else
11009 pdf_filter_options filter = {
11010 NULL, // opaque
11011 NULL, // image filter
11012 NULL, // text filter
11013 NULL, // after text
11014 NULL, // end page
11015 1, // recurse: true
11016 1, // instance forms
11017 1, // sanitize,
11018 0 // do not ascii-escape binary data
11019 };
11020 filter.sanitize = sanitize;
11021 #endif
11022 fz_try(gctx) {
11023 pdf_filter_annot_contents(gctx, pdf, annot, &filter);
11024 }
11025 fz_catch(gctx) {
11026 return NULL;
11027 }
11028 Py_RETURN_NONE;
11029 }
11030
11031
11032 //----------------------------------------------------------------
11033 // set annotation flags
11034 //----------------------------------------------------------------
11035 PARENTCHECK(set_flags, """Set annotation flags.""")
11036 void
11037 set_flags(int flags)
11038 {
11039 pdf_annot *annot = (pdf_annot *) $self;
11040 pdf_set_annot_flags(gctx, annot, flags);
11041 }
11042
11043
11044 //----------------------------------------------------------------
11045 // annotation delete responses
11046 //----------------------------------------------------------------
11047 FITZEXCEPTION(delete_responses, !result)
11048 PARENTCHECK(delete_responses, """Delete 'Popup' and responding annotations.""")
11049 PyObject *
11050 delete_responses()
11051 {
11052 pdf_annot *annot = (pdf_annot *) $self;
11053 pdf_obj *annot_obj = pdf_annot_obj(gctx, annot);
11054 pdf_page *page = pdf_annot_page(gctx, annot);
11055 pdf_annot *irt_annot = NULL;
11056 fz_try(gctx) {
11057 while (1) {
11058 irt_annot = JM_find_annot_irt(gctx, annot);
11059 if (!irt_annot)
11060 break;
11061 pdf_delete_annot(gctx, page, irt_annot);
11062 }
11063 pdf_dict_del(gctx, annot_obj, PDF_NAME(Popup));
11064
11065 pdf_obj *annots = pdf_dict_get(gctx, page->obj, PDF_NAME(Annots));
11066 int i, n = pdf_array_len(gctx, annots), found = 0;
11067 for (i = n - 1; i >= 0; i--) {
11068 pdf_obj *o = pdf_array_get(gctx, annots, i);
11069 pdf_obj *p = pdf_dict_get(gctx, o, PDF_NAME(Parent));
11070 if (!p)
11071 continue;
11072 if (!pdf_objcmp(gctx, p, annot_obj)) {
11073 pdf_array_delete(gctx, annots, i);
11074 found = 1;
11075 }
11076 }
11077 if (found > 0) {
11078 pdf_dict_put(gctx, page->obj, PDF_NAME(Annots), annots);
11079 }
11080 }
11081 fz_catch(gctx) {
11082 return NULL;
11083 }
11084 Py_RETURN_NONE;
11085 }
11086
11087 //----------------------------------------------------------------
11088 // next annotation
11089 //----------------------------------------------------------------
11090 PARENTCHECK(next, """Next annotation.""")
11091 %pythonappend next %{
11092 if not val:
11093 return None
11094 val.thisown = True
11095 val.parent = self.parent # copy owning page object from previous annot
11096 val.parent._annot_refs[id(val)] = val
11097
11098 if val.type[0] == PDF_ANNOT_WIDGET:
11099 widget = Widget()
11100 TOOLS._fill_widget(val, widget)
11101 val = widget
11102 %}
11103 %pythoncode %{@property%}
11104 struct Annot *next()
11105 {
11106 pdf_annot *this_annot = (pdf_annot *) $self;
11107 int type = pdf_annot_type(gctx, this_annot);
11108 pdf_annot *annot;
11109
11110 if (type != PDF_ANNOT_WIDGET) {
11111 annot = pdf_next_annot(gctx, this_annot);
11112 } else {
11113 annot = pdf_next_widget(gctx, this_annot);
11114 }
11115
11116 if (annot)
11117 pdf_keep_annot(gctx, annot);
11118 return (struct Annot *) annot;
11119 }
11120
11121
11122 //----------------------------------------------------------------
11123 // annotation pixmap
11124 //----------------------------------------------------------------
11125 FITZEXCEPTION(get_pixmap, !result)
11126 %pythonprepend get_pixmap
11127 %{"""annotation Pixmap"""
11128
11129 CheckParent(self)
11130 cspaces = {"gray": csGRAY, "rgb": csRGB, "cmyk": csCMYK}
11131 if type(colorspace) is str:
11132 colorspace = cspaces.get(colorspace.lower(), None)
11133 if dpi:
11134 matrix = Matrix(dpi / 72, dpi / 72)
11135 %}
11136 %pythonappend get_pixmap
11137 %{
11138 val.thisown = True
11139 if dpi:
11140 val.set_dpi(dpi, dpi)
11141 %}
11142 struct Pixmap *
11143 get_pixmap(PyObject *matrix = NULL, PyObject *dpi=NULL, struct Colorspace *colorspace = NULL, int alpha = 0)
11144 {
11145 fz_matrix ctm = JM_matrix_from_py(matrix);
11146 fz_colorspace *cs = (fz_colorspace *) colorspace;
11147 fz_pixmap *pix = NULL;
11148 if (!cs) {
11149 cs = fz_device_rgb(gctx);
11150 }
11151
11152 fz_try(gctx) {
11153 pix = pdf_new_pixmap_from_annot(gctx, (pdf_annot *) $self, ctm, cs, NULL, alpha);
11154 }
11155 fz_catch(gctx) {
11156 return NULL;
11157 }
11158 return (struct Pixmap *) pix;
11159 }
11160 %pythoncode %{
11161 def _erase(self):
11162 self.__swig_destroy__(self)
11163 self.parent = None
11164
11165 def __str__(self):
11166 CheckParent(self)
11167 return "'%s' annotation on %s" % (self.type[1], str(self.parent))
11168
11169 def __repr__(self):
11170 CheckParent(self)
11171 return "'%s' annotation on %s" % (self.type[1], str(self.parent))
11172
11173 def __del__(self):
11174 if self.parent is None:
11175 return
11176 self._erase()%}
11177 }
11178 };
11179 %clearnodefaultctor;
11180
11181 //------------------------------------------------------------------------
11182 // fz_link
11183 //------------------------------------------------------------------------
11184 %nodefaultctor;
11185 struct Link
11186 {
11187 %immutable;
11188 %extend {
11189 ~Link() {
11190 DEBUGMSG1("Link");
11191 fz_link *this_link = (fz_link *) $self;
11192 fz_drop_link(gctx, this_link);
11193 DEBUGMSG2;
11194 }
11195
11196 PyObject *_border(struct Document *doc, int xref)
11197 {
11198 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) doc);
11199 if (!pdf) Py_RETURN_NONE;
11200 pdf_obj *link_obj = pdf_new_indirect(gctx, pdf, xref, 0);
11201 if (!link_obj) Py_RETURN_NONE;
11202 PyObject *b = JM_annot_border(gctx, link_obj);
11203 pdf_drop_obj(gctx, link_obj);
11204 return b;
11205 }
11206
11207 PyObject *_setBorder(PyObject *border, struct Document *doc, int xref)
11208 {
11209 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) doc);
11210 if (!pdf) Py_RETURN_NONE;
11211 pdf_obj *link_obj = pdf_new_indirect(gctx, pdf, xref, 0);
11212 if (!link_obj) Py_RETURN_NONE;
11213 PyObject *b = JM_annot_set_border(gctx, border, pdf, link_obj);
11214 pdf_drop_obj(gctx, link_obj);
11215 return b;
11216 }
11217
11218 FITZEXCEPTION(_colors, !result)
11219 PyObject *_colors(struct Document *doc, int xref)
11220 {
11221 pdf_document *pdf = pdf_specifics(gctx, (fz_document *) doc);
11222 if (!pdf) Py_RETURN_NONE;
11223 PyObject *b = NULL;
11224 pdf_obj *link_obj;
11225 fz_try(gctx) {
11226 link_obj = pdf_new_indirect(gctx, pdf, xref, 0);
11227 if (!link_obj) {
11228 RAISEPY(gctx, MSG_BAD_XREF, PyExc_ValueError);
11229 }
11230 b = JM_annot_colors(gctx, link_obj);
11231 }
11232 fz_always(gctx) {
11233 pdf_drop_obj(gctx, link_obj);
11234 }
11235 fz_catch(gctx) {
11236 return NULL;
11237 }
11238 return b;
11239 }
11240
11241
11242 %pythoncode %{
11243 @property
11244 def border(self):
11245 return self._border(self.parent.parent.this, self.xref)
11246
11247 @property
11248 def flags(self)->int:
11249 CheckParent(self)
11250 doc = self.parent.parent
11251 if not doc.is_pdf:
11252 return 0
11253 f = doc.xref_get_key(self.xref, "F")
11254 if f[1] != "null":
11255 return int(f[1])
11256 return 0
11257
11258 def set_flags(self, flags):
11259 CheckParent(self)
11260 doc = self.parent.parent
11261 if not doc.is_pdf:
11262 raise ValueError("is no PDF")
11263 if not type(flags) is int:
11264 raise ValueError("bad 'flags' value")
11265 doc.xref_set_key(self.xref, "F", str(flags))
11266 return None
11267
11268 def set_border(self, border=None, width=0, dashes=None, style=None):
11269 if type(border) is not dict:
11270 border = {"width": width, "style": style, "dashes": dashes}
11271 return self._setBorder(border, self.parent.parent.this, self.xref)
11272
11273 @property
11274 def colors(self):
11275 return self._colors(self.parent.parent.this, self.xref)
11276
11277 def set_colors(self, colors=None, stroke=None, fill=None):
11278 """Set border colors."""
11279 CheckParent(self)
11280 doc = self.parent.parent
11281 if type(colors) is not dict:
11282 colors = {"fill": fill, "stroke": stroke}
11283 fill = colors.get("fill")
11284 stroke = colors.get("stroke")
11285 if fill is not None:
11286 print("warning: links have no fill color")
11287 if stroke in ([], ()):
11288 doc.xref_set_key(self.xref, "C", "[]")
11289 return
11290 if hasattr(stroke, "__float__"):
11291 stroke = [float(stroke)]
11292 CheckColor(stroke)
11293 if len(stroke) == 1:
11294 s = "[%g]" % stroke[0]
11295 elif len(stroke) == 3:
11296 s = "[%g %g %g]" % tuple(stroke)
11297 else:
11298 s = "[%g %g %g %g]" % tuple(stroke)
11299 doc.xref_set_key(self.xref, "C", s)
11300 %}
11301 %pythoncode %{@property%}
11302 PARENTCHECK(uri, """Uri string.""")
11303 PyObject *uri()
11304 {
11305 fz_link *this_link = (fz_link *) $self;
11306 return JM_UnicodeFromStr(this_link->uri);
11307 }
11308
11309 %pythoncode %{@property%}
11310 PARENTCHECK(is_external, """Flag the link as external.""")
11311 PyObject *is_external()
11312 {
11313 fz_link *this_link = (fz_link *) $self;
11314 if (!this_link->uri) Py_RETURN_FALSE;
11315 return JM_BOOL(fz_is_external_link(gctx, this_link->uri));
11316 }
11317
11318 %pythoncode
11319 %{
11320 page = -1
11321 @property
11322 def dest(self):
11323 """Create link destination details."""
11324 if hasattr(self, "parent") and self.parent is None:
11325 raise ValueError("orphaned object: parent is None")
11326 if self.parent.parent.is_closed or self.parent.parent.is_encrypted:
11327 raise ValueError("document closed or encrypted")
11328 doc = self.parent.parent
11329
11330 if self.is_external or self.uri.startswith("#"):
11331 uri = None
11332 else:
11333 uri = doc.resolve_link(self.uri)
11334
11335 return linkDest(self, uri)
11336 %}
11337
11338 PARENTCHECK(rect, """Rectangle ('hot area').""")
11339 %pythoncode %{@property%}
11340 %pythonappend rect %{val = Rect(val)%}
11341 PyObject *rect()
11342 {
11343 fz_link *this_link = (fz_link *) $self;
11344 return JM_py_from_rect(this_link->rect);
11345 }
11346
11347 //----------------------------------------------------------------
11348 // next link
11349 //----------------------------------------------------------------
11350 // we need to increase the link refs number
11351 // so that it will not be freed when the head is dropped
11352 PARENTCHECK(next, """Next link.""")
11353 %pythonappend next %{
11354 if val:
11355 val.thisown = True
11356 val.parent = self.parent # copy owning page from prev link
11357 val.parent._annot_refs[id(val)] = val
11358 if self.xref > 0: # prev link has an xref
11359 link_xrefs = [x[0] for x in self.parent.annot_xrefs() if x[1] == PDF_ANNOT_LINK]
11360 link_ids = [x[2] for x in self.parent.annot_xrefs() if x[1] == PDF_ANNOT_LINK]
11361 idx = link_xrefs.index(self.xref)
11362 val.xref = link_xrefs[idx + 1]
11363 val.id = link_ids[idx + 1]
11364 else:
11365 val.xref = 0
11366 val.id = ""
11367 %}
11368 %pythoncode %{@property%}
11369 struct Link *next()
11370 {
11371 fz_link *this_link = (fz_link *) $self;
11372 fz_link *next_link = this_link->next;
11373 if (!next_link) return NULL;
11374 next_link = fz_keep_link(gctx, next_link);
11375 return (struct Link *) next_link;
11376 }
11377
11378 %pythoncode %{
11379 def _erase(self):
11380 self.__swig_destroy__(self)
11381 self.parent = None
11382
11383 def __str__(self):
11384 CheckParent(self)
11385 return "link on " + str(self.parent)
11386
11387 def __repr__(self):
11388 CheckParent(self)
11389 return "link on " + str(self.parent)
11390
11391 def __del__(self):
11392 self._erase()%}
11393 }
11394 };
11395 %clearnodefaultctor;
11396
11397 //------------------------------------------------------------------------
11398 // fz_display_list
11399 //------------------------------------------------------------------------
11400 struct DisplayList {
11401 %extend
11402 {
11403 ~DisplayList() {
11404 DEBUGMSG1("DisplayList");
11405 fz_display_list *this_dl = (fz_display_list *) $self;
11406 fz_drop_display_list(gctx, this_dl);
11407 DEBUGMSG2;
11408 }
11409 FITZEXCEPTION(DisplayList, !result)
11410 DisplayList(PyObject *mediabox)
11411 {
11412 fz_display_list *dl = NULL;
11413 fz_try(gctx) {
11414 dl = fz_new_display_list(gctx, JM_rect_from_py(mediabox));
11415 }
11416 fz_catch(gctx) {
11417 return NULL;
11418 }
11419 return (struct DisplayList *) dl;
11420 }
11421
11422 FITZEXCEPTION(run, !result)
11423 PyObject *run(struct DeviceWrapper *dw, PyObject *m, PyObject *area) {
11424 fz_try(gctx) {
11425 fz_run_display_list(gctx, (fz_display_list *) $self, dw->device,
11426 JM_matrix_from_py(m), JM_rect_from_py(area), NULL);
11427 }
11428 fz_catch(gctx) {
11429 return NULL;
11430 }
11431 Py_RETURN_NONE;
11432 }
11433
11434 //----------------------------------------------------------------
11435 // DisplayList.rect
11436 //----------------------------------------------------------------
11437 %pythoncode%{@property%}
11438 %pythonappend rect %{val = Rect(val)%}
11439 PyObject *rect()
11440 {
11441 return JM_py_from_rect(fz_bound_display_list(gctx, (fz_display_list *) $self));
11442 }
11443
11444 //----------------------------------------------------------------
11445 // DisplayList.get_pixmap
11446 //----------------------------------------------------------------
11447 FITZEXCEPTION(get_pixmap, !result)
11448 %pythonappend get_pixmap %{val.thisown = True%}
11449 struct Pixmap *get_pixmap(PyObject *matrix=NULL,
11450 struct Colorspace *colorspace=NULL,
11451 int alpha=0,
11452 PyObject *clip=NULL)
11453 {
11454 fz_colorspace *cs = NULL;
11455 fz_pixmap *pix = NULL;
11456
11457 if (colorspace) cs = (fz_colorspace *) colorspace;
11458 else cs = fz_device_rgb(gctx);
11459
11460 fz_try(gctx) {
11461 pix = JM_pixmap_from_display_list(gctx,
11462 (fz_display_list *) $self, matrix, cs,
11463 alpha, clip, NULL);
11464 }
11465 fz_catch(gctx) {
11466 return NULL;
11467 }
11468 return (struct Pixmap *) pix;
11469 }
11470
11471 //----------------------------------------------------------------
11472 // DisplayList.get_textpage
11473 //----------------------------------------------------------------
11474 FITZEXCEPTION(get_textpage, !result)
11475 %pythonappend get_textpage %{val.thisown = True%}
11476 struct TextPage *get_textpage(int flags = 3)
11477 {
11478 fz_display_list *this_dl = (fz_display_list *) $self;
11479 fz_stext_page *tp = NULL;
11480 fz_try(gctx) {
11481 fz_stext_options stext_options = { 0 };
11482 stext_options.flags = flags;
11483 tp = fz_new_stext_page_from_display_list(gctx, this_dl, &stext_options);
11484 }
11485 fz_catch(gctx) {
11486 return NULL;
11487 }
11488 return (struct TextPage *) tp;
11489 }
11490 %pythoncode %{
11491 def __del__(self):
11492 if not type(self) is DisplayList:
11493 return
11494 if getattr(self, "thisown", False):
11495 self.__swig_destroy__(self)
11496 %}
11497 }
11498 };
11499
11500 //------------------------------------------------------------------------
11501 // fz_stext_page
11502 //------------------------------------------------------------------------
11503 struct TextPage {
11504 %extend {
11505 ~TextPage()
11506 {
11507 DEBUGMSG1("TextPage");
11508 fz_stext_page *this_tp = (fz_stext_page *) $self;
11509 fz_drop_stext_page(gctx, this_tp);
11510 DEBUGMSG2;
11511 }
11512
11513 FITZEXCEPTION(TextPage, !result)
11514 %pythonappend TextPage %{self.thisown=True%}
11515 TextPage(PyObject *mediabox)
11516 {
11517 fz_stext_page *tp = NULL;
11518 fz_try(gctx) {
11519 tp = fz_new_stext_page(gctx, JM_rect_from_py(mediabox));
11520 }
11521 fz_catch(gctx) {
11522 return NULL;
11523 }
11524 return (struct TextPage *) tp;
11525 }
11526
11527 //----------------------------------------------------------------
11528 // method search()
11529 //----------------------------------------------------------------
11530 FITZEXCEPTION(search, !result)
11531 %pythonprepend search
11532 %{"""Locate 'needle' returning rects or quads."""%}
11533 %pythonappend search %{
11534 if not val:
11535 return val
11536 items = len(val)
11537 for i in range(items): # change entries to quads or rects
11538 q = Quad(val[i])
11539 if quads:
11540 val[i] = q
11541 else:
11542 val[i] = q.rect
11543 if quads:
11544 return val
11545 i = 0 # join overlapping rects on the same line
11546 while i < items - 1:
11547 v1 = val[i]
11548 v2 = val[i + 1]
11549 if v1.y1 != v2.y1 or (v1 & v2).is_empty:
11550 i += 1
11551 continue # no overlap on same line
11552 val[i] = v1 | v2 # join rectangles
11553 del val[i + 1] # remove v2
11554 items -= 1 # reduce item count
11555 %}
11556 PyObject *search(const char *needle, int hit_max=0, int quads=1)
11557 {
11558 PyObject *liste = NULL;
11559 fz_try(gctx) {
11560 liste = JM_search_stext_page(gctx, (fz_stext_page *) $self, needle);
11561 }
11562 fz_catch(gctx) {
11563 return NULL;
11564 }
11565 return liste;
11566 }
11567
11568
11569 //----------------------------------------------------------------
11570 // Get list of all blocks with block type and bbox as a Python list
11571 //----------------------------------------------------------------
11572 FITZEXCEPTION(_getNewBlockList, !result)
11573 PyObject *
11574 _getNewBlockList(PyObject *page_dict, int raw)
11575 {
11576 fz_try(gctx) {
11577 JM_make_textpage_dict(gctx, (fz_stext_page *) $self, page_dict, raw);
11578 }
11579 fz_catch(gctx) {
11580 return NULL;
11581 }
11582 Py_RETURN_NONE;
11583 }
11584
11585 %pythoncode %{
11586 def _textpage_dict(self, raw=False):
11587 page_dict = {"width": self.rect.width, "height": self.rect.height}
11588 self._getNewBlockList(page_dict, raw)
11589 return page_dict
11590 %}
11591
11592
11593 //----------------------------------------------------------------
11594 // Get image meta information as a Python dictionary
11595 //----------------------------------------------------------------
11596 FITZEXCEPTION(extractIMGINFO, !result)
11597 %pythonprepend extractIMGINFO
11598 %{"""Return a list with image meta information."""%}
11599 PyObject *
11600 extractIMGINFO(int hashes=0)
11601 {
11602 fz_stext_block *block;
11603 int block_n = -1;
11604 fz_stext_page *this_tpage = (fz_stext_page *) $self;
11605 PyObject *rc = NULL, *block_dict = NULL;
11606 fz_pixmap *pix = NULL;
11607 fz_try(gctx) {
11608 rc = PyList_New(0);
11609 for (block = this_tpage->first_block; block; block = block->next) {
11610 block_n++;
11611 if (block->type == FZ_STEXT_BLOCK_TEXT) {
11612 continue;
11613 }
11614 unsigned char digest[16];
11615 fz_image *img = block->u.i.image;
11616 Py_ssize_t img_size = 0;
11617 fz_compressed_buffer *cbuff = fz_compressed_image_buffer(gctx, img);
11618 if (cbuff) {
11619 img_size = (Py_ssize_t) cbuff->buffer->len;
11620 }
11621 if (hashes) {
11622 pix = fz_get_pixmap_from_image(gctx, img, NULL, NULL, NULL, NULL);
11623 if (img_size == 0) {
11624 img_size = (Py_ssize_t) pix->w * pix->h * pix->n;
11625 }
11626 fz_md5_pixmap(gctx, pix, digest);
11627 fz_drop_pixmap(gctx, pix);
11628 pix = NULL;
11629 }
11630 fz_colorspace *cs = img->colorspace;
11631 block_dict = PyDict_New();
11632 DICT_SETITEM_DROP(block_dict, dictkey_number, Py_BuildValue("i", block_n));
11633 DICT_SETITEM_DROP(block_dict, dictkey_bbox,
11634 JM_py_from_rect(block->bbox));
11635 DICT_SETITEM_DROP(block_dict, dictkey_matrix,
11636 JM_py_from_matrix(block->u.i.transform));
11637 DICT_SETITEM_DROP(block_dict, dictkey_width,
11638 Py_BuildValue("i", img->w));
11639 DICT_SETITEM_DROP(block_dict, dictkey_height,
11640 Py_BuildValue("i", img->h));
11641 DICT_SETITEM_DROP(block_dict, dictkey_colorspace,
11642 Py_BuildValue("i",
11643 fz_colorspace_n(gctx, cs)));
11644 DICT_SETITEM_DROP(block_dict, dictkey_cs_name,
11645 Py_BuildValue("s",
11646 fz_colorspace_name(gctx, cs)));
11647 DICT_SETITEM_DROP(block_dict, dictkey_xres,
11648 Py_BuildValue("i", img->xres));
11649 DICT_SETITEM_DROP(block_dict, dictkey_yres,
11650 Py_BuildValue("i", img->xres));
11651 DICT_SETITEM_DROP(block_dict, dictkey_bpc,
11652 Py_BuildValue("i", (int) img->bpc));
11653 DICT_SETITEM_DROP(block_dict, dictkey_size,
11654 Py_BuildValue("n", img_size));
11655 if (hashes) {
11656 DICT_SETITEMSTR_DROP(block_dict, "digest",
11657 PyBytes_FromStringAndSize(digest, 16));
11658 }
11659 LIST_APPEND_DROP(rc, block_dict);
11660 }
11661 }
11662 fz_always(gctx) {
11663 }
11664 fz_catch(gctx) {
11665 Py_CLEAR(rc);
11666 Py_CLEAR(block_dict);
11667 fz_drop_pixmap(gctx, pix);
11668 return NULL;
11669 }
11670 return rc;
11671 }
11672
11673
11674 //----------------------------------------------------------------
11675 // Get text blocks with their bbox and concatenated lines
11676 // as a Python list
11677 //----------------------------------------------------------------
11678 FITZEXCEPTION(extractBLOCKS, !result)
11679 %pythonprepend extractBLOCKS
11680 %{"""Return a list with text block information."""%}
11681 PyObject *
11682 extractBLOCKS()
11683 {
11684 fz_stext_block *block;
11685 fz_stext_line *line;
11686 fz_stext_char *ch;
11687 int block_n = -1;
11688 PyObject *text = NULL, *litem;
11689 fz_buffer *res = NULL;
11690 fz_var(res);
11691 fz_stext_page *this_tpage = (fz_stext_page *) $self;
11692 fz_rect tp_rect = this_tpage->mediabox;
11693 PyObject *lines = NULL;
11694 fz_try(gctx) {
11695 res = fz_new_buffer(gctx, 1024);
11696 lines = PyList_New(0);
11697 for (block = this_tpage->first_block; block; block = block->next) {
11698 block_n++;
11699 fz_rect blockrect = fz_empty_rect;
11700 if (block->type == FZ_STEXT_BLOCK_TEXT) {
11701 fz_clear_buffer(gctx, res); // set text buffer to empty
11702 int line_n = -1;
11703 int last_char = 0;
11704 for (line = block->u.t.first_line; line; line = line->next) {
11705 line_n++;
11706 fz_rect linerect = fz_empty_rect;
11707 for (ch = line->first_char; ch; ch = ch->next) {
11708 fz_rect cbbox = JM_char_bbox(gctx, line, ch);
11709 if (!JM_rects_overlap(tp_rect, cbbox) &&
11710 !fz_is_infinite_rect(tp_rect)) {
11711 continue;
11712 }
11713 JM_append_rune(gctx, res, ch->c);
11714 last_char = ch->c;
11715 linerect = fz_union_rect(linerect, cbbox);
11716 }
11717 if (last_char != 10 && !fz_is_empty_rect(linerect)) {
11718 fz_append_byte(gctx, res, 10);
11719 }
11720 blockrect = fz_union_rect(blockrect, linerect);
11721 }
11722 text = JM_EscapeStrFromBuffer(gctx, res);
11723 } else if (JM_rects_overlap(tp_rect, block->bbox) || fz_is_infinite_rect(tp_rect)) {
11724 fz_image *img = block->u.i.image;
11725 fz_colorspace *cs = img->colorspace;
11726 text = PyUnicode_FromFormat("<image: %s, width: %d, height: %d, bpc: %d>", fz_colorspace_name(gctx, cs), img->w, img->h, img->bpc);
11727 blockrect = fz_union_rect(blockrect, block->bbox);
11728 }
11729 if (!fz_is_empty_rect(blockrect)) {
11730 litem = PyTuple_New(7);
11731 PyTuple_SET_ITEM(litem, 0, Py_BuildValue("f", blockrect.x0));
11732 PyTuple_SET_ITEM(litem, 1, Py_BuildValue("f", blockrect.y0));
11733 PyTuple_SET_ITEM(litem, 2, Py_BuildValue("f", blockrect.x1));
11734 PyTuple_SET_ITEM(litem, 3, Py_BuildValue("f", blockrect.y1));
11735 PyTuple_SET_ITEM(litem, 4, Py_BuildValue("O", text));
11736 PyTuple_SET_ITEM(litem, 5, Py_BuildValue("i", block_n));
11737 PyTuple_SET_ITEM(litem, 6, Py_BuildValue("i", block->type));
11738 LIST_APPEND_DROP(lines, litem);
11739 }
11740 Py_CLEAR(text);
11741 }
11742 }
11743 fz_always(gctx) {
11744 fz_drop_buffer(gctx, res);
11745 PyErr_Clear();
11746 }
11747 fz_catch(gctx) {
11748 Py_CLEAR(lines);
11749 return NULL;
11750 }
11751 return lines;
11752 }
11753
11754 //----------------------------------------------------------------
11755 // Get text words with their bbox
11756 //----------------------------------------------------------------
11757 FITZEXCEPTION(extractWORDS, !result)
11758 %pythonprepend extractWORDS
11759 %{"""Return a list with text word information."""%}
11760 PyObject *
11761 extractWORDS(PyObject *delimiters=NULL)
11762 {
11763 fz_stext_block *block;
11764 fz_stext_line *line;
11765 fz_stext_char *ch;
11766 fz_buffer *buff = NULL;
11767 fz_var(buff);
11768 size_t buflen = 0;
11769 int block_n = -1, line_n, word_n;
11770 fz_rect wbbox = fz_empty_rect; // word bbox
11771 fz_stext_page *this_tpage = (fz_stext_page *) $self;
11772 fz_rect tp_rect = this_tpage->mediabox;
11773 int word_delimiter = 0;
11774 PyObject *lines = NULL;
11775 fz_try(gctx) {
11776 buff = fz_new_buffer(gctx, 64);
11777 lines = PyList_New(0);
11778 for (block = this_tpage->first_block; block; block = block->next) {
11779 block_n++;
11780 if (block->type != FZ_STEXT_BLOCK_TEXT) {
11781 continue;
11782 }
11783 line_n = -1;
11784 for (line = block->u.t.first_line; line; line = line->next) {
11785 line_n++;
11786 word_n = 0; // word counter per line
11787 fz_clear_buffer(gctx, buff); // reset word buffer
11788 buflen = 0; // reset char counter
11789 for (ch = line->first_char; ch; ch = ch->next) {
11790 fz_rect cbbox = JM_char_bbox(gctx, line, ch);
11791 if (!JM_rects_overlap(tp_rect, cbbox) &&
11792 !fz_is_infinite_rect(tp_rect)) {
11793 continue;
11794 }
11795 word_delimiter = JM_is_word_delimiter(ch->c, delimiters);
11796 if (word_delimiter) {
11797 if (buflen == 0) continue; // skip spaces at line start
11798 if (!fz_is_empty_rect(wbbox)) { // output word
11799 word_n = JM_append_word(gctx, lines, buff, &wbbox,
11800 block_n, line_n, word_n);
11801 }
11802 fz_clear_buffer(gctx, buff);
11803 buflen = 0; // reset char counter
11804 continue;
11805 }
11806 // append one unicode character to the word
11807 JM_append_rune(gctx, buff, ch->c);
11808 buflen++;
11809 // enlarge word bbox
11810 wbbox = fz_union_rect(wbbox, JM_char_bbox(gctx, line, ch));
11811 }
11812 if (buflen && !fz_is_empty_rect(wbbox)) {
11813 word_n = JM_append_word(gctx, lines, buff, &wbbox,
11814 block_n, line_n, word_n);
11815 }
11816 fz_clear_buffer(gctx, buff);
11817 buflen = 0;
11818 }
11819 }
11820 }
11821 fz_always(gctx) {
11822 fz_drop_buffer(gctx, buff);
11823 PyErr_Clear();
11824 }
11825 fz_catch(gctx) {
11826 return NULL;
11827 }
11828 return lines;
11829 }
11830
11831 //----------------------------------------------------------------
11832 // TextPage poolsize
11833 //----------------------------------------------------------------
11834 %pythonprepend poolsize
11835 %{"""TextPage current poolsize."""%}
11836 PyObject *poolsize()
11837 {
11838 fz_stext_page *tpage = (fz_stext_page *) $self;
11839 size_t size = fz_pool_size(gctx, tpage->pool);
11840 return PyLong_FromSize_t(size);
11841 }
11842
11843 //----------------------------------------------------------------
11844 // TextPage rectangle
11845 //----------------------------------------------------------------
11846 %pythoncode %{@property%}
11847 %pythonprepend rect
11848 %{"""TextPage rectangle."""%}
11849 %pythonappend rect %{val = Rect(val)%}
11850 PyObject *rect()
11851 {
11852 fz_stext_page *this_tpage = (fz_stext_page *) $self;
11853 fz_rect mediabox = this_tpage->mediabox;
11854 return JM_py_from_rect(mediabox);
11855 }
11856
11857 //----------------------------------------------------------------
11858 // method _extractText()
11859 //----------------------------------------------------------------
11860 FITZEXCEPTION(_extractText, !result)
11861 %newobject _extractText;
11862 PyObject *_extractText(int format)
11863 {
11864 fz_buffer *res = NULL;
11865 fz_output *out = NULL;
11866 PyObject *text = NULL;
11867 fz_var(res);
11868 fz_var(out);
11869 fz_stext_page *this_tpage = (fz_stext_page *) $self;
11870 fz_try(gctx) {
11871 res = fz_new_buffer(gctx, 1024);
11872 out = fz_new_output_with_buffer(gctx, res);
11873 switch(format) {
11874 case(1):
11875 fz_print_stext_page_as_html(gctx, out, this_tpage, 0);
11876 break;
11877 case(3):
11878 fz_print_stext_page_as_xml(gctx, out, this_tpage, 0);
11879 break;
11880 case(4):
11881 fz_print_stext_page_as_xhtml(gctx, out, this_tpage, 0);
11882 break;
11883 default:
11884 JM_print_stext_page_as_text(gctx, res, this_tpage);
11885 break;
11886 }
11887 text = JM_EscapeStrFromBuffer(gctx, res);
11888
11889 }
11890 fz_always(gctx) {
11891 fz_drop_buffer(gctx, res);
11892 fz_drop_output(gctx, out);
11893 }
11894 fz_catch(gctx) {
11895 return NULL;
11896 }
11897 return text;
11898 }
11899
11900
11901 //----------------------------------------------------------------
11902 // method extractTextbox()
11903 //----------------------------------------------------------------
11904 FITZEXCEPTION(extractTextbox, !result)
11905 PyObject *extractTextbox(PyObject *rect)
11906 {
11907 fz_stext_page *this_tpage = (fz_stext_page *) $self;
11908 fz_rect area = JM_rect_from_py(rect);
11909 PyObject *rc = NULL;
11910 fz_try(gctx) {
11911 rc = JM_copy_rectangle(gctx, this_tpage, area);
11912 }
11913 fz_catch(gctx) {
11914 return NULL;
11915 }
11916 return rc;
11917 }
11918
11919 //----------------------------------------------------------------
11920 // method extractSelection()
11921 //----------------------------------------------------------------
11922 PyObject *extractSelection(PyObject *pointa, PyObject *pointb)
11923 {
11924 fz_stext_page *this_tpage = (fz_stext_page *) $self;
11925 fz_point a = JM_point_from_py(pointa);
11926 fz_point b = JM_point_from_py(pointb);
11927 char *found = fz_copy_selection(gctx, this_tpage, a, b, 0);
11928 PyObject *rc = NULL;
11929 if (found) {
11930 rc = PyUnicode_FromString(found);
11931 JM_Free(found);
11932 } else {
11933 rc = EMPTY_STRING;
11934 }
11935 return rc;
11936 }
11937
11938 %pythoncode %{
11939 def extractText(self, sort=False) -> str:
11940 """Return simple, bare text on the page."""
11941 if sort is False:
11942 return self._extractText(0)
11943 blocks = self.extractBLOCKS()[:]
11944 blocks.sort(key=lambda b: (b[3], b[0]))
11945 return "".join([b[4] for b in blocks])
11946
11947 def extractHTML(self) -> str:
11948 """Return page content as a HTML string."""
11949 return self._extractText(1)
11950
11951 def extractJSON(self, cb=None, sort=False) -> str:
11952 """Return 'extractDICT' converted to JSON format."""
11953 import base64, json
11954 val = self._textpage_dict(raw=False)
11955
11956 class b64encode(json.JSONEncoder):
11957 def default(self, s):
11958 if type(s) in (bytes, bytearray):
11959 return base64.b64encode(s).decode()
11960
11961 if cb is not None:
11962 val["width"] = cb.width
11963 val["height"] = cb.height
11964 if sort is True:
11965 blocks = val["blocks"]
11966 blocks.sort(key=lambda b: (b["bbox"][3], b["bbox"][0]))
11967 val["blocks"] = blocks
11968 val = json.dumps(val, separators=(",", ":"), cls=b64encode, indent=1)
11969 return val
11970
11971 def extractRAWJSON(self, cb=None, sort=False) -> str:
11972 """Return 'extractRAWDICT' converted to JSON format."""
11973 import base64, json
11974 val = self._textpage_dict(raw=True)
11975
11976 class b64encode(json.JSONEncoder):
11977 def default(self,s):
11978 if type(s) in (bytes, bytearray):
11979 return base64.b64encode(s).decode()
11980
11981 if cb is not None:
11982 val["width"] = cb.width
11983 val["height"] = cb.height
11984 if sort is True:
11985 blocks = val["blocks"]
11986 blocks.sort(key=lambda b: (b["bbox"][3], b["bbox"][0]))
11987 val["blocks"] = blocks
11988 val = json.dumps(val, separators=(",", ":"), cls=b64encode, indent=1)
11989 return val
11990
11991 def extractXML(self) -> str:
11992 """Return page content as a XML string."""
11993 return self._extractText(3)
11994
11995 def extractXHTML(self) -> str:
11996 """Return page content as a XHTML string."""
11997 return self._extractText(4)
11998
11999 def extractDICT(self, cb=None, sort=False) -> dict:
12000 """Return page content as a Python dict of images and text spans."""
12001 val = self._textpage_dict(raw=False)
12002 if cb is not None:
12003 val["width"] = cb.width
12004 val["height"] = cb.height
12005 if sort is True:
12006 blocks = val["blocks"]
12007 blocks.sort(key=lambda b: (b["bbox"][3], b["bbox"][0]))
12008 val["blocks"] = blocks
12009 return val
12010
12011 def extractRAWDICT(self, cb=None, sort=False) -> dict:
12012 """Return page content as a Python dict of images and text characters."""
12013 val = self._textpage_dict(raw=True)
12014 if cb is not None:
12015 val["width"] = cb.width
12016 val["height"] = cb.height
12017 if sort is True:
12018 blocks = val["blocks"]
12019 blocks.sort(key=lambda b: (b["bbox"][3], b["bbox"][0]))
12020 val["blocks"] = blocks
12021 return val
12022
12023 def __del__(self):
12024 if not type(self) is TextPage:
12025 return
12026 if getattr(self, "thisown", False):
12027 self.__swig_destroy__(self)
12028 %}
12029 }
12030 };
12031
12032 //------------------------------------------------------------------------
12033 // Graftmap - only used internally for inter-PDF object copy operations
12034 //------------------------------------------------------------------------
12035 struct Graftmap
12036 {
12037 %extend
12038 {
12039 ~Graftmap()
12040 {
12041 DEBUGMSG1("Graftmap");
12042 pdf_graft_map *this_gm = (pdf_graft_map *) $self;
12043 pdf_drop_graft_map(gctx, this_gm);
12044 DEBUGMSG2;
12045 }
12046
12047 FITZEXCEPTION(Graftmap, !result)
12048 Graftmap(struct Document *doc)
12049 {
12050 pdf_graft_map *map = NULL;
12051 fz_try(gctx) {
12052 pdf_document *dst = pdf_specifics(gctx, (fz_document *) doc);
12053 ASSERT_PDF(dst);
12054 map = pdf_new_graft_map(gctx, dst);
12055 }
12056 fz_catch(gctx) {
12057 return NULL;
12058 }
12059 return (struct Graftmap *) map;
12060 }
12061
12062 %pythoncode %{
12063 def __del__(self):
12064 if not type(self) is Graftmap:
12065 return
12066 if getattr(self, "thisown", False):
12067 self.__swig_destroy__(self)
12068 %}
12069 }
12070 };
12071
12072
12073 //------------------------------------------------------------------------
12074 // TextWriter
12075 //------------------------------------------------------------------------
12076 struct TextWriter
12077 {
12078 %extend {
12079 ~TextWriter()
12080 {
12081 DEBUGMSG1("TextWriter");
12082 fz_text *this_tw = (fz_text *) $self;
12083 fz_drop_text(gctx, this_tw);
12084 DEBUGMSG2;
12085 }
12086
12087 FITZEXCEPTION(TextWriter, !result)
12088 %pythonprepend TextWriter
12089 %{"""Stores text spans for later output on compatible PDF pages."""%}
12090 %pythonappend TextWriter %{
12091 self.opacity = opacity
12092 self.color = color
12093 self.rect = Rect(page_rect)
12094 self.ctm = Matrix(1, 0, 0, -1, 0, self.rect.height)
12095 self.ictm = ~self.ctm
12096 self.last_point = Point()
12097 self.last_point.__doc__ = "Position following last text insertion."
12098 self.text_rect = Rect()
12099
12100 self.text_rect.__doc__ = "Accumulated area of text spans."
12101 self.used_fonts = set()
12102 self.thisown = True
12103 %}
12104 TextWriter(PyObject *page_rect, float opacity=1, PyObject *color=NULL )
12105 {
12106 fz_text *text = NULL;
12107 fz_try(gctx) {
12108 text = fz_new_text(gctx);
12109 }
12110 fz_catch(gctx) {
12111 return NULL;
12112 }
12113 return (struct TextWriter *) text;
12114 }
12115
12116 FITZEXCEPTION(append, !result)
12117 %pythonprepend append %{
12118 """Store 'text' at point 'pos' using 'font' and 'fontsize'."""
12119
12120 pos = Point(pos) * self.ictm
12121 if font is None:
12122 font = Font("helv")
12123 if not font.is_writable:
12124 raise ValueError("Unsupported font '%s'." % font.name)
12125 if right_to_left:
12126 text = self.clean_rtl(text)
12127 text = "".join(reversed(text))
12128 right_to_left = 0
12129 %}
12130 %pythonappend append %{
12131 self.last_point = Point(val[-2:]) * self.ctm
12132 self.text_rect = self._bbox * self.ctm
12133 val = self.text_rect, self.last_point
12134 if font.flags["mono"] == 1:
12135 self.used_fonts.add(font)
12136 %}
12137 PyObject *
12138 append(PyObject *pos, char *text, struct Font *font=NULL, float fontsize=11, char *language=NULL, int right_to_left=0, int small_caps=0)
12139 {
12140 fz_text_language lang = fz_text_language_from_string(language);
12141 fz_point p = JM_point_from_py(pos);
12142 fz_matrix trm = fz_make_matrix(fontsize, 0, 0, fontsize, p.x, p.y);
12143 int markup_dir = 0, wmode = 0;
12144 fz_try(gctx) {
12145 if (small_caps == 0) {
12146 trm = fz_show_string(gctx, (fz_text *) $self, (fz_font *) font,
12147 trm, text, wmode, right_to_left, markup_dir, lang);
12148 } else {
12149 trm = JM_show_string_cs(gctx, (fz_text *) $self, (fz_font *) font,
12150 trm, text, wmode, right_to_left, markup_dir, lang);
12151 }
12152 }
12153 fz_catch(gctx) {
12154 return NULL;
12155 }
12156 return JM_py_from_matrix(trm);
12157 }
12158
12159 %pythoncode %{
12160 def appendv(self, pos, text, font=None, fontsize=11,
12161 language=None, small_caps=False):
12162 """Append text in vertical write mode."""
12163 lheight = fontsize * 1.2
12164 for c in text:
12165 self.append(pos, c, font=font, fontsize=fontsize,
12166 language=language, small_caps=small_caps)
12167 pos.y += lheight
12168 return self.text_rect, self.last_point
12169
12170
12171 def clean_rtl(self, text):
12172 """Revert the sequence of Latin text parts.
12173
12174 Text with right-to-left writing direction (Arabic, Hebrew) often
12175 contains Latin parts, which are written in left-to-right: numbers, names,
12176 etc. For output as PDF text we need *everything* in right-to-left.
12177 E.g. an input like "<arabic> ABCDE FG HIJ <arabic> KL <arabic>" will be
12178 converted to "<arabic> JIH GF EDCBA <arabic> LK <arabic>". The Arabic
12179 parts remain untouched.
12180
12181 Args:
12182 text: str
12183 Returns:
12184 Massaged string.
12185 """
12186 if not text:
12187 return text
12188 # split into words at space boundaries
12189 words = text.split(" ")
12190 idx = []
12191 for i in range(len(words)):
12192 w = words[i]
12193 # revert character sequence for Latin only words
12194 if not (len(w) < 2 or max([ord(c) for c in w]) > 255):
12195 words[i] = "".join(reversed(w))
12196 idx.append(i) # stored index of Latin word
12197
12198 # adjacent Latin words must revert their sequence, too
12199 idx2 = [] # store indices of adjacent Latin words
12200 for i in range(len(idx)):
12201 if idx2 == []: # empty yet?
12202 idx2.append(idx[i]) # store Latin word number
12203
12204 elif idx[i] > idx2[-1] + 1: # large gap to last?
12205 if len(idx2) > 1: # at least two consecutives?
12206 words[idx2[0] : idx2[-1] + 1] = reversed(
12207 words[idx2[0] : idx2[-1] + 1]
12208 ) # revert their sequence
12209 idx2 = [idx[i]] # re-initialize
12210
12211 elif idx[i] == idx2[-1] + 1: # new adjacent Latin word
12212 idx2.append(idx[i])
12213
12214 text = " ".join(words)
12215 return text
12216 %}
12217
12218
12219 %pythoncode %{@property%}
12220 %pythonappend _bbox%{val = Rect(val)%}
12221 PyObject *_bbox()
12222 {
12223 return JM_py_from_rect(fz_bound_text(gctx, (fz_text *) $self, NULL, fz_identity));
12224 }
12225
12226 FITZEXCEPTION(write_text, !result)
12227 %pythonprepend write_text%{
12228 """Write the text to a PDF page having the TextWriter's page size.
12229
12230 Args:
12231 page: a PDF page having same size.
12232 color: override text color.
12233 opacity: override transparency.
12234 overlay: put in foreground or background.
12235 morph: tuple(Point, Matrix), apply a matrix with a fixpoint.
12236 matrix: Matrix to be used instead of 'morph' argument.
12237 render_mode: (int) PDF render mode operator 'Tr'.
12238 """
12239
12240 CheckParent(page)
12241 if abs(self.rect - page.rect) > 1e-3:
12242 raise ValueError("incompatible page rect")
12243 if morph != None:
12244 if (type(morph) not in (tuple, list)
12245 or type(morph[0]) is not Point
12246 or type(morph[1]) is not Matrix
12247 ):
12248 raise ValueError("morph must be (Point, Matrix) or None")
12249 if matrix != None and morph != None:
12250 raise ValueError("only one of matrix, morph is allowed")
12251 if getattr(opacity, "__float__", None) is None or opacity == -1:
12252 opacity = self.opacity
12253 if color is None:
12254 color = self.color
12255 %}
12256
12257 %pythonappend write_text%{
12258 max_nums = val[0]
12259 content = val[1]
12260 max_alp, max_font = max_nums
12261 old_cont_lines = content.splitlines()
12262
12263 optcont = page._get_optional_content(oc)
12264 if optcont != None:
12265 bdc = "/OC /%s BDC" % optcont
12266 emc = "EMC"
12267 else:
12268 bdc = emc = ""
12269
12270 new_cont_lines = ["q"]
12271 if bdc:
12272 new_cont_lines.append(bdc)
12273
12274 cb = page.cropbox_position
12275 if page.rotation in (90, 270):
12276 delta = page.rect.height - page.rect.width
12277 else:
12278 delta = 0
12279 mb = page.mediabox
12280 if bool(cb) or mb.y0 != 0 or delta != 0:
12281 new_cont_lines.append("1 0 0 1 %g %g cm" % (cb.x, cb.y + mb.y0 - delta))
12282
12283 if morph:
12284 p = morph[0] * self.ictm
12285 delta = Matrix(1, 1).pretranslate(p.x, p.y)
12286 matrix = ~delta * morph[1] * delta
12287 if morph or matrix:
12288 new_cont_lines.append("%g %g %g %g %g %g cm" % JM_TUPLE(matrix))
12289
12290 for line in old_cont_lines:
12291 if line.endswith(" cm"):
12292 continue
12293 if line == "BT":
12294 new_cont_lines.append(line)
12295 new_cont_lines.append("%i Tr" % render_mode)
12296 continue
12297 if line.endswith(" gs"):
12298 alp = int(line.split()[0][4:]) + max_alp
12299 line = "/Alp%i gs" % alp
12300 elif line.endswith(" Tf"):
12301 temp = line.split()
12302 fsize = float(temp[1])
12303 if render_mode != 0:
12304 w = fsize * 0.05
12305 else:
12306 w = 1
12307 new_cont_lines.append("%g w" % w)
12308 font = int(temp[0][2:]) + max_font
12309 line = " ".join(["/F%i" % font] + temp[1:])
12310 elif line.endswith(" rg"):
12311 new_cont_lines.append(line.replace("rg", "RG"))
12312 elif line.endswith(" g"):
12313 new_cont_lines.append(line.replace(" g", " G"))
12314 elif line.endswith(" k"):
12315 new_cont_lines.append(line.replace(" k", " K"))
12316 new_cont_lines.append(line)
12317 if emc:
12318 new_cont_lines.append(emc)
12319 new_cont_lines.append("Q\n")
12320 content = "\n".join(new_cont_lines).encode("utf-8")
12321 TOOLS._insert_contents(page, content, overlay=overlay)
12322 val = None
12323 for font in self.used_fonts:
12324 repair_mono_font(page, font)
12325 %}
12326 PyObject *write_text(struct Page *page, PyObject *color=NULL, float opacity=-1, int overlay=1,
12327 PyObject *morph=NULL, PyObject *matrix=NULL, int render_mode=0, int oc=0)
12328 {
12329 pdf_page *pdfpage = pdf_page_from_fz_page(gctx, (fz_page *) page);
12330 pdf_obj *resources = NULL;
12331 fz_buffer *contents = NULL;
12332 fz_device *dev = NULL;
12333 PyObject *result = NULL, *max_nums, *cont_string;
12334 float alpha = 1;
12335 if (opacity >= 0 && opacity < 1)
12336 alpha = opacity;
12337 fz_colorspace *colorspace;
12338 int ncol = 1;
12339 float dev_color[4] = {0, 0, 0, 0};
12340 if (EXISTS(color)) {
12341 JM_color_FromSequence(color, &ncol, dev_color);
12342 }
12343 switch(ncol) {
12344 case 3: colorspace = fz_device_rgb(gctx); break;
12345 case 4: colorspace = fz_device_cmyk(gctx); break;
12346 default: colorspace = fz_device_gray(gctx); break;
12347 }
12348
12349 fz_var(contents);
12350 fz_var(resources);
12351 fz_var(dev);
12352 fz_try(gctx) {
12353 ASSERT_PDF(pdfpage);
12354 resources = pdf_new_dict(gctx, pdfpage->doc, 5);
12355 contents = fz_new_buffer(gctx, 1024);
12356 dev = pdf_new_pdf_device(gctx, pdfpage->doc, fz_identity,
12357 resources, contents);
12358 fz_fill_text(gctx, dev, (fz_text *) $self, fz_identity,
12359 colorspace, dev_color, alpha, fz_default_color_params);
12360 fz_close_device(gctx, dev);
12361
12362 // copy generated resources into the one of the page
12363 max_nums = JM_merge_resources(gctx, pdfpage, resources);
12364 cont_string = JM_EscapeStrFromBuffer(gctx, contents);
12365 result = Py_BuildValue("OO", max_nums, cont_string);
12366 Py_DECREF(cont_string);
12367 Py_DECREF(max_nums);
12368 }
12369 fz_always(gctx) {
12370 fz_drop_buffer(gctx, contents);
12371 pdf_drop_obj(gctx, resources);
12372 fz_drop_device(gctx, dev);
12373 }
12374 fz_catch(gctx) {
12375 return NULL;
12376 }
12377 return result;
12378 }
12379 %pythoncode %{
12380 def __del__(self):
12381 if not type(self) is TextWriter:
12382 return
12383 if getattr(self, "thisown", False):
12384 self.__swig_destroy__(self)
12385 %}
12386 }
12387 };
12388
12389
12390 //------------------------------------------------------------------------
12391 // Font
12392 //------------------------------------------------------------------------
12393 struct Font
12394 {
12395 %extend
12396 {
12397 ~Font()
12398 {
12399 DEBUGMSG1("Font");
12400 fz_font *this_font = (fz_font *) $self;
12401 fz_drop_font(gctx, this_font);
12402 DEBUGMSG2;
12403 }
12404
12405 FITZEXCEPTION(Font, !result)
12406 %pythonprepend Font %{
12407 if fontbuffer:
12408 if hasattr(fontbuffer, "getvalue"):
12409 fontbuffer = fontbuffer.getvalue()
12410 elif isinstance(fontbuffer, bytearray):
12411 fontbuffer = bytes(fontbuffer)
12412 if not isinstance(fontbuffer, bytes):
12413 raise ValueError("bad type: 'fontbuffer'")
12414
12415 if isinstance(fontname, str):
12416 fname_lower = fontname.lower()
12417 if "/" in fname_lower or "\\" in fname_lower or "." in fname_lower:
12418 print("Warning: did you mean a fontfile?")
12419
12420 if fname_lower in ("cjk", "china-t", "china-ts"):
12421 ordering = 0
12422 elif fname_lower.startswith("china-s"):
12423 ordering = 1
12424 elif fname_lower.startswith("korea"):
12425 ordering = 3
12426 elif fname_lower.startswith("japan"):
12427 ordering = 2
12428 elif fname_lower in fitz_fontdescriptors.keys():
12429 import pymupdf_fonts # optional fonts
12430 fontbuffer = pymupdf_fonts.myfont(fname_lower) # make a copy
12431 fontname = None # ensure using fontbuffer only
12432 del pymupdf_fonts # remove package again
12433
12434 elif ordering < 0:
12435 fontname = Base14_fontdict.get(fontname, fontname)
12436 %}
12437 %pythonappend Font %{self.thisown = True%}
12438 Font(char *fontname=NULL, char *fontfile=NULL,
12439 PyObject *fontbuffer=NULL, int script=0,
12440 char *language=NULL, int ordering=-1, int is_bold=0,
12441 int is_italic=0, int is_serif=0, int embed=1)
12442 {
12443 fz_font *font = NULL;
12444 fz_try(gctx) {
12445 fz_text_language lang = fz_text_language_from_string(language);
12446 font = JM_get_font(gctx, fontname, fontfile,
12447 fontbuffer, script, lang, ordering,
12448 is_bold, is_italic, is_serif, embed);
12449 }
12450 fz_catch(gctx) {
12451 return NULL;
12452 }
12453 return (struct Font *) font;
12454 }
12455
12456
12457 %pythonprepend glyph_advance
12458 %{"""Return the glyph width of a unicode (font size 1)."""%}
12459 PyObject *glyph_advance(int chr, char *language=NULL, int script=0, int wmode=0, int small_caps=0)
12460 {
12461 fz_font *font, *thisfont = (fz_font *) $self;
12462 int gid;
12463 fz_text_language lang = fz_text_language_from_string(language);
12464 if (small_caps) {
12465 gid = fz_encode_character_sc(gctx, thisfont, chr);
12466 if (gid >= 0) font = thisfont;
12467 } else {
12468 gid = fz_encode_character_with_fallback(gctx, thisfont, chr, script, lang, &font);
12469 }
12470 return PyFloat_FromDouble((double) fz_advance_glyph(gctx, font, gid, wmode));
12471 }
12472
12473
12474 FITZEXCEPTION(text_length, !result)
12475 %pythonprepend text_length
12476 %{"""Return length of unicode 'text' under a fontsize."""%}
12477 PyObject *text_length(PyObject *text, double fontsize=11, char *language=NULL, int script=0, int wmode=0, int small_caps=0)
12478 {
12479 fz_font *font=NULL, *thisfont = (fz_font *) $self;
12480 fz_text_language lang = fz_text_language_from_string(language);
12481 double rc = 0;
12482 int gid;
12483 fz_try(gctx) {
12484 if (!PyUnicode_Check(text) || PyUnicode_READY(text) != 0) {
12485 RAISEPY(gctx, MSG_BAD_TEXT, PyExc_TypeError);
12486 }
12487 Py_ssize_t i, len = PyUnicode_GET_LENGTH(text);
12488 int kind = PyUnicode_KIND(text);
12489 void *data = PyUnicode_DATA(text);
12490 for (i = 0; i < len; i++) {
12491 int c = PyUnicode_READ(kind, data, i);
12492 if (small_caps) {
12493 gid = fz_encode_character_sc(gctx, thisfont, c);
12494 if (gid >= 0) font = thisfont;
12495 } else {
12496 gid = fz_encode_character_with_fallback(gctx,thisfont, c, script, lang, &font);
12497 }
12498 rc += (double) fz_advance_glyph(gctx, font, gid, wmode);
12499 }
12500 }
12501 fz_catch(gctx) {
12502 PyErr_Clear();
12503 return NULL;
12504 }
12505 rc *= fontsize;
12506 return PyFloat_FromDouble(rc);
12507 }
12508
12509
12510 FITZEXCEPTION(char_lengths, !result)
12511 %pythonprepend char_lengths
12512 %{"""Return tuple of char lengths of unicode 'text' under a fontsize."""%}
12513 PyObject *char_lengths(PyObject *text, double fontsize=11, char *language=NULL, int script=0, int wmode=0, int small_caps=0)
12514 {
12515 fz_font *font, *thisfont = (fz_font *) $self;
12516 fz_text_language lang = fz_text_language_from_string(language);
12517 PyObject *rc = NULL;
12518 int gid;
12519 fz_try(gctx) {
12520 if (!PyUnicode_Check(text) || PyUnicode_READY(text) != 0) {
12521 RAISEPY(gctx, MSG_BAD_TEXT, PyExc_TypeError);
12522 }
12523 Py_ssize_t i, len = PyUnicode_GET_LENGTH(text);
12524 int kind = PyUnicode_KIND(text);
12525 void *data = PyUnicode_DATA(text);
12526 rc = PyTuple_New(len);
12527 for (i = 0; i < len; i++) {
12528 int c = PyUnicode_READ(kind, data, i);
12529 if (small_caps) {
12530 gid = fz_encode_character_sc(gctx, thisfont, c);
12531 if (gid >= 0) font = thisfont;
12532 } else {
12533 gid = fz_encode_character_with_fallback(gctx,thisfont, c, script, lang, &font);
12534 }
12535 PyTuple_SET_ITEM(rc, i,
12536 PyFloat_FromDouble(fontsize * (double) fz_advance_glyph(gctx, font, gid, wmode)));
12537 }
12538 }
12539 fz_catch(gctx) {
12540 PyErr_Clear();
12541 Py_CLEAR(rc);
12542 return NULL;
12543 }
12544 return rc;
12545 }
12546
12547
12548 %pythonprepend glyph_bbox
12549 %{"""Return the glyph bbox of a unicode (font size 1)."""%}
12550 %pythonappend glyph_bbox %{val = Rect(val)%}
12551 PyObject *glyph_bbox(int chr, char *language=NULL, int script=0, int small_caps=0)
12552 {
12553 fz_font *font, *thisfont = (fz_font *) $self;
12554 int gid;
12555 fz_text_language lang = fz_text_language_from_string(language);
12556 if (small_caps) {
12557 gid = fz_encode_character_sc(gctx, thisfont, chr);
12558 if (gid >= 0) font = thisfont;
12559 } else {
12560 gid = fz_encode_character_with_fallback(gctx, thisfont, chr, script, lang, &font);
12561 }
12562 return JM_py_from_rect(fz_bound_glyph(gctx, font, gid, fz_identity));
12563 }
12564
12565 %pythonprepend has_glyph
12566 %{"""Check whether font has a glyph for this unicode."""%}
12567 PyObject *has_glyph(int chr, char *language=NULL, int script=0, int fallback=0, int small_caps=0)
12568 {
12569 fz_font *font, *thisfont = (fz_font *) $self;
12570 fz_text_language lang;
12571 int gid = 0;
12572 if (fallback) {
12573 lang = fz_text_language_from_string(language);
12574 gid = fz_encode_character_with_fallback(gctx, (fz_font *) $self, chr, script, lang, &font);
12575 } else {
12576 if (!small_caps) {
12577 gid = fz_encode_character(gctx, thisfont, chr);
12578 } else {
12579 gid = fz_encode_character_sc(gctx, thisfont, chr);
12580 }
12581 }
12582 return Py_BuildValue("i", gid);
12583 }
12584
12585
12586 %pythoncode %{
12587 def valid_codepoints(self):
12588 from array import array
12589 gc = self.glyph_count
12590 cp = array("l", (0,) * gc)
12591 arr = cp.buffer_info()
12592 self._valid_unicodes(arr)
12593 return array("l", sorted(set(cp))[1:])
12594 %}
12595 void _valid_unicodes(PyObject *arr)
12596 {
12597 fz_font *font = (fz_font *) $self;
12598 PyObject *temp = PySequence_ITEM(arr, 0);
12599 void *ptr = PyLong_AsVoidPtr(temp);
12600 JM_valid_chars(gctx, font, ptr);
12601 Py_DECREF(temp);
12602 }
12603
12604
12605 %pythoncode %{@property%}
12606 PyObject *flags()
12607 {
12608 fz_font_flags_t *f = fz_font_flags((fz_font *) $self);
12609 if (!f) Py_RETURN_NONE;
12610 return Py_BuildValue(
12611 "{s:N,s:N,s:N,s:N,s:N,s:N,s:N,s:N,s:N,s:N,s:N,s:N"
12612 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR >= 22
12613 ",s:N,s:N"
12614 #endif
12615 "}",
12616 "mono", JM_BOOL(f->is_mono),
12617 "serif", JM_BOOL(f->is_serif),
12618 "bold", JM_BOOL(f->is_bold),
12619 "italic", JM_BOOL(f->is_italic),
12620 "substitute", JM_BOOL(f->ft_substitute),
12621 "stretch", JM_BOOL(f->ft_stretch),
12622 "fake-bold", JM_BOOL(f->fake_bold),
12623 "fake-italic", JM_BOOL(f->fake_italic),
12624 "opentype", JM_BOOL(f->has_opentype),
12625 "invalid-bbox", JM_BOOL(f->invalid_bbox),
12626 "cjk", JM_BOOL(f->cjk),
12627 "cjk-lang", (f->cjk ? PyLong_FromUnsignedLong((unsigned long) f->cjk_lang) : Py_BuildValue("s",NULL))
12628 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR >= 22
12629 ,
12630 "embed", JM_BOOL(f->embed),
12631 "never-embed", JM_BOOL(f->never_embed)
12632 #endif
12633 );
12634
12635 }
12636
12637
12638 %pythoncode %{@property%}
12639 PyObject *is_bold()
12640 {
12641 fz_font *font = (fz_font *) $self;
12642 if (fz_font_is_bold(gctx,font)) {
12643 Py_RETURN_TRUE;
12644 }
12645 Py_RETURN_FALSE;
12646 }
12647
12648
12649 %pythoncode %{@property%}
12650 PyObject *is_serif()
12651 {
12652 fz_font *font = (fz_font *) $self;
12653 if (fz_font_is_serif(gctx,font)) {
12654 Py_RETURN_TRUE;
12655 }
12656 Py_RETURN_FALSE;
12657 }
12658
12659
12660 %pythoncode %{@property%}
12661 PyObject *is_italic()
12662 {
12663 fz_font *font = (fz_font *) $self;
12664 if (fz_font_is_italic(gctx,font)) {
12665 Py_RETURN_TRUE;
12666 }
12667 Py_RETURN_FALSE;
12668 }
12669
12670
12671 %pythoncode %{@property%}
12672 PyObject *is_monospaced()
12673 {
12674 fz_font *font = (fz_font *) $self;
12675 if (fz_font_is_monospaced(gctx,font)) {
12676 Py_RETURN_TRUE;
12677 }
12678 Py_RETURN_FALSE;
12679 }
12680
12681
12682 /* temporarily disabled
12683 * PyObject *is_writable()
12684 * {
12685 * fz_font *font = (fz_font *) $self;
12686 * if (fz_font_t3_procs(gctx, font) ||
12687 * fz_font_flags(font)->ft_substitute ||
12688 * !pdf_font_writing_supported(font)) {
12689 * Py_RETURN_FALSE;
12690 * }
12691 * Py_RETURN_TRUE;
12692 * }
12693 */
12694
12695 %pythoncode %{@property%}
12696 PyObject *name()
12697 {
12698 return JM_UnicodeFromStr(fz_font_name(gctx, (fz_font *) $self));
12699 }
12700
12701 %pythoncode %{@property%}
12702 int glyph_count()
12703 {
12704 fz_font *this_font = (fz_font *) $self;
12705 return this_font->glyph_count;
12706 }
12707
12708 %pythoncode %{@property%}
12709 PyObject *buffer()
12710 {
12711 fz_font *this_font = (fz_font *) $self;
12712 unsigned char *data = NULL;
12713 size_t len = fz_buffer_storage(gctx, this_font->buffer, &data);
12714 return JM_BinFromCharSize(data, len);
12715 }
12716
12717 %pythoncode %{@property%}
12718 %pythonappend bbox%{val = Rect(val)%}
12719 PyObject *bbox()
12720 {
12721 fz_font *this_font = (fz_font *) $self;
12722 return JM_py_from_rect(fz_font_bbox(gctx, this_font));
12723 }
12724
12725 %pythoncode %{@property%}
12726 %pythonprepend ascender
12727 %{"""Return the glyph ascender value."""%}
12728 float ascender()
12729 {
12730 return fz_font_ascender(gctx, (fz_font *) $self);
12731 }
12732
12733
12734 %pythoncode %{@property%}
12735 %pythonprepend descender
12736 %{"""Return the glyph descender value."""%}
12737 float descender()
12738 {
12739 return fz_font_descender(gctx, (fz_font *) $self);
12740 }
12741
12742
12743 %pythoncode %{
12744
12745 @property
12746 def is_writable(self):
12747 return True
12748
12749 def glyph_name_to_unicode(self, name):
12750 """Return the unicode for a glyph name."""
12751 return glyph_name_to_unicode(name)
12752
12753 def unicode_to_glyph_name(self, ch):
12754 """Return the glyph name for a unicode."""
12755 return unicode_to_glyph_name(ch)
12756
12757 def __repr__(self):
12758 return "Font('%s')" % self.name
12759
12760 def __del__(self):
12761 if not type(self) is Font:
12762 return
12763 if getattr(self, "thisown", False):
12764 self.__swig_destroy__(self)
12765 %}
12766 }
12767 };
12768
12769
12770 //------------------------------------------------------------------------
12771 // DocumentWriter
12772 //------------------------------------------------------------------------
12773
12774 struct DocumentWriter
12775 {
12776 %extend
12777 {
12778 ~DocumentWriter()
12779 {
12780 // need this structure to free any fz_output the writer may have
12781 typedef struct { // copied from pdf_write.c
12782 fz_document_writer super;
12783 pdf_document *pdf;
12784 pdf_write_options opts;
12785 fz_output *out;
12786 fz_rect mediabox;
12787 pdf_obj *resources;
12788 fz_buffer *contents;
12789 } pdf_writer;
12790
12791 fz_document_writer *writer_fz = (fz_document_writer *) $self;
12792 fz_output *out = NULL;
12793 pdf_writer *writer_pdf = (pdf_writer *) writer_fz;
12794 if (writer_pdf) {
12795 out = writer_pdf->out;
12796 if (out) {
12797 DEBUGMSG1("Output of DocumentWriter");
12798 fz_drop_output(gctx, out);
12799 writer_pdf->out = NULL;
12800 DEBUGMSG2;
12801 }
12802 }
12803 DEBUGMSG1("DocumentWriter");
12804 fz_drop_document_writer( gctx, writer_fz);
12805 DEBUGMSG2;
12806 }
12807
12808 FITZEXCEPTION(DocumentWriter, !result)
12809 %pythonprepend DocumentWriter
12810 %{
12811 if type(path) is str:
12812 pass
12813 elif hasattr(path, "absolute"):
12814 path = str(path)
12815 elif hasattr(path, "name"):
12816 path = path.name
12817 if options==None:
12818 options=""
12819 %}
12820 %pythonappend DocumentWriter
12821 %{
12822 %}
12823 DocumentWriter( PyObject* path, const char* options=NULL)
12824 {
12825 fz_output *out = NULL;
12826 fz_document_writer* ret=NULL;
12827 fz_try(gctx) {
12828 if (PyUnicode_Check(path)) {
12829 ret = fz_new_pdf_writer( gctx, PyUnicode_AsUTF8(path), options);
12830 } else {
12831 out = JM_new_output_fileptr(gctx, path);
12832 ret = fz_new_pdf_writer_with_output(gctx, out, options);
12833 }
12834 }
12835
12836 fz_catch(gctx) {
12837 return NULL;
12838 }
12839 return (struct DocumentWriter*) ret;
12840 }
12841
12842 struct DeviceWrapper* begin_page( PyObject* mediabox)
12843 {
12844 fz_rect mediabox2 = JM_rect_from_py(mediabox);
12845 fz_device* device = fz_begin_page( gctx, (fz_document_writer*) $self, mediabox2);
12846 struct DeviceWrapper* device_wrapper
12847 = (struct DeviceWrapper*) calloc(1, sizeof(struct DeviceWrapper))
12848 ;
12849 device_wrapper->device = device;
12850 device_wrapper->list = NULL;
12851 return device_wrapper;
12852 }
12853
12854 void end_page()
12855 {
12856 fz_end_page( gctx, (fz_document_writer*) $self);
12857 }
12858
12859 void close()
12860 {
12861 fz_document_writer *writer = (fz_document_writer*) $self;
12862 fz_close_document_writer( gctx, writer);
12863 }
12864 %pythoncode
12865 %{
12866 def __del__(self):
12867 if not type(self) is DocumentWriter:
12868 return
12869 if getattr(self, "thisown", False):
12870 self.__swig_destroy__(self)
12871
12872 def __enter__(self):
12873 return self
12874
12875 def __exit__(self, *args):
12876 self.close()
12877 %}
12878 }
12879 };
12880
12881 //------------------------------------------------------------------------
12882 // Archive
12883 //------------------------------------------------------------------------
12884 struct Archive
12885 {
12886 %extend
12887 {
12888 ~Archive()
12889 {
12890 DEBUGMSG1("Archive");
12891 fz_drop_archive( gctx, (fz_archive *) $self);
12892 DEBUGMSG2;
12893 }
12894 FITZEXCEPTION(Archive, !result)
12895 %pythonprepend Archive %{
12896 self._subarchives = []
12897 %}
12898 %pythonappend Archive %{
12899 self.thisown = True
12900 if args != ():
12901 self.add(*args)
12902 %}
12903
12904 //---------------------------------------
12905 // new empty archive
12906 //---------------------------------------
12907 Archive(struct Archive *a0=NULL, const char *path=NULL)
12908 {
12909 fz_archive *arch=NULL;
12910 fz_try(gctx) {
12911 arch = fz_new_multi_archive(gctx);
12912 }
12913 fz_catch(gctx) {
12914 return NULL;
12915 }
12916 return (struct Archive *) arch;
12917 }
12918
12919 Archive(PyObject *a0=NULL, const char *path=NULL)
12920 {
12921 fz_archive *arch=NULL;
12922 fz_try(gctx) {
12923 arch = fz_new_multi_archive(gctx);
12924 }
12925 fz_catch(gctx) {
12926 return NULL;
12927 }
12928 return (struct Archive *) arch;
12929 }
12930
12931 FITZEXCEPTION(has_entry, !result)
12932 PyObject *has_entry(const char *name)
12933 {
12934 fz_archive *arch = (fz_archive *) $self;
12935 int ret = 0;
12936 fz_try(gctx) {
12937 ret = fz_has_archive_entry(gctx, arch, name);
12938 }
12939 fz_catch(gctx) {
12940 return NULL;
12941 }
12942 return JM_BOOL(ret);
12943 }
12944
12945 FITZEXCEPTION(read_entry, !result)
12946 PyObject *read_entry(const char *name)
12947 {
12948 fz_archive *arch = (fz_archive *) $self;
12949 PyObject *ret = NULL;
12950 fz_buffer *buff = NULL;
12951 fz_try(gctx) {
12952 buff = fz_read_archive_entry(gctx, arch, name);
12953 ret = JM_BinFromBuffer(gctx, buff);
12954 }
12955 fz_always(gctx) {
12956 fz_drop_buffer(gctx, buff);
12957 }
12958 fz_catch(gctx) {
12959 return NULL;
12960 }
12961 return ret;
12962 }
12963
12964 //--------------------------------------
12965 // add dir
12966 //--------------------------------------
12967 FITZEXCEPTION(_add_dir, !result)
12968 PyObject *_add_dir(const char *folder, const char *path=NULL)
12969 {
12970 fz_archive *arch = (fz_archive *) $self;
12971 fz_archive *sub = NULL;
12972 fz_try(gctx) {
12973 sub = fz_open_directory(gctx, folder);
12974 fz_mount_multi_archive(gctx, arch, sub, path);
12975 }
12976 fz_always(gctx) {
12977 fz_drop_archive(gctx, sub);
12978 }
12979 fz_catch(gctx) {
12980 return NULL;
12981 }
12982 Py_RETURN_NONE;
12983 }
12984
12985 //----------------------------------
12986 // add archive
12987 //----------------------------------
12988 FITZEXCEPTION(_add_arch, !result)
12989 PyObject *_add_arch(struct Archive *subarch, const char *path=NULL)
12990 {
12991 fz_archive *arch = (fz_archive *) $self;
12992 fz_archive *sub = (fz_archive *) subarch;
12993 fz_try(gctx) {
12994 fz_mount_multi_archive(gctx, arch, sub, path);
12995 }
12996 fz_catch(gctx) {
12997 return NULL;
12998 }
12999 Py_RETURN_NONE;
13000 }
13001
13002 //----------------------------------
13003 // add ZIP/TAR from file
13004 //----------------------------------
13005 FITZEXCEPTION(_add_ziptarfile, !result)
13006 PyObject *_add_ziptarfile(const char *filepath, int type, const char *path=NULL)
13007 {
13008 fz_archive *arch = (fz_archive *) $self;
13009 fz_archive *sub = NULL;
13010 fz_try(gctx) {
13011 if (type==1) {
13012 sub = fz_open_zip_archive(gctx, filepath);
13013 } else {
13014 sub = fz_open_tar_archive(gctx, filepath);
13015 }
13016 fz_mount_multi_archive(gctx, arch, sub, path);
13017 }
13018 fz_always(gctx) {
13019 fz_drop_archive(gctx, sub);
13020 }
13021 fz_catch(gctx) {
13022 return NULL;
13023 }
13024 Py_RETURN_NONE;
13025 }
13026
13027 //----------------------------------
13028 // add ZIP/TAR from memory
13029 //----------------------------------
13030 FITZEXCEPTION(_add_ziptarmemory, !result)
13031 PyObject *_add_ziptarmemory(PyObject *memory, int type, const char *path=NULL)
13032 {
13033 fz_archive *arch = (fz_archive *) $self;
13034 fz_archive *sub = NULL;
13035 fz_stream *stream = NULL;
13036 fz_buffer *buff = NULL;
13037 fz_try(gctx) {
13038 buff = JM_BufferFromBytes(gctx, memory);
13039 stream = fz_open_buffer(gctx, buff);
13040 if (type==1) {
13041 sub = fz_open_zip_archive_with_stream(gctx, stream);
13042 } else {
13043 sub = fz_open_tar_archive_with_stream(gctx, stream);
13044 }
13045 fz_mount_multi_archive(gctx, arch, sub, path);
13046 }
13047 fz_always(gctx) {
13048 fz_drop_stream(gctx, stream);
13049 fz_drop_buffer(gctx, buff);
13050 fz_drop_archive(gctx, sub);
13051 }
13052 fz_catch(gctx) {
13053 return NULL;
13054 }
13055 Py_RETURN_NONE;
13056 }
13057
13058 //----------------------------------
13059 // add "tree" item
13060 //----------------------------------
13061 FITZEXCEPTION(_add_treeitem, !result)
13062 PyObject *_add_treeitem(PyObject *memory, const char *name, const char *path=NULL)
13063 {
13064 fz_archive *arch = (fz_archive *) $self;
13065 fz_archive *sub = NULL;
13066 fz_buffer *buff = NULL;
13067 int drop_sub = 0;
13068 fz_try(gctx) {
13069 buff = JM_BufferFromBytes(gctx, memory);
13070 sub = JM_last_tree(gctx, arch, path);
13071 if (!sub) {
13072 sub = fz_new_tree_archive(gctx, NULL);
13073 drop_sub = 1;
13074 }
13075 fz_tree_archive_add_buffer(gctx, sub, name, buff);
13076 if (drop_sub) {
13077 fz_mount_multi_archive(gctx, arch, sub, path);
13078 }
13079 }
13080 fz_always(gctx) {
13081 fz_drop_buffer(gctx, buff);
13082 if (drop_sub) {
13083 fz_drop_archive(gctx, sub);
13084 }
13085 }
13086 fz_catch(gctx) {
13087 return NULL;
13088 }
13089 Py_RETURN_NONE;
13090 }
13091
13092 %pythoncode %{
13093 def add(self, content, path=None):
13094 """Add a sub-archive.
13095
13096 Args:
13097 content: content to be added. May be one of Archive, folder
13098 name, file name, raw bytes (bytes, bytearray), zipfile,
13099 tarfile, or a sequence of any of these types.
13100 path: (str) a "virtual" path name, under which the elements
13101 of content can be retrieved. Use it to e.g. cope with
13102 duplicate element names.
13103 """
13104 bin_ok = lambda x: isinstance(x, (bytes, bytearray, io.BytesIO))
13105
13106 entries = []
13107 mount = None
13108 fmt = None
13109
13110 def make_subarch():
13111 subarch = {"fmt": fmt, "entries": entries, "path": mount}
13112 if fmt != "tree" or self._subarchives == []:
13113 self._subarchives.append(subarch)
13114 else:
13115 ltree = self._subarchives[-1]
13116 if ltree["fmt"] != "tree" or ltree["path"] != subarch["path"]:
13117 self._subarchives.append(subarch)
13118 else:
13119 ltree["entries"].extend(subarch["entries"])
13120 self._subarchives[-1] = ltree
13121 return
13122
13123 if isinstance(content, zipfile.ZipFile):
13124 fmt = "zip"
13125 entries = content.namelist()
13126 mount = path
13127 filename = getattr(content, "filename", None)
13128 fp = getattr(content, "fp", None)
13129 if filename:
13130 self._add_ziptarfile(filename, 1, path)
13131 else:
13132 self._add_ziptarmemory(fp.getvalue(), 1, path)
13133 return make_subarch()
13134
13135 if isinstance(content, tarfile.TarFile):
13136 fmt = "tar"
13137 entries = content.getnames()
13138 mount = path
13139 filename = getattr(content.fileobj, "name", None)
13140 fp = content.fileobj
13141 if not isinstance(fp, io.BytesIO) and not filename:
13142 fp = fp.fileobj
13143 if filename:
13144 self._add_ziptarfile(filename, 0, path)
13145 else:
13146 self._add_ziptarmemory(fp.getvalue(), 0, path)
13147 return make_subarch()
13148
13149 if isinstance(content, Archive):
13150 fmt = "multi"
13151 mount = path
13152 self._add_arch(content, path)
13153 return make_subarch()
13154
13155 if bin_ok(content):
13156 if not (path and type(path) is str):
13157 raise ValueError("need name for binary content")
13158 fmt = "tree"
13159 mount = None
13160 entries = [path]
13161 self._add_treeitem(content, path)
13162 return make_subarch()
13163
13164 if hasattr(content, "name"):
13165 content = content.name
13166 elif isinstance(content, pathlib.Path):
13167 content = str(content)
13168
13169 if os.path.isdir(str(content)):
13170 a0 = str(content)
13171 fmt = "dir"
13172 mount = path
13173 entries = os.listdir(a0)
13174 self._add_dir(a0, path)
13175 return make_subarch()
13176
13177 if os.path.isfile(str(content)):
13178 if not (path and type(path) is str):
13179 raise ValueError("need name for binary content")
13180 a0 = str(content)
13181 _ = open(a0, "rb")
13182 ff = _.read()
13183 _.close()
13184 fmt = "tree"
13185 mount = None
13186 entries = [path]
13187 self._add_treeitem(ff, path)
13188 return make_subarch()
13189
13190 if type(content) is str or not getattr(content, "__getitem__", None):
13191 raise ValueError("bad archive content")
13192
13193 #----------------------------------------
13194 # handling sequence types here
13195 #----------------------------------------
13196
13197 if len(content) == 2: # covers the tree item plus path
13198 data, name = content
13199 if bin_ok(data) or os.path.isfile(str(data)):
13200 if not type(name) is str:
13201 raise ValueError(f"bad item name {name}")
13202 mount = path
13203 fmt = "tree"
13204 if bin_ok(data):
13205 self._add_treeitem(data, name, path=mount)
13206 else:
13207 _ = open(str(data), "rb")
13208 ff = _.read()
13209 _.close()
13210 seld._add_treeitem(ff, name, path=mount)
13211 entries = [name]
13212 return make_subarch()
13213
13214 # deal with sequence of disparate items
13215 for item in content:
13216 self.add(item, path)
13217
13218 __doc__ = """Archive(dirname [, path]) - from folder
13219 Archive(file [, path]) - from file name or object
13220 Archive(data, name) - from memory item
13221 Archive() - empty archive
13222 Archive(archive [, path]) - from archive
13223 """
13224
13225 @property
13226 def entry_list(self):
13227 """List of sub archives."""
13228 return self._subarchives
13229
13230 def __repr__(self):
13231 return f"Archive, sub-archives: {len(self._subarchives)}"
13232
13233 def __del__(self):
13234 if not type(self) is Archive:
13235 return
13236 if getattr(self, "thisown", False):
13237 self.__swig_destroy__(self)
13238 %}
13239 }
13240 };
13241 //------------------------------------------------------------------------
13242 // Xml
13243 //------------------------------------------------------------------------
13244 struct Xml
13245 {
13246 %extend
13247 {
13248 ~Xml()
13249 {
13250 DEBUGMSG1("Xml");
13251 fz_drop_xml( gctx, (fz_xml*) $self);
13252 DEBUGMSG2;
13253 }
13254
13255 FITZEXCEPTION(Xml, !result)
13256 Xml(fz_xml* xml)
13257 {
13258 fz_keep_xml( gctx, xml);
13259 return (struct Xml*) xml;
13260 }
13261
13262 Xml(const char *html)
13263 {
13264 fz_buffer *buff = NULL;
13265 fz_xml *ret = NULL;
13266 fz_try(gctx) {
13267 buff = fz_new_buffer_from_copied_data(gctx, html, strlen(html)+1);
13268 ret = fz_parse_xml_from_html5(gctx, buff);
13269 }
13270 fz_always(gctx) {
13271 fz_drop_buffer(gctx, buff);
13272 }
13273 fz_catch(gctx) {
13274 return NULL;
13275 }
13276 fz_keep_xml(gctx, ret);
13277 return (struct Xml*) ret;
13278 }
13279
13280 %pythoncode %{@property%}
13281 FITZEXCEPTION (root, !result)
13282 struct Xml* root()
13283 {
13284 fz_xml* ret = NULL;
13285 fz_try(gctx) {
13286 ret = fz_xml_root((fz_xml_doc *) $self);
13287 }
13288 fz_catch(gctx) {
13289 return NULL;
13290 }
13291 return (struct Xml*) ret;
13292 }
13293
13294 FITZEXCEPTION (bodytag, !result)
13295 struct Xml* bodytag()
13296 {
13297 fz_xml* ret = NULL;
13298 fz_try(gctx) {
13299 ret = fz_keep_xml( gctx, fz_dom_body( gctx, (fz_xml *) $self));
13300 }
13301 fz_catch(gctx) {
13302 return NULL;
13303 }
13304 return (struct Xml*) ret;
13305 }
13306
13307 FITZEXCEPTION (append_child, !result)
13308 PyObject *append_child( struct Xml* child)
13309 {
13310 fz_try(gctx) {
13311 fz_dom_append_child( gctx, (fz_xml *) $self, (fz_xml *) child);
13312 }
13313 fz_catch(gctx) {
13314 return NULL;
13315 }
13316 Py_RETURN_NONE;
13317 }
13318
13319 FITZEXCEPTION (create_text_node, !result)
13320 struct Xml* create_text_node( const char *text)
13321 {
13322 fz_xml* ret = NULL;
13323 fz_try(gctx) {
13324 ret = fz_dom_create_text_node( gctx,(fz_xml *) $self, text);
13325 }
13326 fz_catch(gctx) {
13327 return NULL;
13328 }
13329 fz_keep_xml( gctx, ret);
13330 return (struct Xml*) ret;
13331 }
13332
13333 FITZEXCEPTION (create_element, !result)
13334 struct Xml* create_element( const char *tag)
13335 {
13336 fz_xml* ret = NULL;
13337 fz_try(gctx) {
13338 ret = fz_dom_create_element( gctx, (fz_xml *)$self, tag);
13339 }
13340 fz_catch(gctx) {
13341 return NULL;
13342 }
13343 fz_keep_xml( gctx, ret);
13344 return (struct Xml*) ret;
13345 }
13346
13347 struct Xml *find(const char *tag, const char *att, const char *match)
13348 {
13349 fz_xml* ret=NULL;
13350 ret = fz_dom_find( gctx, (fz_xml *)$self, tag, att, match);
13351 if (!ret) {
13352 return NULL;
13353 }
13354 fz_keep_xml( gctx, ret);
13355 return (struct Xml*) ret;
13356 }
13357
13358 struct Xml *find_next( const char *tag, const char *att, const char *match)
13359 {
13360 fz_xml* ret=NULL;
13361 ret = fz_dom_find_next( gctx, (fz_xml *)$self, tag, att, match);
13362 if (!ret) {
13363 return NULL;
13364 }
13365 fz_keep_xml( gctx, ret);
13366 return (struct Xml*) ret;
13367 }
13368
13369 %pythoncode %{@property%}
13370 struct Xml *next()
13371 {
13372 fz_xml* ret=NULL;
13373 ret = fz_dom_next( gctx, (fz_xml *)$self);
13374 if (!ret) {
13375 return NULL;
13376 }
13377 fz_keep_xml( gctx, ret);
13378 return (struct Xml*) ret;
13379 }
13380
13381 %pythoncode %{@property%}
13382 struct Xml *previous()
13383 {
13384 fz_xml* ret=NULL;
13385 ret = fz_dom_previous( gctx, (fz_xml *)$self);
13386 if (!ret) {
13387 return NULL;
13388 }
13389 fz_keep_xml( gctx, ret);
13390 return (struct Xml*) ret;
13391 }
13392
13393 FITZEXCEPTION (set_attribute, !result)
13394 PyObject *set_attribute(const char *key, const char *value)
13395 {
13396 fz_try(gctx) {
13397 if (strlen(key)==0) {
13398 RAISEPY(gctx, "key must not be empty", PyExc_ValueError);
13399 }
13400 fz_dom_add_attribute(gctx, (fz_xml *)$self, key, value);
13401 }
13402 fz_catch(gctx) {
13403 return NULL;
13404 }
13405 Py_RETURN_NONE;
13406 }
13407
13408 FITZEXCEPTION (remove_attribute, !result)
13409 PyObject *remove_attribute(const char *key)
13410 {
13411 fz_try(gctx) {
13412 if (strlen(key)==0) {
13413 RAISEPY(gctx, "key must not be empty", PyExc_ValueError);
13414 }
13415 fz_xml *elt = (fz_xml *)$self;
13416 fz_dom_remove_attribute(gctx, elt, key);
13417 }
13418 fz_catch(gctx) {
13419 return NULL;
13420 }
13421 Py_RETURN_NONE;
13422 }
13423
13424
13425 FITZEXCEPTION (get_attribute_value, !result)
13426 PyObject *get_attribute_value(const char *key)
13427 {
13428 const char *ret=NULL;
13429 fz_try(gctx) {
13430 if (strlen(key)==0) {
13431 RAISEPY(gctx, "key must not be empty", PyExc_ValueError);
13432 }
13433 fz_xml *elt = (fz_xml *)$self;
13434 ret=fz_dom_attribute(gctx, elt, key);
13435 }
13436 fz_catch(gctx) {
13437 return NULL;
13438 }
13439 return Py_BuildValue("s", ret);
13440 }
13441
13442
13443 FITZEXCEPTION (get_attributes, !result)
13444 PyObject *get_attributes()
13445 {
13446 fz_xml *this = (fz_xml *) $self;
13447 if (fz_xml_text(this)) { // text node has none
13448 Py_RETURN_NONE;
13449 }
13450 PyObject *result=PyDict_New();
13451 fz_try(gctx) {
13452 int i=0;
13453 const char *key=NULL;
13454 const char *val=NULL;
13455 while (1) {
13456 val = fz_dom_get_attribute(gctx, this, i, &key);
13457 if (!val || !key) {
13458 break;
13459 }
13460 PyObject *temp = Py_BuildValue("s",val);
13461 PyDict_SetItemString(result, key, temp);
13462 Py_DECREF(temp);
13463 i += 1;
13464 }
13465 }
13466 fz_catch(gctx) {
13467 Py_DECREF(result);
13468 return NULL;
13469 }
13470 return result;
13471 }
13472
13473
13474 FITZEXCEPTION (insert_before, !result)
13475 PyObject *insert_before(struct Xml *node)
13476 {
13477 fz_xml *existing = (fz_xml *) $self;
13478 fz_xml *what = (fz_xml *) node;
13479 fz_try(gctx)
13480 {
13481 fz_dom_insert_before(gctx, existing, what);
13482 }
13483 fz_catch(gctx) {
13484 return NULL;
13485 }
13486 Py_RETURN_NONE;
13487 }
13488
13489 FITZEXCEPTION (insert_after, !result)
13490 PyObject *insert_after(struct Xml *node)
13491 {
13492 fz_xml *existing = (fz_xml *) $self;
13493 fz_xml *what = (fz_xml *) node;
13494 fz_try(gctx)
13495 {
13496 fz_dom_insert_after(gctx, existing, what);
13497 }
13498 fz_catch(gctx) {
13499 return NULL;
13500 }
13501 Py_RETURN_NONE;
13502 }
13503
13504 FITZEXCEPTION (clone, !result)
13505 struct Xml* clone()
13506 {
13507 fz_xml* ret = NULL;
13508 fz_try(gctx) {
13509 ret = fz_dom_clone( gctx, (fz_xml *)$self);
13510 }
13511 fz_catch(gctx) {
13512 return NULL;
13513 }
13514 fz_keep_xml( gctx, ret);
13515 return (struct Xml*) ret;
13516 }
13517
13518 %pythoncode %{@property%}
13519 struct Xml *parent()
13520 {
13521 fz_xml* ret = NULL;
13522 ret = fz_dom_parent( gctx, (fz_xml *)$self);
13523 if (!ret) {
13524 return NULL;
13525 }
13526 fz_keep_xml( gctx, ret);
13527 return (struct Xml*) ret;
13528 }
13529
13530 %pythoncode %{@property%}
13531 struct Xml *first_child()
13532 {
13533 fz_xml* ret = NULL;
13534 fz_xml *this = (fz_xml *)$self;
13535 if (fz_xml_text(this)) { // a text node has no child
13536 return NULL;
13537 }
13538 ret = fz_dom_first_child( gctx, (fz_xml *)$self);
13539 if (!ret) {
13540 return NULL;
13541 }
13542 fz_keep_xml( gctx, ret);
13543 return (struct Xml*) ret;
13544 }
13545
13546
13547 FITZEXCEPTION (remove, !result)
13548 PyObject *remove()
13549 {
13550 fz_try(gctx) {
13551 fz_dom_remove( gctx, (fz_xml *)$self);
13552 }
13553 fz_catch(gctx) {
13554 return NULL;
13555 }
13556 Py_RETURN_NONE;
13557 }
13558
13559 %pythoncode %{@property%}
13560 PyObject *text()
13561 {
13562 return Py_BuildValue("s", fz_xml_text((fz_xml *)$self));
13563 }
13564
13565 %pythoncode %{@property%}
13566 PyObject *tagname()
13567 {
13568 return Py_BuildValue("s", fz_xml_tag((fz_xml *)$self));
13569 }
13570
13571
13572 %pythoncode %{
13573 def _get_node_tree(self):
13574 def show_node(node, items, shift):
13575 while node != None:
13576 if node.is_text:
13577 items.append((shift, f'"{node.text}"'))
13578 node = node.next
13579 continue
13580 items.append((shift, f"({node.tagname}"))
13581 for k, v in node.get_attributes().items():
13582 items.append((shift, f"={k} '{v}'"))
13583 child = node.first_child
13584 if child:
13585 items = show_node(child, items, shift + 1)
13586 items.append((shift, f"){node.tagname}"))
13587 node = node.next
13588 return items
13589
13590 shift = 0
13591 items = []
13592 items = show_node(self, items, shift)
13593 return items
13594
13595 def debug(self):
13596 """Print a list of the node tree below self."""
13597 items = self._get_node_tree()
13598 for item in items:
13599 print(" " * item[0] + item[1].replace("\n", "\\n"))
13600
13601 @property
13602 def is_text(self):
13603 """Check if this is a text node."""
13604 return self.text != None
13605
13606 @property
13607 def last_child(self):
13608 """Return last child node."""
13609 child = self.first_child
13610 if child==None:
13611 return None
13612 while True:
13613 if child.next == None:
13614 return child
13615 child = child.next
13616
13617 @staticmethod
13618 def color_text(color):
13619 if type(color) is str:
13620 return color
13621 if type(color) is int:
13622 return f"rgb({sRGB_to_rgb(color)})"
13623 if type(color) in (tuple, list):
13624 return f"rgb{tuple(color)}"
13625 return color
13626
13627 def add_number_list(self, start=1, numtype=None):
13628 """Add numbered list ("ol" tag)"""
13629 child = self.create_element("ol")
13630 if start > 1:
13631 child.set_attribute("start", str(start))
13632 if numtype != None:
13633 child.set_attribute("type", numtype)
13634 self.append_child(child)
13635 return child
13636
13637 def add_description_list(self):
13638 """Add description list ("dl" tag)"""
13639 child = self.create_element("dl")
13640 self.append_child(child)
13641 return child
13642
13643 def add_image(self, name, width=None, height=None, imgfloat=None, align=None):
13644 """Add image node (tag "img")."""
13645 child = self.create_element("img")
13646 if width != None:
13647 child.set_attribute("width", f"{width}")
13648 if height != None:
13649 child.set_attribute("height", f"{height}")
13650 if imgfloat != None:
13651 child.set_attribute("style", f"float: {imgfloat}")
13652 if align != None:
13653 child.set_attribute("align", f"{align}")
13654 child.set_attribute("src", f"{name}")
13655 self.append_child(child)
13656 return child
13657
13658 def add_bullet_list(self):
13659 """Add bulleted list ("ul" tag)"""
13660 child = self.create_element("ul")
13661 self.append_child(child)
13662 return child
13663
13664 def add_list_item(self):
13665 """Add item ("li" tag) under a (numbered or bulleted) list."""
13666 if self.tagname not in ("ol", "ul"):
13667 raise ValueError("cannot add list item to", self.tagname)
13668 child = self.create_element("li")
13669 self.append_child(child)
13670 return child
13671
13672 def add_span(self):
13673 child = self.create_element("span")
13674 self.append_child(child)
13675 return child
13676
13677 def add_paragraph(self):
13678 """Add "p" tag"""
13679 child = self.create_element("p")
13680 if self.tagname != "p":
13681 self.append_child(child)
13682 else:
13683 self.parent.append_child(child)
13684 return child
13685
13686 def add_header(self, level=1):
13687 """Add header tag"""
13688 if level not in range(1, 7):
13689 raise ValueError("Header level must be in [1, 6]")
13690 this_tag = self.tagname
13691 new_tag = f"h{level}"
13692 child = self.create_element(new_tag)
13693 prev = self
13694 if this_tag not in ("h1", "h2", "h3", "h4", "h5", "h6", "p"):
13695 self.append_child(child)
13696 return child
13697 self.parent.append_child(child)
13698 return child
13699
13700 def add_division(self):
13701 """Add "div" tag"""
13702 child = self.create_element("div")
13703 self.append_child(child)
13704 return child
13705
13706 def add_horizontal_line(self):
13707 """Add horizontal line ("hr" tag)"""
13708 child = self.create_element("hr")
13709 self.append_child(child)
13710 return child
13711
13712 def add_link(self, href, text=None):
13713 """Add a hyperlink ("a" tag)"""
13714 child = self.create_element("a")
13715 if not isinstance(text, str):
13716 text = href
13717 child.set_attribute("href", href)
13718 child.append_child(self.create_text_node(text))
13719 prev = self.span_bottom()
13720 if prev == None:
13721 prev = self
13722 prev.append_child(child)
13723 return self
13724
13725 def add_code(self, text=None):
13726 """Add a "code" tag"""
13727 child = self.create_element("code")
13728 if type(text) is str:
13729 child.append_child(self.create_text_node(text))
13730 prev = self.span_bottom()
13731 if prev == None:
13732 prev = self
13733 prev.append_child(child)
13734 return self
13735
13736 add_var = add_code
13737 add_samp = add_code
13738 add_kbd = add_code
13739
13740 def add_superscript(self, text=None):
13741 """Add a superscript ("sup" tag)"""
13742 child = self.create_element("sup")
13743 if type(text) is str:
13744 child.append_child(self.create_text_node(text))
13745 prev = self.span_bottom()
13746 if prev == None:
13747 prev = self
13748 prev.append_child(child)
13749 return self
13750
13751 def add_subscript(self, text=None):
13752 """Add a subscript ("sub" tag)"""
13753 child = self.create_element("sub")
13754 if type(text) is str:
13755 child.append_child(self.create_text_node(text))
13756 prev = self.span_bottom()
13757 if prev == None:
13758 prev = self
13759 prev.append_child(child)
13760 return self
13761
13762 def add_codeblock(self):
13763 """Add monospaced lines ("pre" node)"""
13764 child = self.create_element("pre")
13765 self.append_child(child)
13766 return child
13767
13768 def span_bottom(self):
13769 """Find deepest level in stacked spans."""
13770 parent = self
13771 child = self.last_child
13772 if child == None:
13773 return None
13774 while child.is_text:
13775 child = child.previous
13776 if child == None:
13777 break
13778 if child == None or child.tagname != "span":
13779 return None
13780
13781 while True:
13782 if child == None:
13783 return parent
13784 if child.tagname in ("a", "sub","sup","body") or child.is_text:
13785 child = child.next
13786 continue
13787 if child.tagname == "span":
13788 parent = child
13789 child = child.first_child
13790 else:
13791 return parent
13792
13793 def append_styled_span(self, style):
13794 span = self.create_element("span")
13795 span.add_style(style)
13796 prev = self.span_bottom()
13797 if prev == None:
13798 prev = self
13799 prev.append_child(span)
13800 return prev
13801
13802 def set_margins(self, val):
13803 """Set margin values via CSS style"""
13804 text = "margins: %s" % val
13805 self.append_styled_span(text)
13806 return self
13807
13808 def set_font(self, font):
13809 """Set font-family name via CSS style"""
13810 text = "font-family: %s" % font
13811 self.append_styled_span(text)
13812 return self
13813
13814 def set_color(self, color):
13815 """Set text color via CSS style"""
13816 text = f"color: %s" % self.color_text(color)
13817 self.append_styled_span(text)
13818 return self
13819
13820 def set_columns(self, cols):
13821 """Set number of text columns via CSS style"""
13822 text = f"columns: {cols}"
13823 self.append_styled_span(text)
13824 return self
13825
13826 def set_bgcolor(self, color):
13827 """Set background color via CSS style"""
13828 text = f"background-color: %s" % self.color_text(color)
13829 self.add_style(text) # does not work on span level
13830 return self
13831
13832 def set_opacity(self, opacity):
13833 """Set opacity via CSS style"""
13834 text = f"opacity: {opacity}"
13835 self.append_styled_span(text)
13836 return self
13837
13838 def set_align(self, align):
13839 """Set text alignment via CSS style"""
13840 text = "text-align: %s"
13841 if isinstance( align, str):
13842 t = align
13843 elif align == TEXT_ALIGN_LEFT:
13844 t = "left"
13845 elif align == TEXT_ALIGN_CENTER:
13846 t = "center"
13847 elif align == TEXT_ALIGN_RIGHT:
13848 t = "right"
13849 elif align == TEXT_ALIGN_JUSTIFY:
13850 t = "justify"
13851 else:
13852 raise ValueError(f"Unrecognised align={align}")
13853 text = text % t
13854 self.add_style(text)
13855 return self
13856
13857 def set_underline(self, val="underline"):
13858 text = "text-decoration: %s" % val
13859 self.append_styled_span(text)
13860 return self
13861
13862 def set_pagebreak_before(self):
13863 """Insert a page break before this node."""
13864 text = "page-break-before: always"
13865 self.add_style(text)
13866 return self
13867
13868 def set_pagebreak_after(self):
13869 """Insert a page break after this node."""
13870 text = "page-break-after: always"
13871 self.add_style(text)
13872 return self
13873
13874 def set_fontsize(self, fontsize):
13875 """Set font size name via CSS style"""
13876 if type(fontsize) is str:
13877 px=""
13878 else:
13879 px="px"
13880 text = f"font-size: {fontsize}{px}"
13881 self.append_styled_span(text)
13882 return self
13883
13884 def set_lineheight(self, lineheight):
13885 """Set line height name via CSS style - block-level only."""
13886 text = f"line-height: {lineheight}"
13887 self.add_style(text)
13888 return self
13889
13890 def set_leading(self, leading):
13891 """Set inter-line spacing value via CSS style - block-level only."""
13892 text = f"-mupdf-leading: {leading}"
13893 self.add_style(text)
13894 return self
13895
13896 def set_word_spacing(self, spacing):
13897 """Set inter-word spacing value via CSS style"""
13898 text = f"word-spacing: {spacing}"
13899 self.append_styled_span(text)
13900 return self
13901
13902 def set_letter_spacing(self, spacing):
13903 """Set inter-letter spacing value via CSS style"""
13904 text = f"letter-spacing: {spacing}"
13905 self.append_styled_span(text)
13906 return self
13907
13908 def set_text_indent(self, indent):
13909 """Set text indentation name via CSS style - block-level only."""
13910 text = f"text-indent: {indent}"
13911 self.add_style(text)
13912 return self
13913
13914 def set_bold(self, val=True):
13915 """Set bold on / off via CSS style"""
13916 if val:
13917 val="bold"
13918 else:
13919 val="normal"
13920 text = "font-weight: %s" % val
13921 self.append_styled_span(text)
13922 return self
13923
13924 def set_italic(self, val=True):
13925 """Set italic on / off via CSS style"""
13926 if val:
13927 val="italic"
13928 else:
13929 val="normal"
13930 text = "font-style: %s" % val
13931 self.append_styled_span(text)
13932 return self
13933
13934 def set_properties(
13935 self,
13936 align=None,
13937 bgcolor=None,
13938 bold=None,
13939 color=None,
13940 columns=None,
13941 font=None,
13942 fontsize=None,
13943 indent=None,
13944 italic=None,
13945 leading=None,
13946 letter_spacing=None,
13947 lineheight=None,
13948 margins=None,
13949 pagebreak_after=None,
13950 pagebreak_before=None,
13951 word_spacing=None,
13952 unqid=None,
13953 cls=None,
13954 ):
13955 """Set any or all properties of a node.
13956
13957 To be used for existing nodes preferrably.
13958 """
13959 root = self.root
13960 temp = root.add_division()
13961 if align is not None:
13962 temp.set_align(align)
13963 if bgcolor is not None:
13964 temp.set_bgcolor(bgcolor)
13965 if bold is not None:
13966 temp.set_bold(bold)
13967 if color is not None:
13968 temp.set_color(color)
13969 if columns is not None:
13970 temp.set_columns(columns)
13971 if font is not None:
13972 temp.set_font(font)
13973 if fontsize is not None:
13974 temp.set_fontsize(fontsize)
13975 if indent is not None:
13976 temp.set_text_indent(indent)
13977 if italic is not None:
13978 temp.set_italic(italic)
13979 if leading is not None:
13980 temp.set_leading(leading)
13981 if letter_spacing is not None:
13982 temp.set_letter_spacing(letter_spacing)
13983 if lineheight is not None:
13984 temp.set_lineheight(lineheight)
13985 if margins is not None:
13986 temp.set_margins(margins)
13987 if pagebreak_after is not None:
13988 temp.set_pagebreak_after()
13989 if pagebreak_before is not None:
13990 temp.set_pagebreak_before()
13991 if word_spacing is not None:
13992 temp.set_word_spacing(word_spacing)
13993 if unqid is not None:
13994 self.set_id(unqid)
13995 if cls is not None:
13996 self.add_class(cls)
13997
13998 styles = []
13999 top_style = temp.get_attribute_value("style")
14000 if top_style is not None:
14001 styles.append(top_style)
14002 child = temp.first_child
14003 while child:
14004 styles.append(child.get_attribute_value("style"))
14005 child = child.first_child
14006 self.set_attribute("style", ";".join(styles))
14007 temp.remove()
14008 return self
14009
14010 def set_id(self, unique):
14011 """Set a unique id."""
14012 # check uniqueness
14013 tagname = self.tagname
14014 root = self.root
14015 if root.find(None, "id", unique):
14016 raise ValueError(f"id '{unique}' already exists")
14017 self.set_attribute("id", unique)
14018 return self
14019
14020 def add_text(self, text):
14021 """Add text. Line breaks are honored."""
14022 lines = text.splitlines()
14023 line_count = len(lines)
14024 prev = self.span_bottom()
14025 if prev == None:
14026 prev = self
14027
14028 for i, line in enumerate(lines):
14029 prev.append_child(self.create_text_node(line))
14030 if i < line_count - 1:
14031 prev.append_child(self.create_element("br"))
14032 return self
14033
14034 def add_style(self, text):
14035 """Set some style via CSS style. Replaces complete style spec."""
14036 style = self.get_attribute_value("style")
14037 if style != None and text in style:
14038 return self
14039 self.remove_attribute("style")
14040 if style == None:
14041 style = text
14042 else:
14043 style += ";" + text
14044 self.set_attribute("style", style)
14045 return self
14046
14047 def add_class(self, text):
14048 """Set some class via CSS. Replaces complete class spec."""
14049 cls = self.get_attribute_value("class")
14050 if cls != None and text in cls:
14051 return self
14052 self.remove_attribute("class")
14053 if cls == None:
14054 cls = text
14055 else:
14056 cls += " " + text
14057 self.set_attribute("class", cls)
14058 return self
14059
14060 def insert_text(self, text):
14061 lines = text.splitlines()
14062 line_count = len(lines)
14063 for i, line in enumerate(lines):
14064 self.append_child(self.create_text_node(line))
14065 if i < line_count - 1:
14066 self.append_child(self.create_element("br"))
14067 return self
14068
14069 def __enter__(self):
14070 return self
14071
14072 def __exit__(self, *args):
14073 pass
14074
14075 def __del__(self):
14076 if not type(self) is Xml:
14077 return
14078 if getattr(self, "thisown", False):
14079 self.__swig_destroy__(self)
14080 %}
14081 }
14082 };
14083
14084 //------------------------------------------------------------------------
14085 // Story
14086 //------------------------------------------------------------------------
14087 struct Story
14088 {
14089 %extend
14090 {
14091 ~Story()
14092 {
14093 DEBUGMSG1("Story");
14094 fz_story *this_story = (fz_story *) $self;
14095 fz_drop_story(gctx, this_story);
14096 DEBUGMSG2;
14097 }
14098
14099 FITZEXCEPTION(Story, !result)
14100 %pythonprepend Story %{
14101 if archive != None and isinstance(archive, Archive) == False:
14102 archive = Archive(archive)
14103 %}
14104 Story(const char* html=NULL, const char *user_css=NULL, double em=12, struct Archive *archive=NULL)
14105 {
14106 fz_story* story = NULL;
14107 fz_buffer *buffer = NULL;
14108 fz_archive* arch = NULL;
14109 fz_var(story);
14110 fz_var(buffer);
14111 const char *html2="";
14112 if (html) {
14113 html2=html;
14114 }
14115
14116 fz_try(gctx)
14117 {
14118 buffer = fz_new_buffer_from_copied_data(gctx, html2, strlen(html2)+1);
14119 if (archive) {
14120 arch = (fz_archive *) archive;
14121 }
14122 story = fz_new_story(gctx, buffer, user_css, em, arch);
14123 }
14124 fz_always(gctx)
14125 {
14126 fz_drop_buffer(gctx, buffer);
14127 }
14128 fz_catch(gctx)
14129 {
14130 return NULL;
14131 }
14132 struct Story* ret = (struct Story *) story;
14133 return ret;
14134 }
14135
14136 FITZEXCEPTION(reset, !result)
14137 PyObject* reset()
14138 {
14139 fz_try(gctx)
14140 {
14141 fz_reset_story(gctx, (fz_story *)$self);
14142 }
14143 fz_catch(gctx)
14144 {
14145 return NULL;
14146 }
14147 Py_RETURN_NONE;
14148 }
14149
14150 FITZEXCEPTION(place, !result)
14151 PyObject* place( PyObject* where)
14152 {
14153 PyObject* ret = NULL;
14154 fz_try(gctx)
14155 {
14156 fz_rect where2 = JM_rect_from_py(where);
14157 fz_rect filled;
14158 int more = fz_place_story( gctx, (fz_story*) $self, where2, &filled);
14159 ret = PyTuple_New(2);
14160 PyTuple_SET_ITEM( ret, 0, Py_BuildValue( "i", more));
14161 PyTuple_SET_ITEM( ret, 1, JM_py_from_rect( filled));
14162 }
14163 fz_catch(gctx)
14164 {
14165 return NULL;
14166 }
14167 return ret;
14168 }
14169
14170 FITZEXCEPTION(draw, !result)
14171 PyObject* draw( struct DeviceWrapper* device, PyObject* matrix=NULL)
14172 {
14173 fz_try(gctx)
14174 {
14175 fz_matrix ctm2 = JM_matrix_from_py( matrix);
14176 fz_device *dev = (device) ? device->device : NULL;
14177 fz_draw_story( gctx, (fz_story*) $self, dev, ctm2);
14178 }
14179 fz_catch(gctx)
14180 {
14181 return NULL;
14182 }
14183 Py_RETURN_NONE;
14184 }
14185
14186 FITZEXCEPTION(document, !result)
14187 struct Xml* document()
14188 {
14189 fz_xml* dom=NULL;
14190 fz_try(gctx) {
14191 dom = fz_story_document( gctx, (fz_story*) $self);
14192 }
14193 fz_catch(gctx) {
14194 return NULL;
14195 }
14196 fz_keep_xml( gctx, dom);
14197 return (struct Xml*) dom;
14198 }
14199
14200 FITZEXCEPTION(element_positions, !result)
14201 %pythonprepend element_positions %{
14202 """Trigger a callback function to record where items have been placed.
14203
14204 Args:
14205 function: a function accepting exactly one argument.
14206 args: an optional dictionary for passing additional data.
14207 """
14208 if type(args) is dict:
14209 for k in args.keys():
14210 if not (type(k) is str and k.isidentifier()):
14211 raise ValueError(f"invalid key '{k}'")
14212 else:
14213 args = {}
14214 if not callable(function) or function.__code__.co_argcount != 1:
14215 raise ValueError("callback 'function' must be a callable with exactly one argument")
14216 %}
14217 PyObject* element_positions(PyObject *function, PyObject *args)
14218 {
14219 PyObject *callarg=NULL;
14220 fz_try(gctx) {
14221 callarg = Py_BuildValue("OO", function, args);
14222 fz_story_positions(gctx, (fz_story *) $self, Story_Callback, callarg);
14223 }
14224 fz_always(gctx) {
14225 Py_CLEAR(callarg);
14226 }
14227 fz_catch(gctx) {
14228 return NULL;
14229 }
14230 Py_RETURN_NONE;
14231 }
14232
14233 %pythoncode
14234 %{
14235 def write(self, writer, rectfn, positionfn=None, pagefn=None):
14236 dev = None
14237 page_num = 0
14238 rect_num = 0
14239 filled = Rect(0, 0, 0, 0)
14240 while 1:
14241 mediabox, rect, ctm = rectfn(rect_num, filled)
14242 rect_num += 1
14243 if mediabox:
14244 # new page.
14245 page_num += 1
14246 more, filled = self.place( rect)
14247 #print(f"write(): positionfn={positionfn}")
14248 if positionfn:
14249 def positionfn2(position):
14250 # We add a `.page_num` member to the
14251 # `ElementPosition` instance.
14252 position.page_num = page_num
14253 #print(f"write(): position={position}")
14254 positionfn(position)
14255 self.element_positions(positionfn2, {})
14256 if writer:
14257 if mediabox:
14258 # new page.
14259 if dev:
14260 if pagefn:
14261 pagefn(page_num, medibox, dev, 1)
14262 writer.end_page()
14263 dev = writer.begin_page( mediabox)
14264 if pagefn:
14265 pagefn(page_num, mediabox, dev, 0)
14266 self.draw( dev, ctm)
14267 if not more:
14268 if pagefn:
14269 pagefn( page_num, mediabox, dev, 1)
14270 writer.end_page()
14271 else:
14272 self.draw(None, ctm)
14273 if not more:
14274 break
14275
14276 @staticmethod
14277 def write_stabilized(writer, contentfn, rectfn, user_css=None, em=12, positionfn=None, pagefn=None, archive=None, add_header_ids=True):
14278 positions = list()
14279 content = None
14280 # Iterate until stable.
14281 while 1:
14282 content_prev = content
14283 content = contentfn( positions)
14284 stable = False
14285 if content == content_prev:
14286 stable = True
14287 content2 = content
14288 story = Story(content2, user_css, em, archive)
14289
14290 if add_header_ids:
14291 story.add_header_ids()
14292
14293 positions = list()
14294 def positionfn2(position):
14295 #print(f"write_stabilized(): stable={stable} positionfn={positionfn} position={position}")
14296 positions.append(position)
14297 if stable and positionfn:
14298 positionfn(position)
14299 story.write(
14300 writer if stable else None,
14301 rectfn,
14302 positionfn2,
14303 pagefn,
14304 )
14305 if stable:
14306 break
14307
14308 def add_header_ids(self):
14309 '''
14310 Look for `<h1..6>` items in `self` and adds unique `id`
14311 attributes if not already present.
14312 '''
14313 dom = self.body
14314 i = 0
14315 x = dom.find(None, None, None)
14316 while x:
14317 name = x.tagname
14318 if len(name) == 2 and name[0]=="h" and name[1] in "123456":
14319 attr = x.get_attribute_value("id")
14320 if not attr:
14321 id_ = f"h_id_{i}"
14322 #print(f"name={name}: setting id={id_}")
14323 x.set_attribute("id", id_)
14324 i += 1
14325 x = x.find_next(None, None, None)
14326
14327 def write_with_links(self, rectfn, positionfn=None, pagefn=None):
14328 #print("write_with_links()")
14329 stream = io.BytesIO()
14330 writer = DocumentWriter(stream)
14331 positions = []
14332 def positionfn2(position):
14333 #print(f"write_with_links(): position={position}")
14334 positions.append(position)
14335 if positionfn:
14336 positionfn(position)
14337 self.write(writer, rectfn, positionfn=positionfn2, pagefn=pagefn)
14338 writer.close()
14339 stream.seek(0)
14340 return Story.add_pdf_links(stream, positions)
14341
14342 @staticmethod
14343 def write_stabilized_with_links(contentfn, rectfn, user_css=None, em=12, positionfn=None, pagefn=None, archive=None, add_header_ids=True):
14344 #print("write_stabilized_with_links()")
14345 stream = io.BytesIO()
14346 writer = DocumentWriter(stream)
14347 positions = []
14348 def positionfn2(position):
14349 #print(f"write_stabilized_with_links(): position={position}")
14350 positions.append(position)
14351 if positionfn:
14352 positionfn(position)
14353 Story.write_stabilized(writer, contentfn, rectfn, user_css, em, positionfn2, pagefn, archive, add_header_ids)
14354 writer.close()
14355 stream.seek(0)
14356 return Story.add_pdf_links(stream, positions)
14357
14358 @staticmethod
14359 def add_pdf_links(document_or_stream, positions):
14360 """
14361 Adds links to PDF document.
14362 Args:
14363 document_or_stream:
14364 A PDF `Document` or raw PDF content, for example an
14365 `io.BytesIO` instance.
14366 positions:
14367 List of `ElementPosition`'s for `document_or_stream`,
14368 typically from Story.element_positions(). We raise an
14369 exception if two or more positions have same id.
14370 Returns:
14371 `document_or_stream` if a `Document` instance, otherwise a
14372 new `Document` instance.
14373 We raise an exception if an `href` in `positions` refers to an
14374 internal position `#<name>` but no item in `postions` has `id =
14375 name`.
14376 """
14377 if isinstance(document_or_stream, Document):
14378 document = document_or_stream
14379 else:
14380 document = Document("pdf", document_or_stream)
14381
14382 # Create dict from id to position, which we will use to find
14383 # link destinations.
14384 #
14385 id_to_position = dict()
14386 #print(f"positions: {positions}")
14387 for position in positions:
14388 #print(f"add_pdf_links(): position: {position}")
14389 if (position.open_close & 1) and position.id:
14390 #print(f"add_pdf_links(): position with id: {position}")
14391 if position.id in id_to_position:
14392 #print(f"Ignoring duplicate positions with id={position.id!r}")
14393 pass
14394 else:
14395 id_to_position[ position.id] = position
14396
14397 # Insert links for all positions that have an `href` starting
14398 # with '#'.
14399 #
14400 for position_from in positions:
14401 if ((position_from.open_close & 1)
14402 and position_from.href
14403 and position_from.href.startswith("#")
14404 ):
14405 # This is a `<a href="#...">...</a>` internal link.
14406 #print(f"add_pdf_links(): position with href: {position}")
14407 target_id = position_from.href[1:]
14408 try:
14409 position_to = id_to_position[ target_id]
14410 except Exception as e:
14411 raise RuntimeError(f"No destination with id={target_id}, required by position_from: {position_from}")
14412 # Make link from `position_from`'s rect to top-left of
14413 # `position_to`'s rect.
14414 if 0:
14415 print(f"add_pdf_links(): making link from:")
14416 print(f"add_pdf_links(): {position_from}")
14417 print(f"add_pdf_links(): to:")
14418 print(f"add_pdf_links(): {position_to}")
14419 link = dict()
14420 link["kind"] = LINK_GOTO
14421 link["from"] = Rect(position_from.rect)
14422 x0, y0, x1, y1 = position_to.rect
14423 # This appears to work well with viewers which scroll
14424 # to make destination point top-left of window.
14425 link["to"] = Point(x0, y0)
14426 link["page"] = position_to.page_num - 1
14427 document[position_from.page_num - 1].insert_link(link)
14428 return document
14429
14430 @property
14431 def body(self):
14432 dom = self.document()
14433 return dom.bodytag()
14434
14435 def __del__(self):
14436 if not type(self) is Story:
14437 return
14438 if getattr(self, "thisown", False):
14439 self.__swig_destroy__(self)
14440 %}
14441 }
14442 };
14443
14444
14445 //------------------------------------------------------------------------
14446 // Tools - a collection of tools and utilities
14447 //------------------------------------------------------------------------
14448 struct Tools
14449 {
14450 %extend
14451 {
14452 Tools()
14453 {
14454 /* It looks like global objects are never destructed when running
14455 with SWIG, so we use Memento_startLeaking()/Memento_stopLeaking().
14456 */
14457 Memento_startLeaking();
14458 void* p = malloc( sizeof(struct Tools));
14459 Memento_stopLeaking();
14460 //fprintf(stderr, "Tools constructor p=%p\n", p);
14461 return (struct Tools*) p;
14462 }
14463
14464 ~Tools()
14465 {
14466 /* This is not called. */
14467 struct Tools* p = (struct Tools*) $self;
14468 //fprintf(stderr, "~Tools() p=%p\n", p);
14469 free(p);
14470 }
14471
14472 %pythonprepend gen_id
14473 %{"""Return a unique positive integer."""%}
14474 PyObject *gen_id()
14475 {
14476 JM_UNIQUE_ID += 1;
14477 if (JM_UNIQUE_ID < 0) JM_UNIQUE_ID = 1;
14478 return Py_BuildValue("i", JM_UNIQUE_ID);
14479 }
14480
14481
14482 FITZEXCEPTION(set_icc, !result)
14483 %pythonprepend set_icc
14484 %{"""Set ICC color handling on or off."""%}
14485 PyObject *set_icc(int on=0)
14486 {
14487 fz_try(gctx) {
14488 if (on) {
14489 if (FZ_ENABLE_ICC)
14490 fz_enable_icc(gctx);
14491 else {
14492 RAISEPY(gctx, "MuPDF built w/o ICC support",PyExc_ValueError);
14493 }
14494 } else if (FZ_ENABLE_ICC) {
14495 fz_disable_icc(gctx);
14496 }
14497 }
14498 fz_catch(gctx) {
14499 return NULL;
14500 }
14501 Py_RETURN_NONE;
14502 }
14503
14504
14505 %pythonprepend set_annot_stem
14506 %{"""Get / set id prefix for annotations."""%}
14507 char *set_annot_stem(char *stem=NULL)
14508 {
14509 if (!stem) {
14510 return JM_annot_id_stem;
14511 }
14512 size_t len = strlen(stem) + 1;
14513 if (len > 50) len = 50;
14514 memcpy(&JM_annot_id_stem, stem, len);
14515 return JM_annot_id_stem;
14516 }
14517
14518
14519 %pythonprepend set_small_glyph_heights
14520 %{"""Set / unset small glyph heights."""%}
14521 PyObject *set_small_glyph_heights(PyObject *on=NULL)
14522 {
14523 if (!on || on == Py_None) {
14524 return JM_BOOL(small_glyph_heights);
14525 }
14526 if (PyObject_IsTrue(on)) {
14527 small_glyph_heights = 1;
14528 } else {
14529 small_glyph_heights = 0;
14530 }
14531 return JM_BOOL(small_glyph_heights);
14532 }
14533
14534
14535 %pythonprepend set_subset_fontnames
14536 %{"""Set / unset returning fontnames with their subset prefix."""%}
14537 PyObject *set_subset_fontnames(PyObject *on=NULL)
14538 {
14539 if (!on || on == Py_None) {
14540 return JM_BOOL(subset_fontnames);
14541 }
14542 if (PyObject_IsTrue(on)) {
14543 subset_fontnames = 1;
14544 } else {
14545 subset_fontnames = 0;
14546 }
14547 return JM_BOOL(subset_fontnames);
14548 }
14549
14550
14551 %pythonprepend set_low_memory
14552 %{"""Set / unset MuPDF device caching."""%}
14553 PyObject *set_low_memory(PyObject *on=NULL)
14554 {
14555 if (!on || on == Py_None) {
14556 return JM_BOOL(no_device_caching);
14557 }
14558 if (PyObject_IsTrue(on)) {
14559 no_device_caching = 1;
14560 } else {
14561 no_device_caching = 0;
14562 }
14563 return JM_BOOL(no_device_caching);
14564 }
14565
14566
14567 %pythonprepend unset_quad_corrections
14568 %{"""Set ascender / descender corrections on or off."""%}
14569 PyObject *unset_quad_corrections(PyObject *on=NULL)
14570 {
14571 if (!on || on == Py_None) {
14572 return JM_BOOL(skip_quad_corrections);
14573 }
14574 if (PyObject_IsTrue(on)) {
14575 skip_quad_corrections = 1;
14576 } else {
14577 skip_quad_corrections = 0;
14578 }
14579 return JM_BOOL(skip_quad_corrections);
14580 }
14581
14582
14583 %pythonprepend store_shrink
14584 %{"""Free 'percent' of current store size."""%}
14585 PyObject *store_shrink(int percent)
14586 {
14587 if (percent >= 100) {
14588 fz_empty_store(gctx);
14589 return Py_BuildValue("i", 0);
14590 }
14591 if (percent > 0) fz_shrink_store(gctx, 100 - percent);
14592 return Py_BuildValue("i", (int) gctx->store->size);
14593 }
14594
14595
14596 %pythoncode%{@property%}
14597 %pythonprepend store_size
14598 %{"""MuPDF current store size."""%}
14599 PyObject *store_size()
14600 {
14601 return Py_BuildValue("i", (int) gctx->store->size);
14602 }
14603
14604
14605 %pythoncode%{@property%}
14606 %pythonprepend store_maxsize
14607 %{"""MuPDF store size limit."""%}
14608 PyObject *store_maxsize()
14609 {
14610 return Py_BuildValue("i", (int) gctx->store->max);
14611 }
14612
14613
14614 %pythonprepend show_aa_level
14615 %{"""Show anti-aliasing values."""%}
14616 %pythonappend show_aa_level %{
14617 temp = {"graphics": val[0], "text": val[1], "graphics_min_line_width": val[2]}
14618 val = temp%}
14619 PyObject *show_aa_level()
14620 {
14621 return Py_BuildValue("iif",
14622 fz_graphics_aa_level(gctx),
14623 fz_text_aa_level(gctx),
14624 fz_graphics_min_line_width(gctx));
14625 }
14626
14627
14628 %pythonprepend set_aa_level
14629 %{"""Set anti-aliasing level."""%}
14630 void set_aa_level(int level)
14631 {
14632 fz_set_aa_level(gctx, level);
14633 }
14634
14635
14636 %pythonprepend set_graphics_min_line_width
14637 %{"""Set the graphics minimum line width."""%}
14638 void set_graphics_min_line_width(float min_line_width)
14639 {
14640 fz_set_graphics_min_line_width(gctx, min_line_width);
14641 }
14642
14643
14644 FITZEXCEPTION(image_profile, !result)
14645 %pythonprepend image_profile
14646 %{"""Metadata of an image binary stream."""%}
14647 PyObject *image_profile(PyObject *stream, int keep_image=0)
14648 {
14649 PyObject *rc = NULL;
14650 fz_try(gctx) {
14651 rc = JM_image_profile(gctx, stream, keep_image);
14652 }
14653 fz_catch(gctx) {
14654 return NULL;
14655 }
14656 return rc;
14657 }
14658
14659
14660 PyObject *_rotate_matrix(struct Page *page)
14661 {
14662 pdf_page *pdfpage = pdf_page_from_fz_page(gctx, (fz_page *) page);
14663 if (!pdfpage) return JM_py_from_matrix(fz_identity);
14664 return JM_py_from_matrix(JM_rotate_page_matrix(gctx, pdfpage));
14665 }
14666
14667
14668 PyObject *_derotate_matrix(struct Page *page)
14669 {
14670 pdf_page *pdfpage = pdf_page_from_fz_page(gctx, (fz_page *) page);
14671 if (!pdfpage) return JM_py_from_matrix(fz_identity);
14672 return JM_py_from_matrix(JM_derotate_page_matrix(gctx, pdfpage));
14673 }
14674
14675
14676 %pythoncode%{@property%}
14677 %pythonprepend fitz_config
14678 %{"""PyMuPDF configuration parameters."""%}
14679 PyObject *fitz_config()
14680 {
14681 return JM_fitz_config();
14682 }
14683
14684
14685 %pythonprepend glyph_cache_empty
14686 %{"""Empty the glyph cache."""%}
14687 void glyph_cache_empty()
14688 {
14689 fz_purge_glyph_cache(gctx);
14690 }
14691
14692
14693 FITZEXCEPTION(_fill_widget, !result)
14694 %pythonappend _fill_widget %{
14695 widget.rect = Rect(annot.rect)
14696 widget.xref = annot.xref
14697 widget.parent = annot.parent
14698 widget._annot = annot # backpointer to annot object
14699 if not widget.script:
14700 widget.script = None
14701 if not widget.script_stroke:
14702 widget.script_stroke = None
14703 if not widget.script_format:
14704 widget.script_format = None
14705 if not widget.script_change:
14706 widget.script_change = None
14707 if not widget.script_calc:
14708 widget.script_calc = None
14709 if not widget.script_blur:
14710 widget.script_blur = None
14711 if not widget.script_focus:
14712 widget.script_focus = None
14713 %}
14714 PyObject *_fill_widget(struct Annot *annot, PyObject *widget)
14715 {
14716 fz_try(gctx) {
14717 JM_get_widget_properties(gctx, (pdf_annot *) annot, widget);
14718 }
14719 fz_catch(gctx) {
14720 return NULL;
14721 }
14722 Py_RETURN_NONE;
14723 }
14724
14725
14726 FITZEXCEPTION(_save_widget, !result)
14727 PyObject *_save_widget(struct Annot *annot, PyObject *widget)
14728 {
14729 fz_try(gctx) {
14730 JM_set_widget_properties(gctx, (pdf_annot *) annot, widget);
14731 }
14732 fz_catch(gctx) {
14733 return NULL;
14734 }
14735 Py_RETURN_NONE;
14736 }
14737
14738
14739 FITZEXCEPTION(_reset_widget, !result)
14740 PyObject *_reset_widget(struct Annot *annot)
14741 {
14742 fz_try(gctx) {
14743 pdf_annot *this_annot = (pdf_annot *) annot;
14744 pdf_obj *this_annot_obj = pdf_annot_obj(gctx, this_annot);
14745 pdf_document *pdf = pdf_get_bound_document(gctx, this_annot_obj);
14746 pdf_field_reset(gctx, pdf, this_annot_obj);
14747 }
14748 fz_catch(gctx) {
14749 return NULL;
14750 }
14751 Py_RETURN_NONE;
14752 }
14753
14754 // Ensure that widgets with a /AA/C JavaScript are in AcroForm/CO
14755 FITZEXCEPTION(_ensure_widget_calc, !result)
14756 PyObject *_ensure_widget_calc(struct Annot *annot)
14757 {
14758 pdf_obj *PDFNAME_CO=NULL;
14759 fz_try(gctx) {
14760 pdf_obj *annot_obj = pdf_annot_obj(gctx, (pdf_annot *) annot);
14761 pdf_document *pdf = pdf_get_bound_document(gctx, annot_obj);
14762 PDFNAME_CO = pdf_new_name(gctx, "CO"); // = PDF_NAME(CO)
14763 pdf_obj *acro = pdf_dict_getl(gctx, // get AcroForm dict
14764 pdf_trailer(gctx, pdf),
14765 PDF_NAME(Root),
14766 PDF_NAME(AcroForm),
14767 NULL);
14768
14769 pdf_obj *CO = pdf_dict_get(gctx, acro, PDFNAME_CO); // = AcroForm/CO
14770 if (!CO) {
14771 CO = pdf_dict_put_array(gctx, acro, PDFNAME_CO, 2);
14772 }
14773 int i, n = pdf_array_len(gctx, CO);
14774 int xref, nxref, found = 0;
14775 xref = pdf_to_num(gctx, annot_obj);
14776 for (i = 0; i < n; i++) {
14777 nxref = pdf_to_num(gctx, pdf_array_get(gctx, CO, i));
14778 if (xref == nxref) {
14779 found = 1;
14780 break;
14781 }
14782 }
14783 if (!found) {
14784 pdf_array_push_drop(gctx, CO, pdf_new_indirect(gctx, pdf, xref, 0));
14785 }
14786 }
14787 fz_always(gctx) {
14788 pdf_drop_obj(gctx, PDFNAME_CO);
14789 }
14790 fz_catch(gctx) {
14791 return NULL;
14792 }
14793 Py_RETURN_NONE;
14794 }
14795
14796
14797 FITZEXCEPTION(_parse_da, !result)
14798 %pythonappend _parse_da %{
14799 if not val:
14800 return ((0,), "", 0)
14801 font = "Helv"
14802 fsize = 12
14803 col = (0, 0, 0)
14804 dat = val.split() # split on any whitespace
14805 for i, item in enumerate(dat):
14806 if item == "Tf":
14807 font = dat[i - 2][1:]
14808 fsize = float(dat[i - 1])
14809 dat[i] = dat[i-1] = dat[i-2] = ""
14810 continue
14811 if item == "g": # unicolor text
14812 col = [(float(dat[i - 1]))]
14813 dat[i] = dat[i-1] = ""
14814 continue
14815 if item == "rg": # RGB colored text
14816 col = [float(f) for f in dat[i - 3:i]]
14817 dat[i] = dat[i-1] = dat[i-2] = dat[i-3] = ""
14818 continue
14819 if item == "k": # CMYK colored text
14820 col = [float(f) for f in dat[i - 4:i]]
14821 dat[i] = dat[i-1] = dat[i-2] = dat[i-3] = dat[i-4] = ""
14822 continue
14823
14824 val = (col, font, fsize)
14825 %}
14826 PyObject *_parse_da(struct Annot *annot)
14827 {
14828 char *da_str = NULL;
14829 pdf_annot *this_annot = (pdf_annot *) annot;
14830 pdf_obj *this_annot_obj = pdf_annot_obj(gctx, this_annot);
14831 pdf_document *pdf = pdf_get_bound_document(gctx, this_annot_obj);
14832 fz_try(gctx) {
14833 pdf_obj *da = pdf_dict_get_inheritable(gctx, this_annot_obj,
14834 PDF_NAME(DA));
14835 if (!da) {
14836 pdf_obj *trailer = pdf_trailer(gctx, pdf);
14837 da = pdf_dict_getl(gctx, trailer, PDF_NAME(Root),
14838 PDF_NAME(AcroForm),
14839 PDF_NAME(DA),
14840 NULL);
14841 }
14842 da_str = (char *) pdf_to_text_string(gctx, da);
14843 }
14844 fz_catch(gctx) {
14845 return NULL;
14846 }
14847 return JM_UnicodeFromStr(da_str);
14848 }
14849
14850
14851 FITZEXCEPTION(_update_da, !result)
14852 PyObject *_update_da(struct Annot *annot, char *da_str)
14853 {
14854 fz_try(gctx) {
14855 pdf_annot *this_annot = (pdf_annot *) annot;
14856 pdf_obj *this_annot_obj = pdf_annot_obj(gctx, this_annot);
14857 pdf_dict_put_text_string(gctx, this_annot_obj, PDF_NAME(DA), da_str);
14858 pdf_dict_del(gctx, this_annot_obj, PDF_NAME(DS)); /* not supported */
14859 pdf_dict_del(gctx, this_annot_obj, PDF_NAME(RC)); /* not supported */
14860 }
14861 fz_catch(gctx) {
14862 return NULL;
14863 }
14864 Py_RETURN_NONE;
14865 }
14866
14867
14868 FITZEXCEPTION(_get_all_contents, !result)
14869 %pythonprepend _get_all_contents
14870 %{"""Concatenate all /Contents objects of a page into a bytes object."""%}
14871 PyObject *_get_all_contents(struct Page *fzpage)
14872 {
14873 pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) fzpage);
14874 fz_buffer *res = NULL;
14875 PyObject *result = NULL;
14876 fz_try(gctx) {
14877 ASSERT_PDF(page);
14878 res = JM_read_contents(gctx, page->obj);
14879 result = JM_BinFromBuffer(gctx, res);
14880 }
14881 fz_always(gctx) {
14882 fz_drop_buffer(gctx, res);
14883 }
14884 fz_catch(gctx) {
14885 return NULL;
14886 }
14887 return result;
14888 }
14889
14890
14891 FITZEXCEPTION(_insert_contents, !result)
14892 %pythonprepend _insert_contents
14893 %{"""Add bytes as a new /Contents object for a page, and return its xref."""%}
14894 PyObject *_insert_contents(struct Page *page, PyObject *newcont, int overlay=1)
14895 {
14896 fz_buffer *contbuf = NULL;
14897 int xref = 0;
14898 pdf_page *pdfpage = pdf_page_from_fz_page(gctx, (fz_page *) page);
14899 fz_try(gctx) {
14900 ASSERT_PDF(pdfpage);
14901 ENSURE_OPERATION(gctx, pdfpage->doc);
14902 contbuf = JM_BufferFromBytes(gctx, newcont);
14903 xref = JM_insert_contents(gctx, pdfpage->doc, pdfpage->obj, contbuf, overlay);
14904 }
14905 fz_always(gctx) {
14906 fz_drop_buffer(gctx, contbuf);
14907 }
14908 fz_catch(gctx) {
14909 return NULL;
14910 }
14911 return Py_BuildValue("i", xref);
14912 }
14913
14914 %pythonprepend mupdf_version
14915 %{"""Get version of MuPDF binary build."""%}
14916 PyObject *mupdf_version()
14917 {
14918 return Py_BuildValue("s", FZ_VERSION);
14919 }
14920
14921 %pythonprepend mupdf_warnings
14922 %{"""Get the MuPDF warnings/errors with optional reset (default)."""%}
14923 %pythonappend mupdf_warnings %{
14924 val = "\n".join(val)
14925 if reset:
14926 self.reset_mupdf_warnings()%}
14927 PyObject *mupdf_warnings(int reset=1)
14928 {
14929 Py_INCREF(JM_mupdf_warnings_store);
14930 return JM_mupdf_warnings_store;
14931 }
14932
14933 int _int_from_language(char *language)
14934 {
14935 return fz_text_language_from_string(language);
14936 }
14937
14938 %pythonprepend reset_mupdf_warnings
14939 %{"""Empty the MuPDF warnings/errors store."""%}
14940 void reset_mupdf_warnings()
14941 {
14942 Py_CLEAR(JM_mupdf_warnings_store);
14943 JM_mupdf_warnings_store = PyList_New(0);
14944 }
14945
14946 %pythonprepend mupdf_display_errors
14947 %{"""Set MuPDF error display to True or False."""%}
14948 PyObject *mupdf_display_errors(PyObject *on=NULL)
14949 {
14950 if (!on || on == Py_None) {
14951 return JM_BOOL(JM_mupdf_show_errors);
14952 }
14953 if (PyObject_IsTrue(on)) {
14954 JM_mupdf_show_errors = 1;
14955 } else {
14956 JM_mupdf_show_errors = 0;
14957 }
14958 return JM_BOOL(JM_mupdf_show_errors);
14959 }
14960
14961 %pythonprepend mupdf_display_warnings
14962 %{"""Set MuPDF warnings display to True or False."""%}
14963 PyObject *mupdf_display_warnings(PyObject *on=NULL)
14964 {
14965 if (!on || on == Py_None) {
14966 return JM_BOOL(JM_mupdf_show_warnings);
14967 }
14968 if (PyObject_IsTrue(on)) {
14969 JM_mupdf_show_warnings = 1;
14970 } else {
14971 JM_mupdf_show_warnings = 0;
14972 }
14973 return JM_BOOL(JM_mupdf_show_warnings);
14974 }
14975
14976 %pythoncode %{
14977 def _le_annot_parms(self, annot, p1, p2, fill_color):
14978 """Get common parameters for making annot line end symbols.
14979
14980 Returns:
14981 m: matrix that maps p1, p2 to points L, P on the x-axis
14982 im: its inverse
14983 L, P: transformed p1, p2
14984 w: line width
14985 scol: stroke color string
14986 fcol: fill color store_shrink
14987 opacity: opacity string (gs command)
14988 """
14989 w = annot.border["width"] # line width
14990 sc = annot.colors["stroke"] # stroke color
14991 if not sc: # black if missing
14992 sc = (0,0,0)
14993 scol = " ".join(map(str, sc)) + " RG\n"
14994 if fill_color:
14995 fc = fill_color
14996 else:
14997 fc = annot.colors["fill"] # fill color
14998 if not fc:
14999 fc = (1,1,1) # white if missing
15000 fcol = " ".join(map(str, fc)) + " rg\n"
15001 # nr = annot.rect
15002 np1 = p1 # point coord relative to annot rect
15003 np2 = p2 # point coord relative to annot rect
15004 m = Matrix(util_hor_matrix(np1, np2)) # matrix makes the line horizontal
15005 im = ~m # inverted matrix
15006 L = np1 * m # converted start (left) point
15007 R = np2 * m # converted end (right) point
15008 if 0 <= annot.opacity < 1:
15009 opacity = "/H gs\n"
15010 else:
15011 opacity = ""
15012 return m, im, L, R, w, scol, fcol, opacity
15013
15014 def _oval_string(self, p1, p2, p3, p4):
15015 """Return /AP string defining an oval within a 4-polygon provided as points
15016 """
15017 def bezier(p, q, r):
15018 f = "%f %f %f %f %f %f c\n"
15019 return f % (p.x, p.y, q.x, q.y, r.x, r.y)
15020
15021 kappa = 0.55228474983 # magic number
15022 ml = p1 + (p4 - p1) * 0.5 # middle points ...
15023 mo = p1 + (p2 - p1) * 0.5 # for each ...
15024 mr = p2 + (p3 - p2) * 0.5 # polygon ...
15025 mu = p4 + (p3 - p4) * 0.5 # side
15026 ol1 = ml + (p1 - ml) * kappa # the 8 bezier
15027 ol2 = mo + (p1 - mo) * kappa # helper points
15028 or1 = mo + (p2 - mo) * kappa
15029 or2 = mr + (p2 - mr) * kappa
15030 ur1 = mr + (p3 - mr) * kappa
15031 ur2 = mu + (p3 - mu) * kappa
15032 ul1 = mu + (p4 - mu) * kappa
15033 ul2 = ml + (p4 - ml) * kappa
15034 # now draw, starting from middle point of left side
15035 ap = "%f %f m\n" % (ml.x, ml.y)
15036 ap += bezier(ol1, ol2, mo)
15037 ap += bezier(or1, or2, mr)
15038 ap += bezier(ur1, ur2, mu)
15039 ap += bezier(ul1, ul2, ml)
15040 return ap
15041
15042 def _le_diamond(self, annot, p1, p2, lr, fill_color):
15043 """Make stream commands for diamond line end symbol. "lr" denotes left (False) or right point.
15044 """
15045 m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2, fill_color)
15046 shift = 2.5 # 2*shift*width = length of square edge
15047 d = shift * max(1, w)
15048 M = R - (d/2., 0) if lr else L + (d/2., 0)
15049 r = Rect(M, M) + (-d, -d, d, d) # the square
15050 # the square makes line longer by (2*shift - 1)*width
15051 p = (r.tl + (r.bl - r.tl) * 0.5) * im
15052 ap = "q\n%s%f %f m\n" % (opacity, p.x, p.y)
15053 p = (r.tl + (r.tr - r.tl) * 0.5) * im
15054 ap += "%f %f l\n" % (p.x, p.y)
15055 p = (r.tr + (r.br - r.tr) * 0.5) * im
15056 ap += "%f %f l\n" % (p.x, p.y)
15057 p = (r.br + (r.bl - r.br) * 0.5) * im
15058 ap += "%f %f l\n" % (p.x, p.y)
15059 ap += "%g w\n" % w
15060 ap += scol + fcol + "b\nQ\n"
15061 return ap
15062
15063 def _le_square(self, annot, p1, p2, lr, fill_color):
15064 """Make stream commands for square line end symbol. "lr" denotes left (False) or right point.
15065 """
15066 m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2, fill_color)
15067 shift = 2.5 # 2*shift*width = length of square edge
15068 d = shift * max(1, w)
15069 M = R - (d/2., 0) if lr else L + (d/2., 0)
15070 r = Rect(M, M) + (-d, -d, d, d) # the square
15071 # the square makes line longer by (2*shift - 1)*width
15072 p = r.tl * im
15073 ap = "q\n%s%f %f m\n" % (opacity, p.x, p.y)
15074 p = r.tr * im
15075 ap += "%f %f l\n" % (p.x, p.y)
15076 p = r.br * im
15077 ap += "%f %f l\n" % (p.x, p.y)
15078 p = r.bl * im
15079 ap += "%f %f l\n" % (p.x, p.y)
15080 ap += "%g w\n" % w
15081 ap += scol + fcol + "b\nQ\n"
15082 return ap
15083
15084 def _le_circle(self, annot, p1, p2, lr, fill_color):
15085 """Make stream commands for circle line end symbol. "lr" denotes left (False) or right point.
15086 """
15087 m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2, fill_color)
15088 shift = 2.5 # 2*shift*width = length of square edge
15089 d = shift * max(1, w)
15090 M = R - (d/2., 0) if lr else L + (d/2., 0)
15091 r = Rect(M, M) + (-d, -d, d, d) # the square
15092 ap = "q\n" + opacity + self._oval_string(r.tl * im, r.tr * im, r.br * im, r.bl * im)
15093 ap += "%g w\n" % w
15094 ap += scol + fcol + "b\nQ\n"
15095 return ap
15096
15097 def _le_butt(self, annot, p1, p2, lr, fill_color):
15098 """Make stream commands for butt line end symbol. "lr" denotes left (False) or right point.
15099 """
15100 m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2, fill_color)
15101 shift = 3
15102 d = shift * max(1, w)
15103 M = R if lr else L
15104 top = (M + (0, -d/2.)) * im
15105 bot = (M + (0, d/2.)) * im
15106 ap = "\nq\n%s%f %f m\n" % (opacity, top.x, top.y)
15107 ap += "%f %f l\n" % (bot.x, bot.y)
15108 ap += "%g w\n" % w
15109 ap += scol + "s\nQ\n"
15110 return ap
15111
15112 def _le_slash(self, annot, p1, p2, lr, fill_color):
15113 """Make stream commands for slash line end symbol. "lr" denotes left (False) or right point.
15114 """
15115 m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2, fill_color)
15116 rw = 1.1547 * max(1, w) * 1.0 # makes rect diagonal a 30 deg inclination
15117 M = R if lr else L
15118 r = Rect(M.x - rw, M.y - 2 * w, M.x + rw, M.y + 2 * w)
15119 top = r.tl * im
15120 bot = r.br * im
15121 ap = "\nq\n%s%f %f m\n" % (opacity, top.x, top.y)
15122 ap += "%f %f l\n" % (bot.x, bot.y)
15123 ap += "%g w\n" % w
15124 ap += scol + "s\nQ\n"
15125 return ap
15126
15127 def _le_openarrow(self, annot, p1, p2, lr, fill_color):
15128 """Make stream commands for open arrow line end symbol. "lr" denotes left (False) or right point.
15129 """
15130 m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2, fill_color)
15131 shift = 2.5
15132 d = shift * max(1, w)
15133 p2 = R + (d/2., 0) if lr else L - (d/2., 0)
15134 p1 = p2 + (-2*d, -d) if lr else p2 + (2*d, -d)
15135 p3 = p2 + (-2*d, d) if lr else p2 + (2*d, d)
15136 p1 *= im
15137 p2 *= im
15138 p3 *= im
15139 ap = "\nq\n%s%f %f m\n" % (opacity, p1.x, p1.y)
15140 ap += "%f %f l\n" % (p2.x, p2.y)
15141 ap += "%f %f l\n" % (p3.x, p3.y)
15142 ap += "%g w\n" % w
15143 ap += scol + "S\nQ\n"
15144 return ap
15145
15146 def _le_closedarrow(self, annot, p1, p2, lr, fill_color):
15147 """Make stream commands for closed arrow line end symbol. "lr" denotes left (False) or right point.
15148 """
15149 m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2, fill_color)
15150 shift = 2.5
15151 d = shift * max(1, w)
15152 p2 = R + (d/2., 0) if lr else L - (d/2., 0)
15153 p1 = p2 + (-2*d, -d) if lr else p2 + (2*d, -d)
15154 p3 = p2 + (-2*d, d) if lr else p2 + (2*d, d)
15155 p1 *= im
15156 p2 *= im
15157 p3 *= im
15158 ap = "\nq\n%s%f %f m\n" % (opacity, p1.x, p1.y)
15159 ap += "%f %f l\n" % (p2.x, p2.y)
15160 ap += "%f %f l\n" % (p3.x, p3.y)
15161 ap += "%g w\n" % w
15162 ap += scol + fcol + "b\nQ\n"
15163 return ap
15164
15165 def _le_ropenarrow(self, annot, p1, p2, lr, fill_color):
15166 """Make stream commands for right open arrow line end symbol. "lr" denotes left (False) or right point.
15167 """
15168 m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2, fill_color)
15169 shift = 2.5
15170 d = shift * max(1, w)
15171 p2 = R - (d/3., 0) if lr else L + (d/3., 0)
15172 p1 = p2 + (2*d, -d) if lr else p2 + (-2*d, -d)
15173 p3 = p2 + (2*d, d) if lr else p2 + (-2*d, d)
15174 p1 *= im
15175 p2 *= im
15176 p3 *= im
15177 ap = "\nq\n%s%f %f m\n" % (opacity, p1.x, p1.y)
15178 ap += "%f %f l\n" % (p2.x, p2.y)
15179 ap += "%f %f l\n" % (p3.x, p3.y)
15180 ap += "%g w\n" % w
15181 ap += scol + fcol + "S\nQ\n"
15182 return ap
15183
15184 def _le_rclosedarrow(self, annot, p1, p2, lr, fill_color):
15185 """Make stream commands for right closed arrow line end symbol. "lr" denotes left (False) or right point.
15186 """
15187 m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2, fill_color)
15188 shift = 2.5
15189 d = shift * max(1, w)
15190 p2 = R - (2*d, 0) if lr else L + (2*d, 0)
15191 p1 = p2 + (2*d, -d) if lr else p2 + (-2*d, -d)
15192 p3 = p2 + (2*d, d) if lr else p2 + (-2*d, d)
15193 p1 *= im
15194 p2 *= im
15195 p3 *= im
15196 ap = "\nq\n%s%f %f m\n" % (opacity, p1.x, p1.y)
15197 ap += "%f %f l\n" % (p2.x, p2.y)
15198 ap += "%f %f l\n" % (p3.x, p3.y)
15199 ap += "%g w\n" % w
15200 ap += scol + fcol + "b\nQ\n"
15201 return ap
15202
15203 def __del__(self):
15204 if not type(self) is Tools:
15205 return
15206 if getattr(self, "thisown", False):
15207 self.__swig_destroy__(self)
15208 %}
15209 }
15210 };