Mercurial > hgrepos > Python2 > PyMuPDF
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 }; |
