diff --git a/PKG-INFO b/PKG-INFO index 4eb4c32e0..16e646bf8 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: PyMuPDF -Version: 1.14.12 +Version: 1.14.13 Author: Ruikai Liu Author-email: lrk700@gmail.com Maintainer: Jorj X. McKie @@ -21,7 +21,7 @@ Description: Introduction ============ - This is **version 1.14.12 of PyMuPDF**, a Python binding for `MuPDF `_ - "a lightweight PDF and XPS viewer". + This is **version 1.14.13 of PyMuPDF**, a Python binding for `MuPDF `_ - "a lightweight PDF and XPS viewer". MuPDF can access files in PDF, XPS, OpenXPS, epub, comic and fiction book formats, and it is known for both, its top performance and high rendering quality. diff --git a/README.md b/README.md index 65053d1db..09faf9dcd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# PyMuPDF 1.14.12 +# PyMuPDF 1.14.13 ![logo](https://github.com/pymupdf/PyMuPDF/blob/master/demo/pymupdf.jpg) -Release date: January 15, 2018 +Release date: April 7, 2019 **Travis-CI:** [![Build Status](https://travis-ci.org/JorjMcKie/py-mupdf.svg?branch=master)](https://travis-ci.org/JorjMcKie/py-mupdf) @@ -14,7 +14,7 @@ On **[PyPI](https://pypi.org/project/PyMuPDF)** since August 2016: [![](https:// # Introduction -This is **version 1.14.12 of PyMuPDF (formerly python-fitz)**, a Python binding with support for [MuPDF 1.14.x](http://mupdf.com/) - "a lightweight PDF, XPS, and E-book viewer". +This is **version 1.14.13 of PyMuPDF (formerly python-fitz)**, a Python binding with support for [MuPDF 1.14.x](http://mupdf.com/) - "a lightweight PDF, XPS, and E-book viewer". MuPDF can access files in PDF, XPS, OpenXPS, CBZ, EPUB and FB2 (e-books) formats, and it is known for its top performance and high rendering quality. @@ -79,7 +79,7 @@ Our **documentation**, written using Sphinx, is available in various formats fro * You can view it online at [Read the Docs](https://pymupdf.readthedocs.io/). For **best quality downloads** you should however use the following links. * zipped [HTML](https://github.com/pymupdf/PyMuPDF/tree/master/doc/html.zip) -* [Windows CHM](https://github.com/JorjMcKie/PyMuPDF-optional-material/tree/master/doc/PyMuPDF.chm) +* [Windows CHM](https://github.com/pymupdf/PyMuPDF-optional-material/tree/master/doc/PyMuPDF.chm) * [PDF](https://github.com/pymupdf/PyMuPDF/blob/master/doc/PyMuPDF.pdf) # Earlier Versions diff --git a/doc/PyMuPDF.pdf b/doc/PyMuPDF.pdf index 8388c3075..6873aec5c 100644 Binary files a/doc/PyMuPDF.pdf and b/doc/PyMuPDF.pdf differ diff --git a/doc/html.zip b/doc/html.zip index 78b2b7bf3..132476fc2 100644 Binary files a/doc/html.zip and b/doc/html.zip differ diff --git a/fitz/fitz.i b/fitz/fitz.i index b02b3f976..dc7d05008 100644 --- a/fitz/fitz.i +++ b/fitz/fitz.i @@ -101,7 +101,7 @@ # endif // define Python None object -#define NONE Py_BuildValue("", NULL) +#define NONE Py_BuildValue("") #include #include @@ -171,6 +171,7 @@ struct DeviceWrapper { %pythoncode %{ import os import weakref +import io from binascii import hexlify import math @@ -213,28 +214,36 @@ struct fz_document_s if not filename or type(filename) is str: pass else: - if fitz_py2: # Python 2 + if fitz_py2: # Python 2 if type(filename) is unicode: filename = filename.encode("utf8") else: - filename = str(filename) # should take care of pathlib + filename = str(filename) # should take care of pathlib - self.streamlen = len(stream) if stream else 0 + if stream: + if not (filename or filetype): + raise ValueError("need filetype for opening a stream") + + if type(stream) is bytes: + self.stream = stream + elif type(stream) is bytearray: + self.stream = bytes(stream) + elif type(stream) is io.BytesIO: + self.stream = stream.getvalue() + else: + raise ValueError("'stream' has bad type") + stream = self.stream + else: + self.stream = None - self.name = "" - if filename and self.streamlen == 0: + if filename and not stream: self.name = filename - - if self.streamlen > 0: - if not (filename or filetype): - raise ValueError("filetype missing with stream specified") - if type(stream) not in (bytes, bytearray): - raise ValueError("stream must be bytes or bytearray") + else: + self.name = "" self.isClosed = False self.isEncrypted = 0 self.metadata = None - self.stream = stream # prevent garbage collecting this self.openErrCode = 0 self.openErrMsg = '' self.FontInfos = [] @@ -256,16 +265,17 @@ struct fz_document_s self.thisown = False %} - fz_document_s(const char *filename = NULL, PyObject *stream = NULL, - const char *filetype = NULL, PyObject *rect = NULL, - float width = 0, float height = 0, - float fontsize = 11) + fz_document_s(const char *filename=NULL, PyObject *stream=NULL, + const char *filetype=NULL, PyObject *rect=NULL, + float width=0, float height=0, + float fontsize=11) { gctx->error->errcode = 0; // reset any error code gctx->error->message[0] = 0; // reset any error message struct fz_document_s *doc = NULL; + char *c = NULL; + size_t len = 0; fz_stream *data = NULL; - char *streamdata; float w = width, h = height; fz_rect r = JM_rect_from_py(rect); if (!(fz_is_empty_rect(r) && !fz_is_infinite_rect(r))) @@ -274,12 +284,13 @@ struct fz_document_s h = r.y1 - r.y0; } - size_t streamlen = JM_CharFromBytesOrArray(stream, &streamdata); fz_try(gctx) { - if (streamlen > 0) + if (stream != NONE) // stream given, MUST be bytes! { - data = fz_open_memory(gctx, streamdata, streamlen); + c = PyBytes_AsString(stream); // just a pointer, no new obj + len = (size_t) PyBytes_Size(stream); + data = fz_open_memory(gctx, c, len); char *magic = (char *)filename; if (!magic) magic = (char *)filetype; doc = fz_open_document_with_stream(gctx, magic, data); @@ -330,6 +341,7 @@ struct fz_document_s self.Graftmaps = {} self.ShownPages = {} %} + %pythonappend close %{self.thisown = False%} void close() { @@ -492,7 +504,6 @@ struct fz_document_s } FITZEXCEPTION(embeddedFileUpd, !result) - CLOSECHECK(embeddedFileUpd) %feature("autodoc","Change an embedded file given its entry number or name.") embeddedFileUpd; PyObject *embeddedFileUpd(PyObject *id, PyObject *buffer = NULL, char *filename = NULL, char *ufilename = NULL, char *desc = NULL) { @@ -509,19 +520,20 @@ struct fz_document_s pdf_obj *entry = pdf_portfolio_entry_obj(gctx, pdf, n); pdf_obj *filespec = pdf_dict_getl(gctx, entry, PDF_NAME(EF), PDF_NAME(F), NULL); + if (!filespec) THROWMSG("bad PDF: /EF object not found"); - char *data = NULL; - size_t len = JM_CharFromBytesOrArray(buffer, &data); - if (len > 0) + res = JM_BufferFromBytes(gctx, buffer); + if (buffer && !res) THROWMSG("'buffer' has bad type"); + if (res) { - if (!filespec) THROWMSG("/EF object not found"); - res = fz_new_buffer_from_copied_data(gctx, data, len); JM_update_stream(gctx, pdf, filespec, res); // adjust /DL and /Size parameters - pdf_obj *l = pdf_new_int(gctx, (int64_t) len); + int64_t len = (int64_t) fz_buffer_storage(gctx, res, NULL); + pdf_obj *l = pdf_new_int(gctx, len); pdf_dict_put(gctx, filespec, PDF_NAME(DL), l); pdf_dict_putl(gctx, filespec, l, PDF_NAME(Params), PDF_NAME(Size), NULL); } + if (filename) pdf_dict_put_text_string(gctx, entry, PDF_NAME(F), filename); @@ -568,7 +580,10 @@ struct fz_document_s } FITZEXCEPTION(embeddedFileAdd, !result) - CLOSECHECK(embeddedFileAdd) + %pythonprepend embeddedFileAdd %{ +if self.isClosed or self.isEncrypted: + raise ValueError("operation illegal for closed / encrypted doc") +%} %feature("autodoc","Embed a new file.") embeddedFileAdd; PyObject *embeddedFileAdd(PyObject *buffer, const char *name, char *filename=NULL, char *ufilename=NULL, char *desc=NULL) { @@ -589,8 +604,9 @@ struct fz_document_s fz_try(gctx) { assert_PDF(pdf); - size = JM_CharFromBytesOrArray(buffer, &buffdata); - if (size < 1) THROWMSG("buffer not bytes / bytearray"); + data = JM_BufferFromBytes(gctx, buffer); + if (!data) THROWMSG("bad type 'buffer'"); + size = fz_buffer_storage(gctx, data, &buffdata); // we do not allow duplicate names entry = JM_find_embedded(gctx, Py_BuildValue("s", name), pdf); @@ -623,7 +639,6 @@ struct fz_document_s // (2) insert the real file contents pdf_obj *filespec = pdf_dict_getl(gctx, o, PDF_NAME(EF), PDF_NAME(F), NULL); - data = fz_new_buffer_from_copied_data(gctx, buffdata, size); JM_update_stream(gctx, pdf, filespec, data); // finally update some size attributes pdf_obj *l = pdf_new_int(gctx, (int64_t) size); @@ -910,7 +925,7 @@ struct fz_document_s if self.pageCount < 1: raise ValueError("cannot save with zero pages") if incremental: - if self.name != filename or self.streamlen > 0: + if self.name != filename or self.stream: raise ValueError("incremental needs original file") %} @@ -1065,7 +1080,7 @@ if links: PyObject *_newPage(int pno=-1, float width=595, float height=842) { pdf_document *pdf = pdf_specifics(gctx, $self); - fz_rect mediabox = { 0, 0, 595, 842 }; // ISO-A4 portrait values + fz_rect mediabox = fz_unit_rect; mediabox.x1 = width; mediabox.y1 = height; pdf_obj *resources = NULL, *page_obj = NULL; @@ -1073,7 +1088,7 @@ if links: fz_try(gctx) { assert_PDF(pdf); - if (pno < -1) THROWMSG("invalid page number(s)"); + if (pno < -1) THROWMSG("bad page number(s)"); // create /Resources and /Contents objects resources = pdf_add_object_drop(gctx, pdf, pdf_new_dict(gctx, pdf, 1)); page_obj = pdf_add_page(gctx, pdf, mediabox, 0, resources, contents); @@ -1226,7 +1241,7 @@ if links: fz_var(pageref); fz_try(gctx) { - if (n >= pageCount) THROWMSG("invalid page number(s)"); + if (n >= pageCount) THROWMSG("bad page number(s)"); assert_PDF(pdf); pageref = pdf_lookup_page_obj(gctx, pdf, n); } @@ -1256,7 +1271,7 @@ if links: fz_var(liste); fz_try(gctx) { - if (n >= pageCount) THROWMSG("invalid page number(s)"); + if (n >= pageCount) THROWMSG("bad page number(s)"); assert_PDF(pdf); pageref = pdf_lookup_page_obj(gctx, pdf, n); rsrc = pdf_dict_get(gctx, pageref, PDF_NAME(Resources)); @@ -1991,17 +2006,17 @@ if links: def __repr__(self): m = "closed " if self.isClosed else "" - if self.streamlen == 0: - if self.name == "": - return m + "fitz.Document()" + if self.stream is None: + if self.name is "": + return m + "fitz.Document()" % self._graft_id return m + "fitz.Document('%s')" % (self.name,) - return m + "fitz.Document('%s', )" % (self.name,) + return m + "fitz.Document('%s', )" % (self.name, self._graft_id) def __getitem__(self, i=0): if type(i) is not int: - raise ValueError("invalid page number(s)") + raise ValueError("bad page number(s)") if i >= len(self): - raise IndexError("invalid page number(s)") + raise IndexError("bad page number(s)") return self.loadPage(i) def __len__(self): @@ -2030,11 +2045,12 @@ if links: for gmap in self.Graftmaps: self.Graftmaps[gmap] = None if hasattr(self, "this") and self.thisown: - self.thisown = False self.__swig_destroy__(self) + self.thisown = False + self.Graftmaps = {} self.ShownPages = {} - self.stream = None + self.stream = None self._reset_page_refs = DUMMY self.__swig_destroy__ = DUMMY self.isClosed = True @@ -2301,11 +2317,11 @@ struct fz_page_s { fz_try(gctx) { assert_PDF(page); + filebuf = JM_BufferFromBytes(gctx, buffer); + if (!filebuf) THROWMSG("bad 'buffer' data"); annot = pdf_create_annot(gctx, page, ANNOT_FILEATTACHMENT); pdf_set_annot_rect(gctx, annot, r); pdf_set_annot_icon_name(gctx, annot, "PushPin"); - len = JM_CharFromBytesOrArray(buffer, &data); - filebuf = fz_new_buffer_from_shared_data(gctx, data, len); pdf_obj *val = JM_embed_file(gctx, page->doc, filebuf, filename, uf, d); pdf_dict_put(gctx, annot->obj, PDF_NAME(FS), val); @@ -3028,11 +3044,9 @@ fannot._erase() fz_pixmap *pix = NULL; fz_image *mask = NULL; fz_separations *seps = NULL; - pdf_obj *resources, *subres, *ref; + pdf_obj *resources, *xobject, *ref; fz_buffer *res = NULL, *nres = NULL, *imgbuf = NULL; fz_matrix mat = JM_matrix_from_py(matrix); // pre-calculated - char *streamdata = NULL; - size_t streamlen = JM_CharFromBytesOrArray(stream, &streamdata); const char *template = " q %g %g %g %g %g %g cm /%s Do Q "; char *cont = NULL; @@ -3040,33 +3054,23 @@ fannot._erase() fz_image *zimg = NULL, *image = NULL; fz_try(gctx) { - pdf = page->doc; - - // get objects "Resources" & "XObject" - resources = pdf_dict_get(gctx, page->obj, PDF_NAME(Resources)); - subres = pdf_dict_get(gctx, resources, PDF_NAME(XObject)); - if (!subres) // has no XObject yet, create one - { - subres = pdf_new_dict(gctx, pdf, 10); - pdf_dict_putl_drop(gctx, page->obj, subres, PDF_NAME(Resources), PDF_NAME(XObject), NULL); - } - + //------------------------------------------------------------- // create the image - if (filename || streamlen > 0) + //------------------------------------------------------------- + if (filename || stream) { - if (!streamlen) + if (filename) image = fz_new_image_from_file(gctx, filename); else { - imgbuf = fz_new_buffer_from_copied_data(gctx, - streamdata, streamlen); + imgbuf = JM_BufferFromBytes(gctx, stream); image = fz_new_image_from_buffer(gctx, imgbuf); } - // test alpha channel (would require SMask creation) + // test for alpha (which would require making an SMask) pix = fz_get_pixmap_from_image(gctx, image, NULL, NULL, 0, 0); if (pix->alpha == 1) - { // have alpha, therefore create a mask + { // have alpha: create an SMask pm = fz_convert_pixmap(gctx, pix, NULL, NULL, NULL, NULL, 1); pm->alpha = 0; pm->colorspace = fz_keep_colorspace(gctx, fz_device_gray(gctx)); @@ -3082,7 +3086,7 @@ fannot._erase() if (pixmap->alpha == 0) image = fz_new_image_from_pixmap(gctx, pixmap, NULL); else - { // pixmap has alpha, therefore create an SMask + { // pixmap has alpha: create an SMask pm = fz_convert_pixmap(gctx, pixmap, NULL, NULL, NULL, NULL, 1); pm->alpha = 0; pm->colorspace = fz_keep_colorspace(gctx, fz_device_gray(gctx)); @@ -3090,11 +3094,25 @@ fannot._erase() image = fz_new_image_from_pixmap(gctx, pixmap, mask); } } + + //------------------------------------------------------------- // image created - now put it in the PDF + //------------------------------------------------------------- + pdf = page->doc; // owning PDF + + // get /Resources, /XObject + resources = pdf_dict_get(gctx, page->obj, PDF_NAME(Resources)); + xobject = pdf_dict_get(gctx, resources, PDF_NAME(XObject)); + if (!xobject) // has no XObject yet, create one + { + xobject = pdf_new_dict(gctx, pdf, 10); + pdf_dict_putl_drop(gctx, page->obj, xobject, PDF_NAME(Resources), PDF_NAME(XObject), NULL); + } + ref = pdf_add_image(gctx, pdf, image, 0); - pdf_dict_puts(gctx, subres, _imgname, ref); // store ref-name + pdf_dict_puts(gctx, xobject, _imgname, ref); // update XObject - // prep contents stream buffer with invoking command + // make contents stream that invokes the image nres = fz_new_buffer(gctx, 50); fz_append_printf(gctx, nres, template, mat.a, mat.b, mat.c, mat.d, mat.e, mat.f, @@ -3193,6 +3211,7 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, pdf_document *pdf; pdf_obj *resources, *fonts, *font_obj; fz_font *font; + fz_buffer *res = NULL; char *data = NULL; int size, ixref = 0, index = 0, simple = 0; PyObject *value; @@ -3240,9 +3259,9 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, font = fz_new_font_from_file(gctx, NULL, fontfile, idx, 0); else { - size = (int) JM_CharFromBytesOrArray(fontbuffer, &data); - if (!size) THROWMSG("one of fontfile, fontbuffer must be given"); - font = fz_new_font_from_memory(gctx, NULL, data, size, idx, 0); + res = JM_BufferFromBytes(gctx, fontbuffer); + if (!res) THROWMSG("one of fontfile, fontbuffer must be given"); + font = fz_new_font_from_buffer(gctx, NULL, res, idx, 0); } if (!set_simple) @@ -3284,6 +3303,10 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, pdf_drop_obj(gctx, font_obj); fz_drop_font(gctx, font); } + fz_always(gctx) + { + fz_drop_buffer(gctx, res); + } fz_catch(gctx) return NULL; pdf->dirty = 1; return value; @@ -3372,19 +3395,19 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, def __str__(self): CheckParent(self) x = self.parent.name - if self.parent.streamlen > 0: - x += " (memory)" + if self.parent.stream is not None: + x = "" % (self.parent._graft_id,) if x == "": - x = "" + x = "" % self.parent._graft_id return "page %s of %s" % (self.number, x) def __repr__(self): CheckParent(self) x = self.parent.name - if self.parent.streamlen > 0: - x += " (memory)" + if self.parent.stream is not None: + x = "" % (self.parent._graft_id,) if x == "": - x = "" + x = "" % self.parent._graft_id return "page %s of %s" % (self.number, x) def _forget_annot(self, annot): @@ -3565,19 +3588,30 @@ struct fz_pixmap_s //--------------------------------------------------------------------- fz_pixmap_s(struct fz_colorspace_s *cs, int w, int h, PyObject *samples, int alpha = 0) { - char *data = NULL; int n = fz_colorspace_n(gctx, cs); int stride = (n + alpha)*w; fz_separations *seps = NULL; + fz_buffer *res = NULL; fz_pixmap *pm = NULL; - size_t size = JM_CharFromBytesOrArray(samples, &data); fz_try(gctx) { - if (size < 1) THROWMSG("invalid arg type samples"); - if (stride * h != size) THROWMSG("invalid arg len samples"); - pm = fz_new_pixmap_with_data(gctx, cs, w, h, seps, alpha, stride, data); + size_t size = 0; + unsigned char *c = NULL; + res = JM_BufferFromBytes(gctx, samples); + if (!res) THROWMSG("bad samples data"); + size = fz_buffer_storage(gctx, res, &c); + if (stride * h != size) THROWMSG("bad samples length"); + pm = fz_new_pixmap(gctx, cs, w, h, seps, alpha); + memcpy(pm->samples, c, size); + } + fz_always(gctx) + { + fz_drop_buffer(gctx, res); + } + fz_catch(gctx) + { + return NULL; } - fz_catch(gctx) return NULL; return pm; } @@ -3589,12 +3623,17 @@ struct fz_pixmap_s fz_image *img = NULL; fz_pixmap *pm = NULL; fz_try(gctx) { - if (!filename) THROWMSG("invalid argument type"); img = fz_new_image_from_file(gctx, filename); pm = fz_get_pixmap_from_image(gctx, img, NULL, NULL, NULL, NULL); } - fz_always(gctx) fz_drop_image(gctx, img); - fz_catch(gctx) return NULL; + fz_always(gctx) + { + fz_drop_image(gctx, img); + } + fz_catch(gctx) + { + return NULL; + } return pm; } @@ -3603,23 +3642,20 @@ struct fz_pixmap_s //--------------------------------------------------------------------- fz_pixmap_s(PyObject *imagedata) { - size_t size = 0; - char *streamdata; - fz_buffer *data = NULL; + fz_buffer *res = NULL; fz_image *img = NULL; fz_pixmap *pm = NULL; fz_try(gctx) { - size = JM_CharFromBytesOrArray(imagedata, &streamdata); - if (size < 1) THROWMSG("bad image data"); - data = fz_new_buffer_from_shared_data(gctx, - streamdata, size); - img = fz_new_image_from_buffer(gctx, data); + res = JM_BufferFromBytes(gctx, imagedata); + if (!res) THROWMSG("bad image data"); + img = fz_new_image_from_buffer(gctx, res); pm = fz_get_pixmap_from_image(gctx, img, NULL, NULL, NULL, NULL); } fz_always(gctx) { fz_drop_image(gctx, img); + fz_drop_buffer(gctx, res); } fz_catch(gctx) return NULL; return pm; @@ -3747,6 +3783,7 @@ struct fz_pixmap_s FITZEXCEPTION(setAlpha, !result) PyObject *setAlpha(PyObject *alphavalues=NULL) { + fz_buffer *res = NULL; fz_try(gctx) { if ($self->alpha == 0) THROWMSG("pixmap has no alpha"); @@ -3758,9 +3795,14 @@ struct fz_pixmap_s int data_len = 0; if (alphavalues) { - data_len = (int) JM_CharFromBytesOrArray(alphavalues, &data); - if (data_len && data_len < w * h) - THROWMSG("not enough alpha values"); + res = JM_BufferFromBytes(gctx, alphavalues); + if (res) + { + data_len = (int) fz_buffer_storage(gctx, res, &data); + if (data && data_len < w * h) + THROWMSG("not enough alpha values"); + } + else THROWMSG("bad type 'alphavalues'"); } int i = 0, k = 0; while (i < balen) @@ -3771,7 +3813,14 @@ struct fz_pixmap_s k += 1; } } - fz_catch(gctx) return NULL; + fz_always(gctx) + { + fz_drop_buffer(gctx, res); + } + fz_catch(gctx) + { + return NULL; + } return NONE; } @@ -4497,10 +4546,8 @@ struct fz_annot_s if (!apobj) THROWMSG("annot has no /AP/N object"); if (!pdf_is_stream(gctx, apobj)) THROWMSG("/AP/N object is no stream"); - char *c = NULL; - size_t len = JM_CharFromBytesOrArray(ap, &c); - if (!c) THROWMSG("invalid /AP stream argument"); - res = fz_new_buffer_from_copied_data(gctx, c, strlen(c)); + res = JM_BufferFromBytes(gctx, ap); + if (!res) THROWMSG("invalid /AP stream argument"); JM_update_stream(gctx, annot->page->doc, apobj, res); if (rect) { @@ -5128,11 +5175,14 @@ struct fz_annot_s } //--------------------------------------------------------------------- - // annotation update attached file content + // annotation update attached file //--------------------------------------------------------------------- FITZEXCEPTION(fileUpd, !result) - PARENTCHECK(fileUpd) - %feature("autodoc","Update annotation attached file content.") fileUpd; + %pythonprepend fileUpd %{ +CheckParent(self) +%} + + %feature("autodoc","Update annotation attached file.") fileUpd; PyObject *fileUpd(PyObject *buffer=NULL, char *filename=NULL, char *ufilename=NULL, char *desc=NULL) { pdf_annot *annot = pdf_annot_from_fz_annot(gctx, $self); @@ -5144,41 +5194,43 @@ struct fz_annot_s fz_try(gctx) { assert_PDF(annot); // must be a PDF - pdf = annot->page->doc; // this is the PDF + pdf = annot->page->doc; // the owning PDF int type = (int) pdf_annot_type(gctx, annot); if (type != ANNOT_FILEATTACHMENT) - THROWMSG("not a file attachment annot"); + THROWMSG("no FileAttachment annot"); stream = pdf_dict_getl(gctx, annot->obj, PDF_NAME(FS), PDF_NAME(EF), PDF_NAME(F), NULL); // the object for file content - if (!stream) THROWMSG("bad PDF: file entry not found"); + if (!stream) THROWMSG("bad PDF: /EF object not found"); fs = pdf_dict_get(gctx, annot->obj, PDF_NAME(FS)); - // file content is ignored if not bytes / bytearray - size = (int64_t) JM_CharFromBytesOrArray(buffer, &data); - if (size > 0) + // file content given + res = JM_BufferFromBytes(gctx, buffer); + if (buffer && !res) THROWMSG("'buffer' has bad type"); + if (res) { - pdf_obj *s = pdf_new_int(gctx, size); - pdf_dict_put(gctx, stream, PDF_NAME(Filter), - PDF_NAME(FlateDecode)); - - pdf_dict_putl_drop(gctx, stream, s, - PDF_NAME(Params), PDF_NAME(Size), NULL); - res = JM_deflatebuf(gctx, data, size); - pdf_update_stream(gctx, pdf, stream, res, 1); + JM_update_stream(gctx, pdf, stream, res); + // adjust /DL and /Size parameters + int64_t len = (int64_t) fz_buffer_storage(gctx, res, NULL); + pdf_obj *l = pdf_new_int(gctx, len); + pdf_dict_put(gctx, stream, PDF_NAME(DL), l); + pdf_dict_putl(gctx, stream, l, PDF_NAME(Params), PDF_NAME(Size), NULL); } if (filename) // new filename given { pdf_dict_put_text_string(gctx, stream, PDF_NAME(F), filename); pdf_dict_put_text_string(gctx, fs, PDF_NAME(F), filename); + pdf_dict_put_text_string(gctx, stream, PDF_NAME(UF), filename); + pdf_dict_put_text_string(gctx, fs, PDF_NAME(UF), filename); + pdf_dict_put_text_string(gctx, annot->obj, PDF_NAME(Contents), filename); } if (ufilename) { - pdf_dict_put_text_string(gctx, stream, PDF_NAME(UF), filename); - pdf_dict_put_text_string(gctx, fs, PDF_NAME(UF), filename); + pdf_dict_put_text_string(gctx, stream, PDF_NAME(UF), ufilename); + pdf_dict_put_text_string(gctx, fs, PDF_NAME(UF), ufilename); } if (desc) // new description given @@ -6241,6 +6293,12 @@ struct Tools return Py_BuildValue("i", (int) gctx->store->size); } + %feature("autodoc","Determine dimension and other image data.") image_size; + PyObject *image_size(PyObject *imagedata) + { + return JM_image_size(gctx, imagedata); + } + %feature("autodoc","Current store size.") store_size; %pythoncode%{@property%} PyObject *store_size() diff --git a/fitz/fitz.py b/fitz/fitz.py index 7131ed795..4db6f532d 100644 --- a/fitz/fitz.py +++ b/fitz/fitz.py @@ -98,6 +98,7 @@ class _object: import os import weakref +import io from binascii import hexlify import math @@ -105,9 +106,9 @@ class _object: VersionFitz = "1.14.0" -VersionBind = "1.14.12" -VersionDate = "2019-03-21 06:59:25" -version = (VersionBind, VersionFitz, "20190321065925") +VersionBind = "1.14.13" +VersionDate = "2019-04-07 06:43:20" +version = (VersionBind, VersionFitz, "20190407064320") class Matrix(): @@ -174,7 +175,7 @@ def preTranslate(self, tx, ty): return self def preScale(self, sx, sy): - """Calculate pre scaling and replacing current matrix.""" + """Calculate pre scaling and replace current matrix.""" self.a *= sx self.b *= sx self.c *= sy @@ -1782,28 +1783,36 @@ def __init__(self, filename=None, stream=None, filetype=None, rect=None, width=0 if not filename or type(filename) is str: pass else: - if fitz_py2: # Python 2 + if fitz_py2: # Python 2 if type(filename) is unicode: filename = filename.encode("utf8") else: - filename = str(filename) # should take care of pathlib + filename = str(filename) # should take care of pathlib - self.streamlen = len(stream) if stream else 0 + if stream: + if not (filename or filetype): + raise ValueError("need filetype for opening a stream") + + if type(stream) is bytes: + self.stream = stream + elif type(stream) is bytearray: + self.stream = bytes(stream) + elif type(stream) is io.BytesIO: + self.stream = stream.getvalue() + else: + raise ValueError("'stream' has bad type") + stream = self.stream + else: + self.stream = None - self.name = "" - if filename and self.streamlen == 0: + if filename and not stream: self.name = filename - - if self.streamlen > 0: - if not (filename or filetype): - raise ValueError("filetype missing with stream specified") - if type(stream) not in (bytes, bytearray): - raise ValueError("stream must be bytes or bytearray") + else: + self.name = "" self.isClosed = False self.isEncrypted = 0 self.metadata = None - self.stream = stream # prevent garbage collecting this self.openErrCode = 0 self.openErrMsg = '' self.FontInfos = [] @@ -1920,9 +1929,6 @@ def embeddedFileInfo(self, id): def embeddedFileUpd(self, id, buffer=None, filename=None, ufilename=None, desc=None): """Change an embedded file given its entry number or name.""" - if self.isClosed or self.isEncrypted: - raise ValueError("operation illegal for closed / encrypted doc") - return _fitz.Document_embeddedFileUpd(self, id, buffer, filename, ufilename, desc) @@ -1941,9 +1947,11 @@ def embeddedFileGet(self, id): def embeddedFileAdd(self, buffer, name, filename=None, ufilename=None, desc=None): """Embed a new file.""" + if self.isClosed or self.isEncrypted: raise ValueError("operation illegal for closed / encrypted doc") + return _fitz.Document_embeddedFileAdd(self, buffer, name, filename, ufilename, desc) @@ -2126,7 +2134,7 @@ def save(self, filename, garbage=0, clean=0, deflate=0, incremental=0, ascii=0, if self.pageCount < 1: raise ValueError("cannot save with zero pages") if incremental: - if self.name != filename or self.streamlen > 0: + if self.name != filename or self.stream: raise ValueError("incremental needs original file") @@ -2461,17 +2469,17 @@ def saveIncr(self): def __repr__(self): m = "closed " if self.isClosed else "" - if self.streamlen == 0: - if self.name == "": - return m + "fitz.Document()" + if self.stream is None: + if self.name is "": + return m + "fitz.Document()" % self._graft_id return m + "fitz.Document('%s')" % (self.name,) - return m + "fitz.Document('%s', )" % (self.name,) + return m + "fitz.Document('%s', )" % (self.name, self._graft_id) def __getitem__(self, i=0): if type(i) is not int: - raise ValueError("invalid page number(s)") + raise ValueError("bad page number(s)") if i >= len(self): - raise IndexError("invalid page number(s)") + raise IndexError("bad page number(s)") return self.loadPage(i) def __len__(self): @@ -2500,11 +2508,12 @@ def __del__(self): for gmap in self.Graftmaps: self.Graftmaps[gmap] = None if hasattr(self, "this") and self.thisown: - self.thisown = False self.__swig_destroy__(self) + self.thisown = False + self.Graftmaps = {} self.ShownPages = {} - self.stream = None + self.stream = None self._reset_page_refs = DUMMY self.__swig_destroy__ = DUMMY self.isClosed = True @@ -3045,19 +3054,19 @@ def _setContents(self, xref=0): def __str__(self): CheckParent(self) x = self.parent.name - if self.parent.streamlen > 0: - x += " (memory)" + if self.parent.stream is not None: + x = "" % (self.parent._graft_id,) if x == "": - x = "" + x = "" % self.parent._graft_id return "page %s of %s" % (self.number, x) def __repr__(self): CheckParent(self) x = self.parent.name - if self.parent.streamlen > 0: - x += " (memory)" + if self.parent.stream is not None: + x = "" % (self.parent._graft_id,) if x == "": - x = "" + x = "" % self.parent._graft_id return "page %s of %s" % (self.number, x) def _forget_annot(self, annot): @@ -3877,9 +3886,11 @@ def fileGet(self): def fileUpd(self, buffer=None, filename=None, ufilename=None, desc=None): - """Update annotation attached file content.""" + """Update annotation attached file.""" + CheckParent(self) + return _fitz.Annot_fileUpd(self, buffer, filename, ufilename, desc) @property @@ -4382,6 +4393,11 @@ def store_shrink(self, percent): """Free 'percent' of current store size.""" return _fitz.Tools_store_shrink(self, percent) + + def image_size(self, imagedata): + """Determine dimension and other image data.""" + return _fitz.Tools_image_size(self, imagedata) + @property def store_size(self): diff --git a/fitz/fitz_wrap.c b/fitz/fitz_wrap.c index 3cb184379..eb694720a 100644 --- a/fitz/fitz_wrap.c +++ b/fitz/fitz_wrap.c @@ -3073,7 +3073,7 @@ static swig_module_info swig_module = {swig_types, 13, 0, 0, 0, 0}; # endif // define Python None object -#define NONE Py_BuildValue("", NULL) +#define NONE Py_BuildValue("") #include #include @@ -3515,9 +3515,9 @@ void JM_color_FromSequence(PyObject *color, int *n, float col[4]) PyObject *JM_BinFromBuffer(fz_context *ctx, fz_buffer *buffer) { PyObject *bytes = PyBytes_FromString(""); - char *c = NULL; if (buffer) { + char *c = NULL; size_t len = fz_buffer_storage(gctx, buffer, &c); bytes = PyBytes_FromStringAndSize(c, (Py_ssize_t) len); } @@ -3691,44 +3691,42 @@ void hexlify(int n, unsigned char *in, unsigned char *out) } //---------------------------------------------------------------------------- -// Turn a bytes or bytearray object into char* string -// using the "_AsString" functions. Returns string size or 0 on error. -//---------------------------------------------------------------------------- -size_t JM_CharFromBytesOrArray(PyObject *stream, char **data) -{ - *data = NULL; - size_t len = 0; - if (!stream) return 0; - if (PyBytes_Check(stream)) - { - *data = PyBytes_AsString(stream); - len = (size_t) PyBytes_Size(stream); - } - else if (PyByteArray_Check(stream)) - { - *data = PyByteArray_AsString(stream); - len = (size_t) PyByteArray_Size(stream); - } - return len; -} - -//---------------------------------------------------------------------------- -// Return fz_buffer from a PyBytes or PyByteArray object -// Attention: must be freed by caller! +// Make fz_buffer from a PyBytes, PyByteArray, io.BytesIO object //---------------------------------------------------------------------------- fz_buffer *JM_BufferFromBytes(fz_context *ctx, PyObject *stream) { if (!stream) return NULL; + if (stream == NONE) return NULL; char *c = NULL; - size_t len = JM_CharFromBytesOrArray(stream, &c); - if (!c) return NULL; + PyObject *mybytes = NULL; + size_t len = 0; fz_buffer *res = NULL; fz_var(res); fz_try(ctx) { - res = fz_new_buffer(ctx, len); - fz_append_data(ctx, res, c, len); - fz_terminate_buffer(ctx, res); + if (PyBytes_Check(stream)) + { + c = PyBytes_AsString(stream); + len = (size_t) PyBytes_Size(stream); + } + else if (PyByteArray_Check(stream)) + { + c = PyByteArray_AS_STRING(stream); + len = (size_t) PyByteArray_Size(stream); + } + else if (PyObject_HasAttrString(stream, "getvalue")) + { // we assume here that this delivers what we expect + mybytes = PyObject_CallMethod(stream, "getvalue", NULL); + c = PyBytes_AsString(mybytes); + len = (size_t) PyBytes_Size(mybytes); + } + // all the above leave c as NULL pointer if unsuccessful + if (c) res = fz_new_buffer_from_copied_data(ctx, c, len); + } + fz_always(ctx) + { + Py_CLEAR(mybytes); + PyErr_Clear(); } fz_catch(ctx) { @@ -7233,6 +7231,29 @@ JM_invert_pixmap_rect(fz_context *ctx, fz_pixmap *dest, fz_irect b) return 1; } +PyObject *JM_image_size(fz_context *ctx, PyObject *imagedata) +{ + fz_buffer *res = NULL; + fz_image *image = NULL; + PyObject *result = NULL; + fz_try(ctx) + { + res = JM_BufferFromBytes(ctx, imagedata); + image = fz_new_image_from_buffer(ctx, res); + result = Py_BuildValue("iiii", image->w, image->h, (int) image->n, (int) image->bpc); + } + fz_always(ctx) + { + fz_drop_buffer(ctx, res); + fz_drop_image(ctx, image); + } + fz_catch(ctx) + { + result = NONE; + } + return result; +} + //---------------------------------------------------------------------------- @@ -9298,6 +9319,7 @@ PyObject *JM_convert_to_pdf(fz_context *ctx, fz_document *doc, int fp, int tp, i // PDF created - now write it to Python bytearray PyObject *r = NULL; fz_output *out = NULL; + fz_buffer *res = NULL; // prepare write options structure int errors = 0; pdf_write_options opts = { 0 }; @@ -9316,14 +9338,20 @@ PyObject *JM_convert_to_pdf(fz_context *ctx, fz_document *doc, int fp, int tp, i opts.errors = &errors; fz_try(ctx) { - r = PyByteArray_FromStringAndSize("", 0); - out = JM_OutFromBarray(gctx, r); + res = fz_new_buffer(ctx, 8192); + out = fz_new_output_with_buffer(ctx, res); pdf_write_document(ctx, pdfout, out, &opts); + char *c = NULL; + size_t len = fz_buffer_storage(gctx, res, &c); + r = PyBytes_FromStringAndSize(c, (Py_ssize_t) len); + } + fz_always(ctx) + { + fz_drop_output(ctx, out); + fz_drop_buffer(ctx, res); } - fz_always(ctx) fz_drop_output(ctx, out); fz_catch(ctx) { - Py_CLEAR(r); fz_rethrow(ctx); } return r; @@ -9573,8 +9601,9 @@ SWIGINTERN struct fz_document_s *new_fz_document_s(char const *filename,PyObject gctx->error->errcode = 0; // reset any error code gctx->error->message[0] = 0; // reset any error message struct fz_document_s *doc = NULL; + char *c = NULL; + size_t len = 0; fz_stream *data = NULL; - char *streamdata; float w = width, h = height; fz_rect r = JM_rect_from_py(rect); if (!(fz_is_empty_rect(r) && !fz_is_infinite_rect(r))) @@ -9583,12 +9612,13 @@ SWIGINTERN struct fz_document_s *new_fz_document_s(char const *filename,PyObject h = r.y1 - r.y0; } - size_t streamlen = JM_CharFromBytesOrArray(stream, &streamdata); fz_try(gctx) { - if (streamlen > 0) + if (stream != NONE) // stream given, MUST be bytes! { - data = fz_open_memory(gctx, streamdata, streamlen); + c = PyBytes_AsString(stream); // just a pointer, no new obj + len = (size_t) PyBytes_Size(stream); + data = fz_open_memory(gctx, c, len); char *magic = (char *)filename; if (!magic) magic = (char *)filetype; doc = fz_open_document_with_stream(gctx, magic, data); @@ -9857,19 +9887,20 @@ SWIGINTERN PyObject *fz_document_s_embeddedFileUpd(struct fz_document_s *self,Py pdf_obj *entry = pdf_portfolio_entry_obj(gctx, pdf, n); pdf_obj *filespec = pdf_dict_getl(gctx, entry, PDF_NAME(EF), PDF_NAME(F), NULL); + if (!filespec) THROWMSG("bad PDF: /EF object not found"); - char *data = NULL; - size_t len = JM_CharFromBytesOrArray(buffer, &data); - if (len > 0) + res = JM_BufferFromBytes(gctx, buffer); + if (buffer && !res) THROWMSG("'buffer' has bad type"); + if (res) { - if (!filespec) THROWMSG("/EF object not found"); - res = fz_new_buffer_from_copied_data(gctx, data, len); JM_update_stream(gctx, pdf, filespec, res); // adjust /DL and /Size parameters - pdf_obj *l = pdf_new_int(gctx, (int64_t) len); + int64_t len = (int64_t) fz_buffer_storage(gctx, res, NULL); + pdf_obj *l = pdf_new_int(gctx, len); pdf_dict_put(gctx, filespec, PDF_NAME(DL), l); pdf_dict_putl(gctx, filespec, l, PDF_NAME(Params), PDF_NAME(Size), NULL); } + if (filename) pdf_dict_put_text_string(gctx, entry, PDF_NAME(F), filename); @@ -9921,8 +9952,9 @@ SWIGINTERN PyObject *fz_document_s_embeddedFileAdd(struct fz_document_s *self,Py fz_try(gctx) { assert_PDF(pdf); - size = JM_CharFromBytesOrArray(buffer, &buffdata); - if (size < 1) THROWMSG("buffer not bytes / bytearray"); + data = JM_BufferFromBytes(gctx, buffer); + if (!data) THROWMSG("bad type 'buffer'"); + size = fz_buffer_storage(gctx, data, &buffdata); // we do not allow duplicate names entry = JM_find_embedded(gctx, Py_BuildValue("s", name), pdf); @@ -9955,7 +9987,6 @@ SWIGINTERN PyObject *fz_document_s_embeddedFileAdd(struct fz_document_s *self,Py // (2) insert the real file contents pdf_obj *filespec = pdf_dict_getl(gctx, o, PDF_NAME(EF), PDF_NAME(F), NULL); - data = fz_new_buffer_from_copied_data(gctx, buffdata, size); JM_update_stream(gctx, pdf, filespec, data); // finally update some size attributes pdf_obj *l = pdf_new_int(gctx, (int64_t) size); @@ -10327,7 +10358,7 @@ SWIGINTERN PyObject *fz_document_s_insertPDF(struct fz_document_s *self,struct f } SWIGINTERN PyObject *fz_document_s__newPage(struct fz_document_s *self,int pno,float width,float height){ pdf_document *pdf = pdf_specifics(gctx, self); - fz_rect mediabox = { 0, 0, 595, 842 }; // ISO-A4 portrait values + fz_rect mediabox = fz_unit_rect; mediabox.x1 = width; mediabox.y1 = height; pdf_obj *resources = NULL, *page_obj = NULL; @@ -10335,7 +10366,7 @@ SWIGINTERN PyObject *fz_document_s__newPage(struct fz_document_s *self,int pno,f fz_try(gctx) { assert_PDF(pdf); - if (pno < -1) THROWMSG("invalid page number(s)"); + if (pno < -1) THROWMSG("bad page number(s)"); // create /Resources and /Contents objects resources = pdf_add_object_drop(gctx, pdf, pdf_new_dict(gctx, pdf, 1)); page_obj = pdf_add_page(gctx, pdf, mediabox, 0, resources, contents); @@ -10458,7 +10489,7 @@ SWIGINTERN PyObject *fz_document_s__getPageObjNumber(struct fz_document_s *self, fz_var(pageref); fz_try(gctx) { - if (n >= pageCount) THROWMSG("invalid page number(s)"); + if (n >= pageCount) THROWMSG("bad page number(s)"); assert_PDF(pdf); pageref = pdf_lookup_page_obj(gctx, pdf, n); } @@ -10477,7 +10508,7 @@ SWIGINTERN PyObject *fz_document_s__getPageInfo(struct fz_document_s *self,int p fz_var(liste); fz_try(gctx) { - if (n >= pageCount) THROWMSG("invalid page number(s)"); + if (n >= pageCount) THROWMSG("bad page number(s)"); assert_PDF(pdf); pageref = pdf_lookup_page_obj(gctx, pdf, n); rsrc = pdf_dict_get(gctx, pageref, PDF_NAME(Resources)); @@ -11199,11 +11230,11 @@ SWIGINTERN struct fz_annot_s *fz_page_s_addFileAnnot(struct fz_page_s *self,PyOb fz_try(gctx) { assert_PDF(page); + filebuf = JM_BufferFromBytes(gctx, buffer); + if (!filebuf) THROWMSG("bad 'buffer' data"); annot = pdf_create_annot(gctx, page, ANNOT_FILEATTACHMENT); pdf_set_annot_rect(gctx, annot, r); pdf_set_annot_icon_name(gctx, annot, "PushPin"); - len = JM_CharFromBytesOrArray(buffer, &data); - filebuf = fz_new_buffer_from_shared_data(gctx, data, len); pdf_obj *val = JM_embed_file(gctx, page->doc, filebuf, filename, uf, d); pdf_dict_put(gctx, annot->obj, PDF_NAME(FS), val); @@ -11679,11 +11710,9 @@ SWIGINTERN PyObject *fz_page_s__insertImage(struct fz_page_s *self,char const *f fz_pixmap *pix = NULL; fz_image *mask = NULL; fz_separations *seps = NULL; - pdf_obj *resources, *subres, *ref; + pdf_obj *resources, *xobject, *ref; fz_buffer *res = NULL, *nres = NULL, *imgbuf = NULL; fz_matrix mat = JM_matrix_from_py(matrix); // pre-calculated - char *streamdata = NULL; - size_t streamlen = JM_CharFromBytesOrArray(stream, &streamdata); const char *template = " q %g %g %g %g %g %g cm /%s Do Q "; char *cont = NULL; @@ -11691,33 +11720,23 @@ SWIGINTERN PyObject *fz_page_s__insertImage(struct fz_page_s *self,char const *f fz_image *zimg = NULL, *image = NULL; fz_try(gctx) { - pdf = page->doc; - - // get objects "Resources" & "XObject" - resources = pdf_dict_get(gctx, page->obj, PDF_NAME(Resources)); - subres = pdf_dict_get(gctx, resources, PDF_NAME(XObject)); - if (!subres) // has no XObject yet, create one - { - subres = pdf_new_dict(gctx, pdf, 10); - pdf_dict_putl_drop(gctx, page->obj, subres, PDF_NAME(Resources), PDF_NAME(XObject), NULL); - } - + //------------------------------------------------------------- // create the image - if (filename || streamlen > 0) + //------------------------------------------------------------- + if (filename || stream) { - if (!streamlen) + if (filename) image = fz_new_image_from_file(gctx, filename); else { - imgbuf = fz_new_buffer_from_copied_data(gctx, - streamdata, streamlen); + imgbuf = JM_BufferFromBytes(gctx, stream); image = fz_new_image_from_buffer(gctx, imgbuf); } - // test alpha channel (would require SMask creation) + // test for alpha (which would require making an SMask) pix = fz_get_pixmap_from_image(gctx, image, NULL, NULL, 0, 0); if (pix->alpha == 1) - { // have alpha, therefore create a mask + { // have alpha: create an SMask pm = fz_convert_pixmap(gctx, pix, NULL, NULL, NULL, NULL, 1); pm->alpha = 0; pm->colorspace = fz_keep_colorspace(gctx, fz_device_gray(gctx)); @@ -11733,7 +11752,7 @@ SWIGINTERN PyObject *fz_page_s__insertImage(struct fz_page_s *self,char const *f if (pixmap->alpha == 0) image = fz_new_image_from_pixmap(gctx, pixmap, NULL); else - { // pixmap has alpha, therefore create an SMask + { // pixmap has alpha: create an SMask pm = fz_convert_pixmap(gctx, pixmap, NULL, NULL, NULL, NULL, 1); pm->alpha = 0; pm->colorspace = fz_keep_colorspace(gctx, fz_device_gray(gctx)); @@ -11741,11 +11760,25 @@ SWIGINTERN PyObject *fz_page_s__insertImage(struct fz_page_s *self,char const *f image = fz_new_image_from_pixmap(gctx, pixmap, mask); } } + + //------------------------------------------------------------- // image created - now put it in the PDF + //------------------------------------------------------------- + pdf = page->doc; // owning PDF + + // get /Resources, /XObject + resources = pdf_dict_get(gctx, page->obj, PDF_NAME(Resources)); + xobject = pdf_dict_get(gctx, resources, PDF_NAME(XObject)); + if (!xobject) // has no XObject yet, create one + { + xobject = pdf_new_dict(gctx, pdf, 10); + pdf_dict_putl_drop(gctx, page->obj, xobject, PDF_NAME(Resources), PDF_NAME(XObject), NULL); + } + ref = pdf_add_image(gctx, pdf, image, 0); - pdf_dict_puts(gctx, subres, _imgname, ref); // store ref-name + pdf_dict_puts(gctx, xobject, _imgname, ref); // update XObject - // prep contents stream buffer with invoking command + // make contents stream that invokes the image nres = fz_new_buffer(gctx, 50); fz_append_printf(gctx, nres, template, mat.a, mat.b, mat.c, mat.d, mat.e, mat.f, @@ -11770,6 +11803,7 @@ SWIGINTERN PyObject *fz_page_s__insertFont(struct fz_page_s *self,char *fontname pdf_document *pdf; pdf_obj *resources, *fonts, *font_obj; fz_font *font; + fz_buffer *res = NULL; char *data = NULL; int size, ixref = 0, index = 0, simple = 0; PyObject *value; @@ -11817,9 +11851,9 @@ SWIGINTERN PyObject *fz_page_s__insertFont(struct fz_page_s *self,char *fontname font = fz_new_font_from_file(gctx, NULL, fontfile, idx, 0); else { - size = (int) JM_CharFromBytesOrArray(fontbuffer, &data); - if (!size) THROWMSG("one of fontfile, fontbuffer must be given"); - font = fz_new_font_from_memory(gctx, NULL, data, size, idx, 0); + res = JM_BufferFromBytes(gctx, fontbuffer); + if (!res) THROWMSG("one of fontfile, fontbuffer must be given"); + font = fz_new_font_from_buffer(gctx, NULL, res, idx, 0); } if (!set_simple) @@ -11861,6 +11895,10 @@ SWIGINTERN PyObject *fz_page_s__insertFont(struct fz_page_s *self,char *fontname pdf_drop_obj(gctx, font_obj); fz_drop_font(gctx, font); } + fz_always(gctx) + { + fz_drop_buffer(gctx, res); + } fz_catch(gctx) return NULL; pdf->dirty = 1; return value; @@ -12004,51 +12042,64 @@ SWIGINTERN struct fz_pixmap_s *new_fz_pixmap_s__SWIG_3(struct fz_pixmap_s *spix, return pm; } SWIGINTERN struct fz_pixmap_s *new_fz_pixmap_s__SWIG_4(struct fz_colorspace_s *cs,int w,int h,PyObject *samples,int alpha){ - char *data = NULL; int n = fz_colorspace_n(gctx, cs); int stride = (n + alpha)*w; fz_separations *seps = NULL; + fz_buffer *res = NULL; fz_pixmap *pm = NULL; - size_t size = JM_CharFromBytesOrArray(samples, &data); fz_try(gctx) { - if (size < 1) THROWMSG("invalid arg type samples"); - if (stride * h != size) THROWMSG("invalid arg len samples"); - pm = fz_new_pixmap_with_data(gctx, cs, w, h, seps, alpha, stride, data); + size_t size = 0; + unsigned char *c = NULL; + res = JM_BufferFromBytes(gctx, samples); + if (!res) THROWMSG("bad samples data"); + size = fz_buffer_storage(gctx, res, &c); + if (stride * h != size) THROWMSG("bad samples length"); + pm = fz_new_pixmap(gctx, cs, w, h, seps, alpha); + memcpy(pm->samples, c, size); + } + fz_always(gctx) + { + fz_drop_buffer(gctx, res); + } + fz_catch(gctx) + { + return NULL; } - fz_catch(gctx) return NULL; return pm; } SWIGINTERN struct fz_pixmap_s *new_fz_pixmap_s__SWIG_5(char *filename){ fz_image *img = NULL; fz_pixmap *pm = NULL; fz_try(gctx) { - if (!filename) THROWMSG("invalid argument type"); img = fz_new_image_from_file(gctx, filename); pm = fz_get_pixmap_from_image(gctx, img, NULL, NULL, NULL, NULL); } - fz_always(gctx) fz_drop_image(gctx, img); - fz_catch(gctx) return NULL; + fz_always(gctx) + { + fz_drop_image(gctx, img); + } + fz_catch(gctx) + { + return NULL; + } return pm; } SWIGINTERN struct fz_pixmap_s *new_fz_pixmap_s__SWIG_6(PyObject *imagedata){ - size_t size = 0; - char *streamdata; - fz_buffer *data = NULL; + fz_buffer *res = NULL; fz_image *img = NULL; fz_pixmap *pm = NULL; fz_try(gctx) { - size = JM_CharFromBytesOrArray(imagedata, &streamdata); - if (size < 1) THROWMSG("bad image data"); - data = fz_new_buffer_from_shared_data(gctx, - streamdata, size); - img = fz_new_image_from_buffer(gctx, data); + res = JM_BufferFromBytes(gctx, imagedata); + if (!res) THROWMSG("bad image data"); + img = fz_new_image_from_buffer(gctx, res); pm = fz_get_pixmap_from_image(gctx, img, NULL, NULL, NULL, NULL); } fz_always(gctx) { fz_drop_image(gctx, img); + fz_drop_buffer(gctx, res); } fz_catch(gctx) return NULL; return pm; @@ -12125,6 +12176,7 @@ SWIGINTERN PyObject *fz_pixmap_s_copyPixmap(struct fz_pixmap_s *self,struct fz_p return NONE; } SWIGINTERN PyObject *fz_pixmap_s_setAlpha(struct fz_pixmap_s *self,PyObject *alphavalues){ + fz_buffer *res = NULL; fz_try(gctx) { if (self->alpha == 0) THROWMSG("pixmap has no alpha"); @@ -12136,9 +12188,14 @@ SWIGINTERN PyObject *fz_pixmap_s_setAlpha(struct fz_pixmap_s *self,PyObject *alp int data_len = 0; if (alphavalues) { - data_len = (int) JM_CharFromBytesOrArray(alphavalues, &data); - if (data_len && data_len < w * h) - THROWMSG("not enough alpha values"); + res = JM_BufferFromBytes(gctx, alphavalues); + if (res) + { + data_len = (int) fz_buffer_storage(gctx, res, &data); + if (data && data_len < w * h) + THROWMSG("not enough alpha values"); + } + else THROWMSG("bad type 'alphavalues'"); } int i = 0, k = 0; while (i < balen) @@ -12149,7 +12206,14 @@ SWIGINTERN PyObject *fz_pixmap_s_setAlpha(struct fz_pixmap_s *self,PyObject *alp k += 1; } } - fz_catch(gctx) return NULL; + fz_always(gctx) + { + fz_drop_buffer(gctx, res); + } + fz_catch(gctx) + { + return NULL; + } return NONE; } SWIGINTERN PyObject *fz_pixmap_s__getImageData(struct fz_pixmap_s *self,int format){ @@ -12464,10 +12528,8 @@ SWIGINTERN PyObject *fz_annot_s__setAP(struct fz_annot_s *self,PyObject *ap,int if (!apobj) THROWMSG("annot has no /AP/N object"); if (!pdf_is_stream(gctx, apobj)) THROWMSG("/AP/N object is no stream"); - char *c = NULL; - size_t len = JM_CharFromBytesOrArray(ap, &c); - if (!c) THROWMSG("invalid /AP stream argument"); - res = fz_new_buffer_from_copied_data(gctx, c, strlen(c)); + res = JM_BufferFromBytes(gctx, ap); + if (!res) THROWMSG("invalid /AP stream argument"); JM_update_stream(gctx, annot->page->doc, apobj, res); if (rect) { @@ -12833,41 +12895,43 @@ SWIGINTERN PyObject *fz_annot_s_fileUpd(struct fz_annot_s *self,PyObject *buffer fz_try(gctx) { assert_PDF(annot); // must be a PDF - pdf = annot->page->doc; // this is the PDF + pdf = annot->page->doc; // the owning PDF int type = (int) pdf_annot_type(gctx, annot); if (type != 16) - THROWMSG("not a file attachment annot"); + THROWMSG("no FileAttachment annot"); stream = pdf_dict_getl(gctx, annot->obj, PDF_NAME(FS), PDF_NAME(EF), PDF_NAME(F), NULL); // the object for file content - if (!stream) THROWMSG("bad PDF: file entry not found"); + if (!stream) THROWMSG("bad PDF: /EF object not found"); fs = pdf_dict_get(gctx, annot->obj, PDF_NAME(FS)); - // file content is ignored if not bytes / bytearray - size = (int64_t) JM_CharFromBytesOrArray(buffer, &data); - if (size > 0) + // file content given + res = JM_BufferFromBytes(gctx, buffer); + if (buffer && !res) THROWMSG("'buffer' has bad type"); + if (res) { - pdf_obj *s = pdf_new_int(gctx, size); - pdf_dict_put(gctx, stream, PDF_NAME(Filter), - PDF_NAME(FlateDecode)); - - pdf_dict_putl_drop(gctx, stream, s, - PDF_NAME(Params), PDF_NAME(Size), NULL); - res = JM_deflatebuf(gctx, data, size); - pdf_update_stream(gctx, pdf, stream, res, 1); + JM_update_stream(gctx, pdf, stream, res); + // adjust /DL and /Size parameters + int64_t len = (int64_t) fz_buffer_storage(gctx, res, NULL); + pdf_obj *l = pdf_new_int(gctx, len); + pdf_dict_put(gctx, stream, PDF_NAME(DL), l); + pdf_dict_putl(gctx, stream, l, PDF_NAME(Params), PDF_NAME(Size), NULL); } if (filename) // new filename given { pdf_dict_put_text_string(gctx, stream, PDF_NAME(F), filename); pdf_dict_put_text_string(gctx, fs, PDF_NAME(F), filename); + pdf_dict_put_text_string(gctx, stream, PDF_NAME(UF), filename); + pdf_dict_put_text_string(gctx, fs, PDF_NAME(UF), filename); + pdf_dict_put_text_string(gctx, annot->obj, PDF_NAME(Contents), filename); } if (ufilename) { - pdf_dict_put_text_string(gctx, stream, PDF_NAME(UF), filename); - pdf_dict_put_text_string(gctx, fs, PDF_NAME(UF), filename); + pdf_dict_put_text_string(gctx, stream, PDF_NAME(UF), ufilename); + pdf_dict_put_text_string(gctx, fs, PDF_NAME(UF), ufilename); } if (desc) // new description given @@ -13495,6 +13559,9 @@ SWIGINTERN PyObject *Tools_store_shrink(struct Tools *self,int percent){ if (percent > 0) fz_shrink_store(gctx, 100 - percent); return Py_BuildValue("i", (int) gctx->store->size); } +SWIGINTERN PyObject *Tools_image_size(struct Tools *self,PyObject *imagedata){ + return JM_image_size(gctx, imagedata); + } SWIGINTERN PyObject *Tools_store_size(struct Tools *self){ return Py_BuildValue("i", (int) gctx->store->size); } @@ -21290,6 +21357,31 @@ SWIGINTERN PyObject *_wrap_Tools_store_shrink(PyObject *SWIGUNUSEDPARM(self), Py } +SWIGINTERN PyObject *_wrap_Tools_image_size(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { + PyObject *resultobj = 0; + struct Tools *arg1 = (struct Tools *) 0 ; + PyObject *arg2 = (PyObject *) 0 ; + void *argp1 = 0 ; + int res1 = 0 ; + PyObject * obj0 = 0 ; + PyObject * obj1 = 0 ; + PyObject *result = 0 ; + + if (!PyArg_ParseTuple(args,(char *)"OO:Tools_image_size",&obj0,&obj1)) SWIG_fail; + res1 = SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_Tools, 0 | 0 ); + if (!SWIG_IsOK(res1)) { + SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "Tools_image_size" "', argument " "1"" of type '" "struct Tools *""'"); + } + arg1 = (struct Tools *)(argp1); + arg2 = obj1; + result = (PyObject *)Tools_image_size(arg1,arg2); + resultobj = result; + return resultobj; +fail: + return NULL; +} + + SWIGINTERN PyObject *_wrap_Tools_store_size(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { PyObject *resultobj = 0; struct Tools *arg1 = (struct Tools *) 0 ; @@ -22027,7 +22119,7 @@ static PyMethodDef SwigMethods[] = { { (char *)"Annot_widget_choices", _wrap_Annot_widget_choices, METH_VARARGS, (char *)"Annot_widget_choices(self) -> PyObject *"}, { (char *)"Annot_fileInfo", _wrap_Annot_fileInfo, METH_VARARGS, (char *)"Retrieve attached file information."}, { (char *)"Annot_fileGet", _wrap_Annot_fileGet, METH_VARARGS, (char *)"Retrieve annotation attached file content."}, - { (char *)"Annot_fileUpd", _wrap_Annot_fileUpd, METH_VARARGS, (char *)"Update annotation attached file content."}, + { (char *)"Annot_fileUpd", _wrap_Annot_fileUpd, METH_VARARGS, (char *)"Update annotation attached file."}, { (char *)"Annot_info", _wrap_Annot_info, METH_VARARGS, (char *)"Annot_info(self) -> PyObject *"}, { (char *)"Annot_setInfo", _wrap_Annot_setInfo, METH_VARARGS, (char *)"Annot_setInfo(self, info) -> PyObject *"}, { (char *)"Annot_border", _wrap_Annot_border, METH_VARARGS, (char *)"Annot_border(self) -> PyObject *"}, @@ -22069,6 +22161,7 @@ static PyMethodDef SwigMethods[] = { { (char *)"Graftmap_swigregister", Graftmap_swigregister, METH_VARARGS, NULL}, { (char *)"Tools_gen_id", _wrap_Tools_gen_id, METH_VARARGS, (char *)"Return a unique positive integer."}, { (char *)"Tools_store_shrink", _wrap_Tools_store_shrink, METH_VARARGS, (char *)"Free 'percent' of current store size."}, + { (char *)"Tools_image_size", _wrap_Tools_image_size, METH_VARARGS, (char *)"Determine dimension and other image data."}, { (char *)"Tools_store_size", _wrap_Tools_store_size, METH_VARARGS, (char *)"Current store size."}, { (char *)"Tools_store_maxsize", _wrap_Tools_store_maxsize, METH_VARARGS, (char *)"Maximum store size."}, { (char *)"Tools_fitz_config", _wrap_Tools_fitz_config, METH_VARARGS, (char *)"Show configuration data."}, diff --git a/fitz/helper-convert.i b/fitz/helper-convert.i index 07efd5d6c..ba0982ed9 100644 --- a/fitz/helper-convert.i +++ b/fitz/helper-convert.i @@ -52,6 +52,7 @@ PyObject *JM_convert_to_pdf(fz_context *ctx, fz_document *doc, int fp, int tp, i // PDF created - now write it to Python bytearray PyObject *r = NULL; fz_output *out = NULL; + fz_buffer *res = NULL; // prepare write options structure int errors = 0; pdf_write_options opts = { 0 }; @@ -70,14 +71,20 @@ PyObject *JM_convert_to_pdf(fz_context *ctx, fz_document *doc, int fp, int tp, i opts.errors = &errors; fz_try(ctx) { - r = PyByteArray_FromStringAndSize("", 0); - out = JM_OutFromBarray(gctx, r); + res = fz_new_buffer(ctx, 8192); + out = fz_new_output_with_buffer(ctx, res); pdf_write_document(ctx, pdfout, out, &opts); + char *c = NULL; + size_t len = fz_buffer_storage(gctx, res, &c); + r = PyBytes_FromStringAndSize(c, (Py_ssize_t) len); + } + fz_always(ctx) + { + fz_drop_output(ctx, out); + fz_drop_buffer(ctx, res); } - fz_always(ctx) fz_drop_output(ctx, out); fz_catch(ctx) { - Py_CLEAR(r); fz_rethrow(ctx); } return r; diff --git a/fitz/helper-geo-py.i b/fitz/helper-geo-py.i index 161e9319e..22578d0db 100644 --- a/fitz/helper-geo-py.i +++ b/fitz/helper-geo-py.i @@ -63,7 +63,7 @@ class Matrix(): return self def preScale(self, sx, sy): - """Calculate pre scaling and replacing current matrix.""" + """Calculate pre scaling and replace current matrix.""" self.a *= sx self.b *= sx self.c *= sy diff --git a/fitz/helper-other.i b/fitz/helper-other.i index 115581e36..e74265485 100644 --- a/fitz/helper-other.i +++ b/fitz/helper-other.i @@ -173,9 +173,9 @@ void JM_color_FromSequence(PyObject *color, int *n, float col[4]) PyObject *JM_BinFromBuffer(fz_context *ctx, fz_buffer *buffer) { PyObject *bytes = PyBytes_FromString(""); - char *c = NULL; if (buffer) { + char *c = NULL; size_t len = fz_buffer_storage(gctx, buffer, &c); bytes = PyBytes_FromStringAndSize(c, (Py_ssize_t) len); } @@ -349,44 +349,42 @@ void hexlify(int n, unsigned char *in, unsigned char *out) } //---------------------------------------------------------------------------- -// Turn a bytes or bytearray object into char* string -// using the "_AsString" functions. Returns string size or 0 on error. -//---------------------------------------------------------------------------- -size_t JM_CharFromBytesOrArray(PyObject *stream, char **data) -{ - *data = NULL; - size_t len = 0; - if (!stream) return 0; - if (PyBytes_Check(stream)) - { - *data = PyBytes_AsString(stream); - len = (size_t) PyBytes_Size(stream); - } - else if (PyByteArray_Check(stream)) - { - *data = PyByteArray_AsString(stream); - len = (size_t) PyByteArray_Size(stream); - } - return len; -} - -//---------------------------------------------------------------------------- -// Return fz_buffer from a PyBytes or PyByteArray object -// Attention: must be freed by caller! +// Make fz_buffer from a PyBytes, PyByteArray, io.BytesIO object //---------------------------------------------------------------------------- fz_buffer *JM_BufferFromBytes(fz_context *ctx, PyObject *stream) { if (!stream) return NULL; + if (stream == NONE) return NULL; char *c = NULL; - size_t len = JM_CharFromBytesOrArray(stream, &c); - if (!c) return NULL; + PyObject *mybytes = NULL; + size_t len = 0; fz_buffer *res = NULL; fz_var(res); fz_try(ctx) { - res = fz_new_buffer(ctx, len); - fz_append_data(ctx, res, c, len); - fz_terminate_buffer(ctx, res); + if (PyBytes_Check(stream)) + { + c = PyBytes_AsString(stream); + len = (size_t) PyBytes_Size(stream); + } + else if (PyByteArray_Check(stream)) + { + c = PyByteArray_AS_STRING(stream); + len = (size_t) PyByteArray_Size(stream); + } + else if (PyObject_HasAttrString(stream, "getvalue")) + { // we assume here that this delivers what we expect + mybytes = PyObject_CallMethod(stream, "getvalue", NULL); + c = PyBytes_AsString(mybytes); + len = (size_t) PyBytes_Size(mybytes); + } + // all the above leave c as NULL pointer if unsuccessful + if (c) res = fz_new_buffer_from_copied_data(ctx, c, len); + } + fz_always(ctx) + { + Py_CLEAR(mybytes); + PyErr_Clear(); } fz_catch(ctx) { diff --git a/fitz/helper-pixmap.i b/fitz/helper-pixmap.i index 3876cd876..52a8552d9 100644 --- a/fitz/helper-pixmap.i +++ b/fitz/helper-pixmap.i @@ -126,4 +126,27 @@ JM_invert_pixmap_rect(fz_context *ctx, fz_pixmap *dest, fz_irect b) return 1; } +PyObject *JM_image_size(fz_context *ctx, PyObject *imagedata) +{ + fz_buffer *res = NULL; + fz_image *image = NULL; + PyObject *result = NULL; + fz_try(ctx) + { + res = JM_BufferFromBytes(ctx, imagedata); + image = fz_new_image_from_buffer(ctx, res); + result = Py_BuildValue("iiii", image->w, image->h, (int) image->n, (int) image->bpc); + } + fz_always(ctx) + { + fz_drop_buffer(ctx, res); + fz_drop_image(ctx, image); + } + fz_catch(ctx) + { + result = NONE; + } + return result; +} + %} \ No newline at end of file diff --git a/fitz/utils.py b/fitz/utils.py index 05ddf41e0..4e5fb77a6 100644 --- a/fitz/utils.py +++ b/fitz/utils.py @@ -1,6 +1,8 @@ from fitz import * import math +import os import warnings +import io warnings.simplefilter("once") """ The following is a collection of functions to extend PyMupdf. @@ -80,6 +82,8 @@ def calc_matrix(sr, tr, keep=True, rotate=0): if reuse_xref > 0: warnings.warn("ignoring 'reuse_xref'", DeprecationWarning) + while pno < 0: # support negative page numbers + pno += len(src) src_page = src[pno] # load ource page if len(src_page._getContents()) == 0: raise ValueError("nothing to show - source page empty") @@ -150,51 +154,112 @@ def insertImage(page, rect, filename=None, pixmap=None, stream=None, rotate=0, overlay: (bool) put in foreground """ + def calc_matrix(fw, fh, tr, rotate=0): + """ Calculate transformation matrix for image insertion. + + Notes: + The image will preserve its aspect ratio if and only if arguments + fw, fh are both equal to 1. + Args: + fw, fh: width / height ratio factors of image - floats in (0,1]. + At least one of them (corresponding to the longer side) is equal to 1. + tr: target rect in PDF coordinates + rotate: rotation angle in degrees + Returns: + Transformation matrix. + """ + # center point of target rect + tmp = Point((tr.x1 + tr.x0) / 2., (tr.y1 + tr.y0) / 2.) + + rot = Matrix(rotate) # rotation matrix + + # matrix m moves image center to (0, 0), then rotates + m = Matrix(1, 0, 0, 1, -0.5, -0.5) * rot + + #sr1 = sr * m # resulting image rect + + # -------------------------------------------------------------------- + # calculate the scale matrix + # -------------------------------------------------------------------- + small = min(fw, fh) # factor of the smaller side + + if rotate not in (0, 180): + fw, fh = fh, fw # width / height exchange their roles + + if fw < 1: # portrait + if (float(tr.width) / fw) > (float(tr.height) / fh): + w = tr.height * small + h = tr.height + else: + w = tr.width + h = tr.width / small + + elif fw != fh: # landscape + if (float(tr.width) / fw) > (float(tr.height) / fh): + w = tr.height / small + h = tr.height + else: + w = tr.width + h = tr.width * small + + else: # (treated as) equal sided + w = tr.width + h = tr.height + + m *= Matrix(w, h) # concat scale matrix + + m *= Matrix(1, 0, 0, 1, tmp.x, tmp.y) # concat move to target center + + return m + # ------------------------------------------------------------------------- + CheckParent(page) doc = page.parent if not doc.isPDF: raise ValueError("not a PDF") - if sum([bool(filename), bool(stream), bool(pixmap)]) != 1: + if bool(filename) + bool(stream) + bool(pixmap) != 1: raise ValueError("need exactly one of filename, pixmap, stream") + if filename and not os.path.exists(filename): + raise FileNotFoundError("No such file: '%s'" % filename) + elif stream and type(stream) not in (bytes, bytearray, io.BytesIO): + raise ValueError("stream must be bytes-like or BytesIO") + elif pixmap and type(pixmap) is not Pixmap: + raise ValueError("pixmap must be a Pixmap") + while rotate < 0: rotate += 360 while rotate > 360: rotate -= 360 - if rotate % 90 != 0: + if rotate not in (0, 90, 180, 270): raise ValueError("bad rotate value") r = page.rect & rect if r.isEmpty or r.isInfinite: raise ValueError("rect must be finite and not empty") - clip = r * ~page._getTransformation() # rect in PDF coordinates - - rot = Matrix(rotate) - - fw = r.width - fh = r.height - if keep_proportion: - fw = fh = min(fw, fh) - my = r.height - max(fw, fh) - mx = r.width - max(fw, fh) - else: - my = mx = 0 - - if rotate == 0: - m3 = Matrix(fw, fh) - m0 = Matrix(1, 0, 0, 1, clip.x0, clip.y0 + my) - elif rotate == 180: - m3 = Matrix(fw, fh) - m0 = Matrix(1, 0, 0, 1, clip.x1 - mx, clip.y1) - elif rotate == 90: - m3 = Matrix(fh, fw) - m0 = Matrix(1, 0, 0, 1, clip.x1 - mx, clip.y0 + my) + if keep_proportion is True: # for this we need the image dimension + if pixmap: # this is the easy case + w = pixmap.width + h = pixmap.height + elif stream: # use tool to access the information + w, h, _, _ = TOOLS.image_size(stream) + else: # worst case, we need to read the file ourselves + img = open(filename, "rb") + stream = img.read() # re-use this as parameter + w, h, _, _ = TOOLS.image_size(stream) + filename = None # prevent using this again + img.close() # close iamge file + + maxf = max(w, h).__float__() + fw = w / maxf + fh = h / maxf else: - m3 = Matrix(fh, fw) - m0 = Matrix(1, 0, 0, 1, clip.x0, clip.y1) + fw = fh = 1.0 + + clip = r * ~page._getTransformation() # target rect in PDF coordinates - matrix = m3 * rot * m0 + matrix = calc_matrix(fw, fh, clip, rotate=rotate) ilst = [i[7] for i in doc.getPageImageList(page.number)] n = "fzImg" diff --git a/fitz/version.i b/fitz/version.i index 34d831e87..4384a51f6 100644 --- a/fitz/version.i +++ b/fitz/version.i @@ -1,6 +1,6 @@ %pythoncode %{ VersionFitz = "1.14.0" -VersionBind = "1.14.12" -VersionDate = "2019-03-21 06:59:25" -version = (VersionBind, VersionFitz, "20190321065925") +VersionBind = "1.14.13" +VersionDate = "2019-04-07 06:43:20" +version = (VersionBind, VersionFitz, "20190407064320") %} \ No newline at end of file diff --git a/nano_setup.py b/nano_setup.py index 4b08385cf..ed1887b2a 100644 --- a/nano_setup.py +++ b/nano_setup.py @@ -60,7 +60,7 @@ setup( name="PyMuPDF", - version="1.14.12", + version="1.14.13", description="Python bindings for the PDF rendering library MuPDF", classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/setup.py b/setup.py index 29fb3cd54..5de1952e0 100644 --- a/setup.py +++ b/setup.py @@ -73,7 +73,7 @@ setup( name="PyMuPDF", - version="1.14.12", + version="1.14.13", description="Python bindings for the PDF rendering library MuPDF", long_description=long_desc, classifiers=classifier,