From 4e6782ce43cec8c474a9f8956ade2ad6ffe244d6 Mon Sep 17 00:00:00 2001 From: "Jorj X. McKie" Date: Tue, 18 Dec 2018 09:44:10 -0400 Subject: [PATCH 1/3] Fix issue #239 --- README.md | 4 +- fitz/fitz.i | 220 +++++++++++++++++++++--------------------- fitz/fitz.py | 133 ++++++++++++------------- fitz/fitz_wrap.c | 201 ++++++++++++++++++++++++-------------- fitz/helper-geo-py.i | 32 ++---- fitz/helper-python.i | 8 +- fitz/helper-xobject.i | 10 +- fitz/utils.py | 126 +++++++++++++----------- fitz/version.i | 6 +- setup.py | 2 +- 10 files changed, 397 insertions(+), 345 deletions(-) diff --git a/README.md b/README.md index 81139cbaf..5b9bef8dc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PyMuPDF 1.14.3 +# PyMuPDF 1.14.4 ![logo](https://github.com/rk700/PyMuPDF/blob/master/demo/pymupdf.jpg) @@ -14,7 +14,7 @@ On **[PyPI](https://pypi.org/project/PyMuPDF)** since August 2016: [![](https:// # Introduction -This is **version 1.14.3 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.4 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. diff --git a/fitz/fitz.i b/fitz/fitz.i index ddaa8fd2f..7d2501505 100644 --- a/fitz/fitz.i +++ b/fitz/fitz.i @@ -176,9 +176,8 @@ import os import weakref from binascii import hexlify import math -import platform -platform_bitness = platform.architecture()[0] -del platform + +fitz_py2 = str is bytes # if true, this is Python 2 %} %include version.i %include helper-geo-c.i @@ -215,7 +214,7 @@ struct fz_document_s if not filename or type(filename) is str: pass else: - if str is bytes: # Python 2 + if fitz_py2: # Python 2 if type(filename) is unicode: filename = filename.encode("utf8") else: @@ -1239,18 +1238,20 @@ if links: { int pageCount = fz_count_pages(gctx, $self); pdf_document *pdf = pdf_specifics(gctx, $self); + int n = pno; + while (n < 0) n += pageCount; + pdf_obj *pageref = NULL; + fz_var(pageref); fz_try(gctx) { - if (pno >= pageCount) THROWMSG("invalid page number(s)"); + if (n >= pageCount) THROWMSG("invalid page number(s)"); assert_PDF(pdf); + pageref = pdf_lookup_page_obj(gctx, pdf, n); } fz_catch(gctx) return NULL; - int n = pno; - while (n < 0) n += pageCount; - pdf_obj *pageref = pdf_lookup_page_obj(gctx, pdf, n); - long objnum = (long) pdf_to_num(gctx, pageref); - long objgen = (long) pdf_to_gen(gctx, pageref); - return Py_BuildValue("(l, l)", objnum, objgen); + + return Py_BuildValue("ii", pdf_to_num(gctx, pageref), + pdf_to_gen(gctx, pageref)); } FITZEXCEPTION(_getPageInfo, !result) @@ -2127,6 +2128,8 @@ struct fz_page_s { pdf_set_text_annot_position(gctx, annot, pos); pdf_set_annot_contents(gctx, annot, text); pdf_set_annot_icon_name(gctx, annot, "Note"); + float col[3] = {0.9f, 0.9f, 0.0f}; + pdf_set_annot_color(gctx, annot, 3, col); pdf_update_annot(gctx, annot); } fz_catch(gctx) return NULL; @@ -2144,15 +2147,16 @@ struct fz_page_s { pdf_annot *annot = NULL; PyObject *p = NULL, *sublist = NULL; pdf_obj *inklist = NULL, *stroke = NULL; - double x, y, height; - fz_rect prect; + fz_matrix ctm, inv_ctm; + double x, y; + fz_point point; fz_var(annot); fz_try(gctx) { assert_PDF(page); if (!PySequence_Check(list)) THROWMSG("arg must be a sequence"); - prect = pdf_bound_page(gctx, page); - height = prect.y1 - prect.y0; + pdf_page_transform(gctx, page, NULL, &ctm); + inv_ctm = fz_invert_matrix(ctm); annot = pdf_create_annot(gctx, page, PDF_ANNOT_INK); Py_ssize_t i, j, n0 = PySequence_Size(list), n1; inklist = pdf_new_array(gctx, annot->page->doc, n0); @@ -2173,8 +2177,9 @@ struct fz_page_s { if (PyErr_Occurred()) THROWMSG("invalid point coordinate"); Py_CLEAR(p); - pdf_array_push_real(gctx, stroke, x); - pdf_array_push_real(gctx, stroke, height - y); + point = fz_transform_point(fz_make_point(x, y), inv_ctm); + pdf_array_push_real(gctx, stroke, point.x); + pdf_array_push_real(gctx, stroke, point.y); } pdf_array_push_drop(gctx, inklist, stroke); stroke = NULL; @@ -2260,6 +2265,8 @@ struct fz_page_s { filename, uf, d); pdf_dict_put(gctx, annot->obj, PDF_NAME(FS), val); pdf_dict_put_text_string(gctx, annot->obj, PDF_NAME(Contents), filename); + float col[3] = {0.9f, 0.9f, 0.0f}; + pdf_set_annot_color(gctx, annot, 3, col); pdf_update_annot(gctx, annot); } fz_catch(gctx) return NULL; @@ -2561,7 +2568,7 @@ struct fz_page_s { { assert_PDF(page); fz_rect mediabox = pdf_bound_page(gctx, page); - pdf_obj *o = pdf_dict_get(gctx, page->obj, PDF_NAME(MediaBox)); + pdf_obj *o = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(MediaBox)); if (o) mediabox = pdf_to_rect(gctx, o); fz_rect cropbox = fz_empty_rect; fz_rect r = JM_rect_from_py(rect); @@ -2707,7 +2714,7 @@ fannot._erase() pdf_page *page = pdf_page_from_fz_page(gctx, $self); if (!page) return p; fz_rect r = fz_empty_rect; - pdf_obj *o = pdf_dict_get(gctx, page->obj, PDF_NAME(MediaBox)); + pdf_obj *o = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(MediaBox)); if (!o) return p; r = pdf_to_rect(gctx, o); @@ -2726,7 +2733,7 @@ fannot._erase() PyObject *p = JM_py_from_point(fz_make_point(0, 0)); pdf_page *page = pdf_page_from_fz_page(gctx, $self); if (!page) return p; // not a PDF - pdf_obj *o = pdf_dict_get(gctx, page->obj, PDF_NAME(CropBox)); + pdf_obj *o = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(CropBox)); if (!o) return p; // no CropBox specified fz_rect cbox = pdf_to_rect(gctx, o); return JM_py_from_point(fz_make_point(cbox.x0, cbox.y0));; @@ -2742,9 +2749,7 @@ fannot._erase() { pdf_page *page = pdf_page_from_fz_page(gctx, $self); if (!page) return -1; - pdf_obj *o = pdf_dict_get(gctx, page->obj, PDF_NAME(Rotate)); - if (!o) return 0; - return pdf_to_int(gctx, o); + return pdf_to_int(gctx, pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(Rotate))); } /*********************************************************************/ @@ -2759,7 +2764,7 @@ fannot._erase() { pdf_page *page = pdf_page_from_fz_page(gctx, $self); assert_PDF(page); - if (rot % 90) THROWMSG("rotate not 90 * int"); + if (rot % 90) THROWMSG("rotation not multiple of 90"); pdf_dict_put_int(gctx, page->obj, PDF_NAME(Rotate), (int64_t) rot); page->doc->dirty = 1; } @@ -2937,7 +2942,7 @@ fannot._erase() fz_matrix mat = fz_identity; fz_rect prect = fz_bound_page(gctx, $self); fz_rect r = fz_empty_rect; - o = pdf_dict_get(gctx, tpageref, PDF_NAME(CropBox)); + o = pdf_dict_get_inheritable(gctx, tpageref, PDF_NAME(CropBox)); if (o) { @@ -2945,7 +2950,7 @@ fannot._erase() prect.x0 = r.x0; prect.y0 = r.y0; } - o = pdf_dict_get(gctx, tpageref, PDF_NAME(MediaBox)); + o = pdf_dict_get_inheritable(gctx, tpageref, PDF_NAME(MediaBox)); if (o) { @@ -2995,7 +3000,7 @@ fannot._erase() if (!subres) // has no XObject dict yet: create one { subres = pdf_new_dict(gctx, pdfout, 10); - pdf_dict_put(gctx, resources, PDF_NAME(XObject), subres); + pdf_dict_putl(gctx, tpageref, subres, PDF_NAME(Resources), PDF_NAME(XObject), NULL); } pdf_dict_puts(gctx, subres, _imgname, xobj2); @@ -3042,7 +3047,7 @@ fannot._erase() pdf_obj *resources, *subres, *ref; fz_buffer *res = NULL, *nres = NULL, *imgbuf = NULL; - unsigned char *streamdata = NULL; + char *streamdata = NULL; size_t streamlen = JM_CharFromBytesOrArray(stream, &streamdata); const char *template = "\nq %g 0 0 %g %g %g cm /%s Do Q "; @@ -3067,14 +3072,14 @@ fannot._erase() fz_rect prect = fz_bound_page(gctx, $self); // page rectangle fz_rect r = fz_empty_rect; // modify where necessary - pdf_obj *o = pdf_dict_get(gctx, page->obj, PDF_NAME(CropBox)); + pdf_obj *o = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(CropBox)); if (o) { // set top-left of page rect to new values r = pdf_to_rect(gctx, o); prect.x0 = r.x0; prect.y0 = r.y0; } - o = pdf_dict_get(gctx, page->obj, PDF_NAME(MediaBox)); + o = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(MediaBox)); if (o) { // set bottom-right to new values r = pdf_to_rect(gctx, o); @@ -3095,7 +3100,7 @@ fannot._erase() if (!subres) // has no XObject yet, create one { subres = pdf_new_dict(gctx, pdf, 10); - pdf_dict_put_drop(gctx, resources, PDF_NAME(XObject), subres); + pdf_dict_putl_drop(gctx, page->obj, subres, PDF_NAME(Resources), PDF_NAME(XObject), NULL); } // create the image @@ -3235,9 +3240,9 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, pdf_document *pdf; pdf_obj *resources, *fonts, *font_obj; fz_font *font; - char *data = NULL; + const char *data = NULL; int size, ixref = 0, index = 0, simple = 0; - PyObject *info, *value; + PyObject *value; PyObject *exto = NULL; fz_try(gctx) { @@ -3249,7 +3254,7 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, if (!fonts) // page has no fonts yet { fonts = pdf_new_dict(gctx, pdf, 10); - pdf_dict_put_drop(gctx, resources, PDF_NAME(Font), fonts); + pdf_dict_putl_drop(gctx, page->obj, fonts, PDF_NAME(Resources), PDF_NAME(Font), NULL); } //------------------------------------------------------------- @@ -3331,11 +3336,27 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, return value; } + //--------------------------------------------------------------------- + // Get page transformation matrix + //--------------------------------------------------------------------- + PARENTCHECK(_getTransformation) + %pythonappend _getTransformation %{val = Matrix(val)%} + PyObject *_getTransformation() + { + fz_matrix ctm = fz_identity; + pdf_page *page = pdf_page_from_fz_page(gctx, $self); + if (!page) return JM_py_from_matrix(ctm); + fz_try(gctx) pdf_page_transform(gctx, page, NULL, &ctm); + fz_catch(gctx) {;} + return JM_py_from_matrix(ctm); + } + //--------------------------------------------------------------------- // Get list of contents objects //--------------------------------------------------------------------- FITZEXCEPTION(_getContents, !result) PARENTCHECK(_getContents) + %feature("autodoc","Return list of /Contents objects as xref integers.") _getContents; PyObject *_getContents() { pdf_page *page = pdf_page_from_fz_page(gctx, $self); @@ -3344,10 +3365,10 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, int i, xref; fz_try(gctx) { - assert_PDF(page); - list = PyList_New(0); + assert_PDF(page); // only works for PDF contents = pdf_dict_get(gctx, page->obj, PDF_NAME(Contents)); - if (pdf_is_array(gctx, contents)) + list = PyList_New(0); // init an empty list + if (pdf_is_array(gctx, contents)) // may be several { for (i=0; i < pdf_array_len(gctx, contents); i++) { icont = pdf_array_get(gctx, contents, i); @@ -3355,7 +3376,7 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, PyList_Append(list, Py_BuildValue("i", xref)); } } - else + else if (contents) // at most 1 object there { xref = pdf_to_num(gctx, contents); PyList_Append(list, Py_BuildValue("i", xref)); @@ -3366,7 +3387,7 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, } //--------------------------------------------------------------------- - // Set /Contents of a page to object given by xref + // Set given object to be the /Contents of a page //--------------------------------------------------------------------- FITZEXCEPTION(_setContents, !result) PARENTCHECK(_setContents) @@ -3378,12 +3399,15 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, fz_try(gctx) { - assert_PDF(page); + assert_PDF(page); // only works for PDF + if (!INRANGE(xref, 1, pdf_xref_len(gctx, page->doc) - 1)) THROWMSG("xref out of range"); + contents = pdf_new_indirect(gctx, page->doc, xref, 0); if (!pdf_is_stream(gctx, contents)) THROWMSG("xref is not a stream"); + pdf_dict_put_drop(gctx, page->obj, PDF_NAME(Contents), contents); } fz_catch(gctx) return NULL; @@ -4389,58 +4413,37 @@ struct fz_annot_s %pythoncode %{@property%} PyObject *vertices() { - PyObject *res, *list; pdf_annot *annot = pdf_annot_from_fz_annot(gctx, $self); if (!annot) return NONE; // not a PDF! - + PyObject *res = NONE; + pdf_obj *o; //---------------------------------------------------------------- // The following objects occur in different annotation types. // So we are sure that o != NULL occurs at most once. // Every pair of floats is one point, that needs to be separately - // transformed with the page's transformation matrix. + // transformed with the page transformation matrix. //---------------------------------------------------------------- - pdf_obj *o = pdf_dict_get(gctx, annot->obj, PDF_NAME(Vertices)); + o = pdf_dict_get(gctx, annot->obj, PDF_NAME(Vertices)); if (!o) o = pdf_dict_get(gctx, annot->obj, PDF_NAME(L)); if (!o) o = pdf_dict_get(gctx, annot->obj, PDF_NAME(QuadPoints)); if (!o) o = pdf_dict_gets(gctx, annot->obj, "CL"); - int i, j, n; - fz_point point; // point object to work with - fz_matrix page_ctm; // page transformation matrix - pdf_page_transform(gctx, annot->page, NULL, &page_ctm); + if (!o) o = pdf_dict_get(gctx, annot->obj, PDF_NAME(InkList)); - if (o) // anything found yet? + if (o) // anything found yet? { - res = PyList_New(0); // create Python list + int i, j, n; + fz_point point; // point object to work with + fz_matrix page_ctm; // page transformation matrix + pdf_page_transform(gctx, annot->page, NULL, &page_ctm); + res = PyList_New(0); // create Python list n = pdf_array_len(gctx, o); for (i = 0; i < n; i += 2) { point.x = pdf_to_real(gctx, pdf_array_get(gctx, o, i)); point.y = pdf_to_real(gctx, pdf_array_get(gctx, o, i+1)); - fz_transform_point(point, page_ctm); + point = fz_transform_point(point, page_ctm); PyList_Append(res, Py_BuildValue("ff", point.x, point.y)); } - return res; - } - // nothing found so far - maybe an Ink annotation? - pdf_obj *il_o = pdf_dict_get(gctx, annot->obj, PDF_NAME(InkList)); - if (!il_o) return NONE; // no inkList - res = PyList_New(0); // create result list - fz_rect prect = pdf_bound_page(gctx, annot->page); - double h = prect.y1 - prect.y0; - n = pdf_array_len(gctx, il_o); - for (i = 0; i < n; i++) - { - list = PyList_New(0); - o = pdf_array_get(gctx, il_o, i); - int m = pdf_array_len(gctx, o); - for (j = 0; j < m; j += 2) - { - point.x = pdf_to_real(gctx, pdf_array_get(gctx, o, j)); - point.y = pdf_to_real(gctx, pdf_array_get(gctx, o, j+1)); - PyList_Append(list, Py_BuildValue("ff", point.x, h - point.y)); - } - PyList_Append(res, list); - Py_CLEAR(list); } return res; } @@ -4500,7 +4503,7 @@ struct fz_annot_s col = " ".join(map(str, cs)) + app else: col = "%g" % cs + app - return bytes(col, "utf8") if str is not bytes else col + return bytes(col, "utf8") if not fitz_py2 else col type = self.type[0] # get the annot type dt = self.border["dashes"] # get the dashes spec @@ -4509,6 +4512,8 @@ struct fz_annot_s fill = self.colors["fill"] # get the fill color rect = None # used if we change the rect here bfill = color_string(fill, "f") + p_ctm = self.parent._getTransformation() # page transformation matrix + imat = ~p_ctm # inverse page transf. matrix line_end_le, line_end_ri = 0, 0 # line end codes if self.lineEnds: @@ -4575,7 +4580,11 @@ struct fz_annot_s ap = b"/Alp0 gs\n" + ap ap_updated = True - if line_end_le + line_end_ri > 0 and type in (ANNOT_POLYGON, ANNOT_POLYLINE): + #---------------------------------------------------------------------- + # the following handles line end symbols for 'Polygon' and 'Polyline + #---------------------------------------------------------------------- + if max(line_end_le, line_end_ri) > 0 and type in (ANNOT_POLYGON, ANNOT_POLYLINE): + le_funcs = (None, TOOLS._le_square, TOOLS._le_circle, TOOLS._le_diamond, TOOLS._le_openarrow, TOOLS._le_closedarrow, TOOLS._le_butt, @@ -4588,15 +4597,15 @@ struct fz_annot_s points = self.vertices ap = b"q\n" + ap + b"\nQ\n" if line_end_le in le_funcs_range: - p1 = Point(points[0]) - p2 = Point(points[1]) + p1 = Point(points[0]) * imat + p2 = Point(points[1]) * imat left = le_funcs[line_end_le](self, p1, p2, False) - ap += bytes(left, "utf8") if str is not bytes else left + ap += bytes(left, "utf8") if not fitz_py2 else left if line_end_ri in le_funcs_range: - p1 = Point(points[-2]) - p2 = Point(points[-1]) + p1 = Point(points[-2]) * imat + p2 = Point(points[-1]) * imat left = le_funcs[line_end_ri](self, p1, p2, True) - ap += bytes(left, "utf8") if str is not bytes else left + ap += bytes(left, "utf8") if not fitz_py2 else left if ap_updated: if rect: # rect modified here? @@ -5950,10 +5959,10 @@ struct fz_stext_page_s { class b64encode(json.JSONEncoder): def default(self,s): - if str is not bytes and type(s) is bytes: + if not fitz_py2 and type(s) is bytes: return base64.b64encode(s).decode() if type(s) is bytearray: - if str is bytes: + if fitz_py2: return base64.b64encode(s) else: return base64.b64encode(s).decode() @@ -6137,6 +6146,7 @@ struct Tools assert_PDF(page); contbuf = JM_BufferFromBytes(gctx, newcont); xref = JM_insert_contents(gctx, page->doc, page->obj, contbuf, overlay); + page->doc->dirty = 1; } fz_always(gctx) fz_drop_buffer(gctx, contbuf); fz_catch(gctx) return NULL; @@ -6202,6 +6212,13 @@ struct Tools JM_rect_from_py(r2))); } + %feature("autodoc","Concatenate matrices m1, m2.") _concat_matrix; + PyObject *_concat_matrix(PyObject *m1, PyObject *m2) + { + return JM_py_from_matrix(fz_concat(JM_matrix_from_py(m1), + JM_matrix_from_py(m2))); + } + %feature("autodoc","Invert a matrix.") _invert_matrix; PyObject *_invert_matrix(PyObject *matrix) { @@ -6231,7 +6248,6 @@ def _hor_matrix(self, C, P): S = (P - C).unit # unit vector C -> P return Matrix(1, 0, 0, 1, -C.x, -C.y) * Matrix(S.x, -S.y, S.y, S.x, 0, 0) - def _le_annot_parms(self, annot, p1, p2): """Get common parameters for making line end symbols. """ @@ -6243,7 +6259,6 @@ def _le_annot_parms(self, annot, p1, p2): if not fc: fc = (0,0,0) fcol = " ".join(map(str, fc)) + " rg\n" nr = annot.rect - h = nr.y1 np1 = p1 # point coord relative to annot rect np2 = p2 # point coord relative to annot rect m = self._hor_matrix(np1, np2) # matrix makes the line horizontal @@ -6254,10 +6269,9 @@ def _le_annot_parms(self, annot, p1, p2): opacity = "/Alp0 gs\n" else: opacity = "" - return m, im, L, R, w, h, scol, fcol, opacity - + return m, im, L, R, w, scol, fcol, opacity -def _oval_string(self, h, p1, p2, p3, p4): +def _oval_string(self, p1, p2, p3, p4): """Return /AP string defining an oval within a 4-polygon provided as points """ def bezier(p, q, r): @@ -6285,11 +6299,10 @@ def _oval_string(self, h, p1, p2, p3, p4): ap += bezier(ul1, ul2, ml) return ap - def _le_diamond(self, annot, p1, p2, lr): """Make stream commands for diamond line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 # 2*shift*width = length of square edge d = shift * max(1, w) M = R - (d/2., 0) if lr else L + (d/2., 0) @@ -6307,11 +6320,10 @@ def _le_diamond(self, annot, p1, p2, lr): ap += scol + fcol + "b\nQ\n" return ap - def _le_square(self, annot, p1, p2, lr): """Make stream commands for square line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 # 2*shift*width = length of square edge d = shift * max(1, w) M = R - (d/2., 0) if lr else L + (d/2., 0) @@ -6329,25 +6341,23 @@ def _le_square(self, annot, p1, p2, lr): ap += scol + fcol + "b\nQ\n" return ap - def _le_circle(self, annot, p1, p2, lr): """Make stream commands for circle line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 # 2*shift*width = length of square edge d = shift * max(1, w) M = R - (d/2., 0) if lr else L + (d/2., 0) r = Rect(M, M) + (-d, -d, d, d) # the square - ap = "q\n" + opacity + self._oval_string(h, r.tl * im, r.tr * im, r.br * im, r.bl * im) + ap = "q\n" + opacity + self._oval_string(r.tl * im, r.tr * im, r.br * im, r.bl * im) ap += "%g w\n" % w ap += scol + fcol + "b\nQ\n" return ap - def _le_butt(self, annot, p1, p2, lr): """Make stream commands for butt line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 3 d = shift * max(1, w) M = R if lr else L @@ -6358,12 +6368,11 @@ def _le_butt(self, annot, p1, p2, lr): ap += "%g w\n" % w ap += scol + "s\nQ\n" return ap - def _le_slash(self, annot, p1, p2, lr): """Make stream commands for slash line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) rw = 1.1547 * max(1, w) * 1.0 # makes rect diagonal a 30 deg inclination M = R if lr else L r = Rect(M.x - rw, M.y - 2 * w, M.x + rw, M.y + 2 * w) @@ -6375,11 +6384,10 @@ def _le_slash(self, annot, p1, p2, lr): ap += scol + "s\nQ\n" return ap - def _le_openarrow(self, annot, p1, p2, lr): """Make stream commands for open arrow line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 d = shift * max(1, w) p2 = R + (d/2., 0) if lr else L - (d/2., 0) @@ -6394,12 +6402,11 @@ def _le_openarrow(self, annot, p1, p2, lr): ap += "%g w\n" % w ap += scol + "S\nQ\n" return ap - def _le_closedarrow(self, annot, p1, p2, lr): """Make stream commands for closed arrow line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 d = shift * max(1, w) p2 = R + (d/2., 0) if lr else L - (d/2., 0) @@ -6414,12 +6421,11 @@ def _le_closedarrow(self, annot, p1, p2, lr): ap += "%g w\n" % w ap += scol + fcol + "b\nQ\n" return ap - def _le_ropenarrow(self, annot, p1, p2, lr): """Make stream commands for right open arrow line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 d = shift * max(1, w) p2 = R - (d/3., 0) if lr else L + (d/3., 0) @@ -6434,12 +6440,11 @@ def _le_ropenarrow(self, annot, p1, p2, lr): ap += "%g w\n" % w ap += scol + fcol + "S\nQ\n" return ap - def _le_rclosedarrow(self, annot, p1, p2, lr): """Make stream commands for right closed arrow line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 d = shift * max(1, w) p2 = R - (2*d, 0) if lr else L + (2*d, 0) @@ -6454,7 +6459,6 @@ def _le_rclosedarrow(self, annot, p1, p2, lr): ap += "%g w\n" % w ap += scol + fcol + "b\nQ\n" return ap - %} } }; diff --git a/fitz/fitz.py b/fitz/fitz.py index f935fd7c5..cb0d3d0c4 100644 --- a/fitz/fitz.py +++ b/fitz/fitz.py @@ -100,15 +100,14 @@ class _object: import weakref from binascii import hexlify import math -import platform -platform_bitness = platform.architecture()[0] -del platform + +fitz_py2 = str is bytes # if true, this is Python 2 VersionFitz = "1.14.0" -VersionBind = "1.14.3" -VersionDate = "2018-12-01 18:33:20" -version = (VersionBind, VersionFitz, "20181201183320") +VersionBind = "1.14.4" +VersionDate = "2018-12-18 08:56:44" +version = (VersionBind, VersionFitz, "20181218085644") class Matrix(): @@ -130,8 +129,8 @@ def __init__(self, *args): if len(args) == 1: # either an angle or a sequ if hasattr(args[0], "__float__"): theta = args[0] * math.pi / 180.0 - c = math.cos(theta) - s = math.sin(theta) + c = round(math.cos(theta), 10) + s = round(math.sin(theta), 10) self.a = self.d = c self.b = s self.c = -s @@ -240,19 +239,13 @@ def preRotate(self, theta): def concat(self, one, two): """Multiply two matrices and replace current one.""" - dst = Matrix() - dst.a = one[0] * two[0] + one[1] * two[2] - dst.b = one[0] * two[1] + one[1] * two[3] - dst.c = one[2] * two[0] + one[3] * two[2] - dst.d = one[2] * two[1] + one[3] * two[3] - dst.e = one[4] * two[0] + one[5] * two[2] + two[4] - dst.f = one[4] * two[1] + one[5] * two[3] + two[5] - self.a = dst.a - self.b = dst.b - self.c = dst.c - self.d = dst.d - self.e = dst.e - self.f = dst.f + dst = TOOLS._concat_matrix(one, two) + self.a = dst[0] + self.b = dst[1] + self.c = dst[2] + self.d = dst[3] + self.e = dst[4] + self.f = dst[5] return self def __getitem__(self, i): @@ -285,13 +278,7 @@ def __mul__(self, m): if hasattr(m, "__float__"): return Matrix(self.a * m, self.b * m, self.c * m, self.d * m, self.e * m, self.f * m) - a = self.a * m[0] + self.b * m[2] - b = self.a * m[1] + self.b * m[3] - c = self.c * m[0] + self.d * m[2] - d = self.c * m[1] + self.d * m[3] - e = self.e * m[0] + self.f * m[2] + m[4] - f = self.e * m[1] + self.f * m[3] + m[5] - return Matrix(a, b, c, d, e, f) + return self.concat(self, m) def __truediv__(self, m): if hasattr(m, "__float__"): @@ -1456,11 +1443,11 @@ def getPDFstr(x): # require full unicode: make a UTF-16BE hex string with BOM "feff" r = hexlify(bytearray([254, 255]) + bytearray(x, "UTF-16BE")) # r is 'bytes', so convert to 'str' if Python 3 - t = r if str is bytes else r.decode() + t = r if fitz_py2 else r.decode() return "<" + t + ">" # brackets indicate hex s = x.replace("\x00", " ") - if str is bytes: + if fitz_py2: if type(s) is str: s = unicode(s, "utf-8", "replace") @@ -1597,9 +1584,9 @@ def CheckMorph(o): if not bool(o): return False if not (type(o) in (list, tuple) and len(o) == 2): raise ValueError("morph must be a sequence of length 2") - if not (type(o[0]) == Point and issubclass(type(o[1]), Matrix)): + if not (len(o[0]) == 2 and len(o[1]) == 6): raise ValueError("invalid morph parm 0") - if not o[1].e == o[1].f == 0: + if not o[1][4] == o[1][5] == 0: raise ValueError("invalid morph parm 1") return True @@ -1718,7 +1705,7 @@ def __init__(self, filename=None, stream=None, filetype=None, rect=None, width=0 if not filename or type(filename) is str: pass else: - if str is bytes: # Python 2 + if fitz_py2: # Python 2 if type(filename) is unicode: filename = filename.encode("utf8") else: @@ -2968,8 +2955,18 @@ def _insertFont(self, fontname, bfname, fontfile, fontbuffer, set_simple, idx, w return _fitz.Page__insertFont(self, fontname, bfname, fontfile, fontbuffer, set_simple, idx, wmode, serif, encoding, ordering) + def _getTransformation(self): + """_getTransformation(self) -> PyObject *""" + CheckParent(self) + + val = _fitz.Page__getTransformation(self) + val = Matrix(val) + + return val + + def _getContents(self): - """_getContents(self) -> PyObject *""" + """Return list of /Contents objects as xref integers.""" CheckParent(self) return _fitz.Page__getContents(self) @@ -3551,7 +3548,7 @@ def color_string(cs, code): col = " ".join(map(str, cs)) + app else: col = "%g" % cs + app - return bytes(col, "utf8") if str is not bytes else col + return bytes(col, "utf8") if not fitz_py2 else col type = self.type[0] # get the annot type dt = self.border["dashes"] # get the dashes spec @@ -3560,6 +3557,8 @@ def color_string(cs, code): fill = self.colors["fill"] # get the fill color rect = None # used if we change the rect here bfill = color_string(fill, "f") + p_ctm = self.parent._getTransformation() # page transformation matrix + imat = ~p_ctm # inverse page transf. matrix line_end_le, line_end_ri = 0, 0 # line end codes if self.lineEnds: @@ -3626,7 +3625,11 @@ def color_string(cs, code): ap = b"/Alp0 gs\n" + ap ap_updated = True - if line_end_le + line_end_ri > 0 and type in (ANNOT_POLYGON, ANNOT_POLYLINE): + #---------------------------------------------------------------------- + # the following handles line end symbols for 'Polygon' and 'Polyline + #---------------------------------------------------------------------- + if max(line_end_le, line_end_ri) > 0 and type in (ANNOT_POLYGON, ANNOT_POLYLINE): + le_funcs = (None, TOOLS._le_square, TOOLS._le_circle, TOOLS._le_diamond, TOOLS._le_openarrow, TOOLS._le_closedarrow, TOOLS._le_butt, @@ -3639,15 +3642,15 @@ def color_string(cs, code): points = self.vertices ap = b"q\n" + ap + b"\nQ\n" if line_end_le in le_funcs_range: - p1 = Point(points[0]) - p2 = Point(points[1]) + p1 = Point(points[0]) * imat + p2 = Point(points[1]) * imat left = le_funcs[line_end_le](self, p1, p2, False) - ap += bytes(left, "utf8") if str is not bytes else left + ap += bytes(left, "utf8") if not fitz_py2 else left if line_end_ri in le_funcs_range: - p1 = Point(points[-2]) - p2 = Point(points[-1]) + p1 = Point(points[-2]) * imat + p2 = Point(points[-1]) * imat left = le_funcs[line_end_ri](self, p1, p2, True) - ap += bytes(left, "utf8") if str is not bytes else left + ap += bytes(left, "utf8") if not fitz_py2 else left if ap_updated: if rect: # rect modified here? @@ -4176,10 +4179,10 @@ def _extractText(self, format): class b64encode(json.JSONEncoder): def default(self,s): - if str is not bytes and type(s) is bytes: + if not fitz_py2 and type(s) is bytes: return base64.b64encode(s).decode() if type(s) is bytearray: - if str is bytes: + if fitz_py2: return base64.b64encode(s) else: return base64.b64encode(s).decode() @@ -4338,6 +4341,11 @@ def _union_rect(self, r1, r2): return _fitz.Tools__union_rect(self, r1, r2) + def _concat_matrix(self, m1, m2): + """Concatenate matrices m1, m2.""" + return _fitz.Tools__concat_matrix(self, m1, m2) + + def _invert_matrix(self, matrix): """Invert a matrix.""" return _fitz.Tools__invert_matrix(self, matrix) @@ -4350,7 +4358,6 @@ def _hor_matrix(self, C, P): S = (P - C).unit # unit vector C -> P return Matrix(1, 0, 0, 1, -C.x, -C.y) * Matrix(S.x, -S.y, S.y, S.x, 0, 0) - def _le_annot_parms(self, annot, p1, p2): """Get common parameters for making line end symbols. """ @@ -4362,7 +4369,6 @@ def _le_annot_parms(self, annot, p1, p2): if not fc: fc = (0,0,0) fcol = " ".join(map(str, fc)) + " rg\n" nr = annot.rect - h = nr.y1 np1 = p1 # point coord relative to annot rect np2 = p2 # point coord relative to annot rect m = self._hor_matrix(np1, np2) # matrix makes the line horizontal @@ -4373,10 +4379,9 @@ def _le_annot_parms(self, annot, p1, p2): opacity = "/Alp0 gs\n" else: opacity = "" - return m, im, L, R, w, h, scol, fcol, opacity + return m, im, L, R, w, scol, fcol, opacity - - def _oval_string(self, h, p1, p2, p3, p4): + def _oval_string(self, p1, p2, p3, p4): """Return /AP string defining an oval within a 4-polygon provided as points """ def bezier(p, q, r): @@ -4404,11 +4409,10 @@ def bezier(p, q, r): ap += bezier(ul1, ul2, ml) return ap - def _le_diamond(self, annot, p1, p2, lr): """Make stream commands for diamond line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 # 2*shift*width = length of square edge d = shift * max(1, w) M = R - (d/2., 0) if lr else L + (d/2., 0) @@ -4426,11 +4430,10 @@ def _le_diamond(self, annot, p1, p2, lr): ap += scol + fcol + "b\nQ\n" return ap - def _le_square(self, annot, p1, p2, lr): """Make stream commands for square line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 # 2*shift*width = length of square edge d = shift * max(1, w) M = R - (d/2., 0) if lr else L + (d/2., 0) @@ -4448,25 +4451,23 @@ def _le_square(self, annot, p1, p2, lr): ap += scol + fcol + "b\nQ\n" return ap - def _le_circle(self, annot, p1, p2, lr): """Make stream commands for circle line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 # 2*shift*width = length of square edge d = shift * max(1, w) M = R - (d/2., 0) if lr else L + (d/2., 0) r = Rect(M, M) + (-d, -d, d, d) # the square - ap = "q\n" + opacity + self._oval_string(h, r.tl * im, r.tr * im, r.br * im, r.bl * im) + ap = "q\n" + opacity + self._oval_string(r.tl * im, r.tr * im, r.br * im, r.bl * im) ap += "%g w\n" % w ap += scol + fcol + "b\nQ\n" return ap - def _le_butt(self, annot, p1, p2, lr): """Make stream commands for butt line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 3 d = shift * max(1, w) M = R if lr else L @@ -4478,11 +4479,10 @@ def _le_butt(self, annot, p1, p2, lr): ap += scol + "s\nQ\n" return ap - def _le_slash(self, annot, p1, p2, lr): """Make stream commands for slash line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) rw = 1.1547 * max(1, w) * 1.0 # makes rect diagonal a 30 deg inclination M = R if lr else L r = Rect(M.x - rw, M.y - 2 * w, M.x + rw, M.y + 2 * w) @@ -4494,11 +4494,10 @@ def _le_slash(self, annot, p1, p2, lr): ap += scol + "s\nQ\n" return ap - def _le_openarrow(self, annot, p1, p2, lr): """Make stream commands for open arrow line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 d = shift * max(1, w) p2 = R + (d/2., 0) if lr else L - (d/2., 0) @@ -4514,11 +4513,10 @@ def _le_openarrow(self, annot, p1, p2, lr): ap += scol + "S\nQ\n" return ap - def _le_closedarrow(self, annot, p1, p2, lr): """Make stream commands for closed arrow line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 d = shift * max(1, w) p2 = R + (d/2., 0) if lr else L - (d/2., 0) @@ -4534,11 +4532,10 @@ def _le_closedarrow(self, annot, p1, p2, lr): ap += scol + fcol + "b\nQ\n" return ap - def _le_ropenarrow(self, annot, p1, p2, lr): """Make stream commands for right open arrow line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 d = shift * max(1, w) p2 = R - (d/3., 0) if lr else L + (d/3., 0) @@ -4554,11 +4551,10 @@ def _le_ropenarrow(self, annot, p1, p2, lr): ap += scol + fcol + "S\nQ\n" return ap - def _le_rclosedarrow(self, annot, p1, p2, lr): """Make stream commands for right closed arrow line end symbol. "lr" denotes left (False) or right point. """ - m, im, L, R, w, h, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) + m, im, L, R, w, scol, fcol, opacity = self._le_annot_parms(annot, p1, p2) shift = 2.5 d = shift * max(1, w) p2 = R - (2*d, 0) if lr else L + (2*d, 0) @@ -4575,7 +4571,6 @@ def _le_rclosedarrow(self, annot, p1, p2, lr): return ap - def __init__(self): """__init__(self) -> Tools""" this = _fitz.new_Tools() diff --git a/fitz/fitz_wrap.c b/fitz/fitz_wrap.c index b5cceaa92..8c2193814 100644 --- a/fitz/fitz_wrap.c +++ b/fitz/fitz_wrap.c @@ -8723,12 +8723,12 @@ pdf_obj *JM_xobject_from_page(fz_context *ctx, pdf_document *pdfout, pdf_documen if (pno < 0 || pno >= pdf_count_pages(ctx, pdfsrc)) THROWMSG("invalid page number(s)"); spageref = pdf_lookup_page_obj(ctx, pdfsrc, pno); - pdf_obj *mb = pdf_dict_get(ctx, spageref, PDF_NAME(MediaBox)); + pdf_obj *mb = pdf_dict_get_inheritable(ctx, spageref, PDF_NAME(MediaBox)); if (mb) *mediabox = pdf_to_rect(ctx, mb); else *mediabox = pdf_bound_page(ctx, pdf_load_page(ctx, pdfsrc, pno)); - o = pdf_dict_get(ctx, spageref, PDF_NAME(CropBox)); + o = pdf_dict_get_inheritable(ctx, spageref, PDF_NAME(CropBox)); if (!o) { cropbox->x0 = mediabox->x0; @@ -8811,16 +8811,16 @@ int JM_insert_contents(fz_context *ctx, pdf_document *pdf, } else // make new array { - pdf_obj *carr = pdf_new_array(ctx, pdf, 2); + pdf_obj *carr = pdf_new_array(ctx, pdf, 5); if (overlay) { - pdf_array_push(ctx, carr, contents); + if (contents) pdf_array_push(ctx, carr, contents); pdf_array_push_drop(ctx, carr, newconts); } else { pdf_array_push_drop(ctx, carr, newconts); - pdf_array_push(ctx, carr, contents); + if (contents) pdf_array_push(ctx, carr, contents); } pdf_dict_put_drop(ctx, pageref, PDF_NAME(Contents), carr); } @@ -10341,18 +10341,20 @@ SWIGINTERN PyObject *fz_document_s__getCharWidths(struct fz_document_s *self,int SWIGINTERN PyObject *fz_document_s__getPageObjNumber(struct fz_document_s *self,int pno){ int pageCount = fz_count_pages(gctx, self); pdf_document *pdf = pdf_specifics(gctx, self); + int n = pno; + while (n < 0) n += pageCount; + pdf_obj *pageref = NULL; + fz_var(pageref); fz_try(gctx) { - if (pno >= pageCount) THROWMSG("invalid page number(s)"); + if (n >= pageCount) THROWMSG("invalid page number(s)"); assert_PDF(pdf); + pageref = pdf_lookup_page_obj(gctx, pdf, n); } fz_catch(gctx) return NULL; - int n = pno; - while (n < 0) n += pageCount; - pdf_obj *pageref = pdf_lookup_page_obj(gctx, pdf, n); - long objnum = (long) pdf_to_num(gctx, pageref); - long objgen = (long) pdf_to_gen(gctx, pageref); - return Py_BuildValue("(l, l)", objnum, objgen); + + return Py_BuildValue("ii", pdf_to_num(gctx, pageref), + pdf_to_gen(gctx, pageref)); } SWIGINTERN PyObject *fz_document_s__getPageInfo(struct fz_document_s *self,int pno,int what){ pdf_document *pdf = pdf_specifics(gctx, self); @@ -10921,6 +10923,8 @@ SWIGINTERN struct fz_annot_s *fz_page_s_addTextAnnot(struct fz_page_s *self,PyOb pdf_set_text_annot_position(gctx, annot, pos); pdf_set_annot_contents(gctx, annot, text); pdf_set_annot_icon_name(gctx, annot, "Note"); + float col[3] = {0.9f, 0.9f, 0.0f}; + pdf_set_annot_color(gctx, annot, 3, col); pdf_update_annot(gctx, annot); } fz_catch(gctx) return NULL; @@ -10932,15 +10936,16 @@ SWIGINTERN struct fz_annot_s *fz_page_s_addInkAnnot(struct fz_page_s *self,PyObj pdf_annot *annot = NULL; PyObject *p = NULL, *sublist = NULL; pdf_obj *inklist = NULL, *stroke = NULL; - double x, y, height; - fz_rect prect; + fz_matrix ctm, inv_ctm; + double x, y; + fz_point point; fz_var(annot); fz_try(gctx) { assert_PDF(page); if (!PySequence_Check(list)) THROWMSG("arg must be a sequence"); - prect = pdf_bound_page(gctx, page); - height = prect.y1 - prect.y0; + pdf_page_transform(gctx, page, NULL, &ctm); + inv_ctm = fz_invert_matrix(ctm); annot = pdf_create_annot(gctx, page, PDF_ANNOT_INK); Py_ssize_t i, j, n0 = PySequence_Size(list), n1; inklist = pdf_new_array(gctx, annot->page->doc, n0); @@ -10961,8 +10966,9 @@ SWIGINTERN struct fz_annot_s *fz_page_s_addInkAnnot(struct fz_page_s *self,PyObj if (PyErr_Occurred()) THROWMSG("invalid point coordinate"); Py_CLEAR(p); - pdf_array_push_real(gctx, stroke, x); - pdf_array_push_real(gctx, stroke, height - y); + point = fz_transform_point(fz_make_point(x, y), inv_ctm); + pdf_array_push_real(gctx, stroke, point.x); + pdf_array_push_real(gctx, stroke, point.y); } pdf_array_push_drop(gctx, inklist, stroke); stroke = NULL; @@ -11036,6 +11042,8 @@ SWIGINTERN struct fz_annot_s *fz_page_s_addFileAnnot(struct fz_page_s *self,PyOb filename, uf, d); pdf_dict_put(gctx, annot->obj, PDF_NAME(FS), val); pdf_dict_put_text_string(gctx, annot->obj, PDF_NAME(Contents), filename); + float col[3] = {0.9f, 0.9f, 0.0f}; + pdf_set_annot_color(gctx, annot, 3, col); pdf_update_annot(gctx, annot); } fz_catch(gctx) return NULL; @@ -11216,7 +11224,7 @@ SWIGINTERN PyObject *fz_page_s_setCropBox(struct fz_page_s *self,PyObject *rect) { assert_PDF(page); fz_rect mediabox = pdf_bound_page(gctx, page); - pdf_obj *o = pdf_dict_get(gctx, page->obj, PDF_NAME(MediaBox)); + pdf_obj *o = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(MediaBox)); if (o) mediabox = pdf_to_rect(gctx, o); fz_rect cropbox = fz_empty_rect; fz_rect r = JM_rect_from_py(rect); @@ -11288,7 +11296,7 @@ SWIGINTERN PyObject *fz_page_s_MediaBoxSize(struct fz_page_s *self){ pdf_page *page = pdf_page_from_fz_page(gctx, self); if (!page) return p; fz_rect r = fz_empty_rect; - pdf_obj *o = pdf_dict_get(gctx, page->obj, PDF_NAME(MediaBox)); + pdf_obj *o = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(MediaBox)); if (!o) return p; r = pdf_to_rect(gctx, o); @@ -11298,7 +11306,7 @@ SWIGINTERN PyObject *fz_page_s_CropBoxPosition(struct fz_page_s *self){ PyObject *p = JM_py_from_point(fz_make_point(0, 0)); pdf_page *page = pdf_page_from_fz_page(gctx, self); if (!page) return p; // not a PDF - pdf_obj *o = pdf_dict_get(gctx, page->obj, PDF_NAME(CropBox)); + pdf_obj *o = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(CropBox)); if (!o) return p; // no CropBox specified fz_rect cbox = pdf_to_rect(gctx, o); return JM_py_from_point(fz_make_point(cbox.x0, cbox.y0));; @@ -11306,16 +11314,14 @@ SWIGINTERN PyObject *fz_page_s_CropBoxPosition(struct fz_page_s *self){ SWIGINTERN int fz_page_s_rotation(struct fz_page_s *self){ pdf_page *page = pdf_page_from_fz_page(gctx, self); if (!page) return -1; - pdf_obj *o = pdf_dict_get(gctx, page->obj, PDF_NAME(Rotate)); - if (!o) return 0; - return pdf_to_int(gctx, o); + return pdf_to_int(gctx, pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(Rotate))); } SWIGINTERN PyObject *fz_page_s_setRotation(struct fz_page_s *self,int rot){ fz_try(gctx) { pdf_page *page = pdf_page_from_fz_page(gctx, self); assert_PDF(page); - if (rot % 90) THROWMSG("rotate not 90 * int"); + if (rot % 90) THROWMSG("rotation not multiple of 90"); pdf_dict_put_int(gctx, page->obj, PDF_NAME(Rotate), (int64_t) rot); page->doc->dirty = 1; } @@ -11466,7 +11472,7 @@ SWIGINTERN PyObject *fz_page_s__showPDFpage(struct fz_page_s *self,PyObject *rec fz_matrix mat = fz_identity; fz_rect prect = fz_bound_page(gctx, self); fz_rect r = fz_empty_rect; - o = pdf_dict_get(gctx, tpageref, PDF_NAME(CropBox)); + o = pdf_dict_get_inheritable(gctx, tpageref, PDF_NAME(CropBox)); if (o) { @@ -11474,7 +11480,7 @@ SWIGINTERN PyObject *fz_page_s__showPDFpage(struct fz_page_s *self,PyObject *rec prect.x0 = r.x0; prect.y0 = r.y0; } - o = pdf_dict_get(gctx, tpageref, PDF_NAME(MediaBox)); + o = pdf_dict_get_inheritable(gctx, tpageref, PDF_NAME(MediaBox)); if (o) { @@ -11524,7 +11530,7 @@ SWIGINTERN PyObject *fz_page_s__showPDFpage(struct fz_page_s *self,PyObject *rec if (!subres) // has no XObject dict yet: create one { subres = pdf_new_dict(gctx, pdfout, 10); - pdf_dict_put(gctx, resources, PDF_NAME(XObject), subres); + pdf_dict_putl(gctx, tpageref, subres, PDF_NAME(Resources), PDF_NAME(XObject), NULL); } pdf_dict_puts(gctx, subres, _imgname, xobj2); @@ -11553,7 +11559,7 @@ SWIGINTERN PyObject *fz_page_s_insertImage(struct fz_page_s *self,PyObject *rect pdf_obj *resources, *subres, *ref; fz_buffer *res = NULL, *nres = NULL, *imgbuf = NULL; - unsigned char *streamdata = NULL; + char *streamdata = NULL; size_t streamlen = JM_CharFromBytesOrArray(stream, &streamdata); const char *template = "\nq %g 0 0 %g %g %g cm /%s Do Q "; @@ -11578,14 +11584,14 @@ SWIGINTERN PyObject *fz_page_s_insertImage(struct fz_page_s *self,PyObject *rect fz_rect prect = fz_bound_page(gctx, self); // page rectangle fz_rect r = fz_empty_rect; // modify where necessary - pdf_obj *o = pdf_dict_get(gctx, page->obj, PDF_NAME(CropBox)); + pdf_obj *o = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(CropBox)); if (o) { // set top-left of page rect to new values r = pdf_to_rect(gctx, o); prect.x0 = r.x0; prect.y0 = r.y0; } - o = pdf_dict_get(gctx, page->obj, PDF_NAME(MediaBox)); + o = pdf_dict_get_inheritable(gctx, page->obj, PDF_NAME(MediaBox)); if (o) { // set bottom-right to new values r = pdf_to_rect(gctx, o); @@ -11606,7 +11612,7 @@ SWIGINTERN PyObject *fz_page_s_insertImage(struct fz_page_s *self,PyObject *rect if (!subres) // has no XObject yet, create one { subres = pdf_new_dict(gctx, pdf, 10); - pdf_dict_put_drop(gctx, resources, PDF_NAME(XObject), subres); + pdf_dict_putl_drop(gctx, page->obj, subres, PDF_NAME(Resources), PDF_NAME(XObject), NULL); } // create the image @@ -11672,9 +11678,9 @@ 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; - char *data = NULL; + const char *data = NULL; int size, ixref = 0, index = 0, simple = 0; - PyObject *info, *value; + PyObject *value; PyObject *exto = NULL; fz_try(gctx) { @@ -11686,7 +11692,7 @@ SWIGINTERN PyObject *fz_page_s__insertFont(struct fz_page_s *self,char *fontname if (!fonts) // page has no fonts yet { fonts = pdf_new_dict(gctx, pdf, 10); - pdf_dict_put_drop(gctx, resources, PDF_NAME(Font), fonts); + pdf_dict_putl_drop(gctx, page->obj, fonts, PDF_NAME(Resources), PDF_NAME(Font), NULL); } //------------------------------------------------------------- @@ -11767,6 +11773,14 @@ SWIGINTERN PyObject *fz_page_s__insertFont(struct fz_page_s *self,char *fontname pdf->dirty = 1; return value; } +SWIGINTERN PyObject *fz_page_s__getTransformation(struct fz_page_s *self){ + fz_matrix ctm = fz_identity; + pdf_page *page = pdf_page_from_fz_page(gctx, self); + if (!page) return JM_py_from_matrix(ctm); + fz_try(gctx) pdf_page_transform(gctx, page, NULL, &ctm); + fz_catch(gctx) {;} + return JM_py_from_matrix(ctm); + } SWIGINTERN PyObject *fz_page_s__getContents(struct fz_page_s *self){ pdf_page *page = pdf_page_from_fz_page(gctx, self); PyObject *list = NULL; @@ -11774,10 +11788,10 @@ SWIGINTERN PyObject *fz_page_s__getContents(struct fz_page_s *self){ int i, xref; fz_try(gctx) { - assert_PDF(page); - list = PyList_New(0); + assert_PDF(page); // only works for PDF contents = pdf_dict_get(gctx, page->obj, PDF_NAME(Contents)); - if (pdf_is_array(gctx, contents)) + list = PyList_New(0); // init an empty list + if (pdf_is_array(gctx, contents)) // may be several { for (i=0; i < pdf_array_len(gctx, contents); i++) { icont = pdf_array_get(gctx, contents, i); @@ -11785,7 +11799,7 @@ SWIGINTERN PyObject *fz_page_s__getContents(struct fz_page_s *self){ PyList_Append(list, Py_BuildValue("i", xref)); } } - else + else if (contents) // at most 1 object there { xref = pdf_to_num(gctx, contents); PyList_Append(list, Py_BuildValue("i", xref)); @@ -11800,12 +11814,15 @@ SWIGINTERN PyObject *fz_page_s__setContents(struct fz_page_s *self,int xref){ fz_try(gctx) { - assert_PDF(page); + assert_PDF(page); // only works for PDF + if (!INRANGE(xref, 1, pdf_xref_len(gctx, page->doc) - 1)) THROWMSG("xref out of range"); + contents = pdf_new_indirect(gctx, page->doc, xref, 0); if (!pdf_is_stream(gctx, contents)) THROWMSG("xref is not a stream"); + pdf_dict_put_drop(gctx, page->obj, PDF_NAME(Contents), contents); } fz_catch(gctx) return NULL; @@ -12270,58 +12287,37 @@ SWIGINTERN void fz_annot_s_setRect(struct fz_annot_s *self,PyObject *rect){ return; } SWIGINTERN PyObject *fz_annot_s_vertices(struct fz_annot_s *self){ - PyObject *res, *list; pdf_annot *annot = pdf_annot_from_fz_annot(gctx, self); if (!annot) return NONE; // not a PDF! - + PyObject *res = NONE; + pdf_obj *o; //---------------------------------------------------------------- // The following objects occur in different annotation types. // So we are sure that o != NULL occurs at most once. // Every pair of floats is one point, that needs to be separately - // transformed with the page's transformation matrix. + // transformed with the page transformation matrix. //---------------------------------------------------------------- - pdf_obj *o = pdf_dict_get(gctx, annot->obj, PDF_NAME(Vertices)); + o = pdf_dict_get(gctx, annot->obj, PDF_NAME(Vertices)); if (!o) o = pdf_dict_get(gctx, annot->obj, PDF_NAME(L)); if (!o) o = pdf_dict_get(gctx, annot->obj, PDF_NAME(QuadPoints)); if (!o) o = pdf_dict_gets(gctx, annot->obj, "CL"); - int i, j, n; - fz_point point; // point object to work with - fz_matrix page_ctm; // page transformation matrix - pdf_page_transform(gctx, annot->page, NULL, &page_ctm); + if (!o) o = pdf_dict_get(gctx, annot->obj, PDF_NAME(InkList)); - if (o) // anything found yet? + if (o) // anything found yet? { - res = PyList_New(0); // create Python list + int i, j, n; + fz_point point; // point object to work with + fz_matrix page_ctm; // page transformation matrix + pdf_page_transform(gctx, annot->page, NULL, &page_ctm); + res = PyList_New(0); // create Python list n = pdf_array_len(gctx, o); for (i = 0; i < n; i += 2) { point.x = pdf_to_real(gctx, pdf_array_get(gctx, o, i)); point.y = pdf_to_real(gctx, pdf_array_get(gctx, o, i+1)); - fz_transform_point(point, page_ctm); + point = fz_transform_point(point, page_ctm); PyList_Append(res, Py_BuildValue("ff", point.x, point.y)); } - return res; - } - // nothing found so far - maybe an Ink annotation? - pdf_obj *il_o = pdf_dict_get(gctx, annot->obj, PDF_NAME(InkList)); - if (!il_o) return NONE; // no inkList - res = PyList_New(0); // create result list - fz_rect prect = pdf_bound_page(gctx, annot->page); - double h = prect.y1 - prect.y0; - n = pdf_array_len(gctx, il_o); - for (i = 0; i < n; i++) - { - list = PyList_New(0); - o = pdf_array_get(gctx, il_o, i); - int m = pdf_array_len(gctx, o); - for (j = 0; j < m; j += 2) - { - point.x = pdf_to_real(gctx, pdf_array_get(gctx, o, j)); - point.y = pdf_to_real(gctx, pdf_array_get(gctx, o, j+1)); - PyList_Append(list, Py_BuildValue("ff", point.x, h - point.y)); - } - PyList_Append(res, list); - Py_CLEAR(list); } return res; } @@ -13310,6 +13306,7 @@ SWIGINTERN PyObject *Tools__insert_contents(struct Tools *self,struct fz_page_s assert_PDF(page); contbuf = JM_BufferFromBytes(gctx, newcont); xref = JM_insert_contents(gctx, page->doc, page->obj, contbuf, overlay); + page->doc->dirty = 1; } fz_always(gctx) fz_drop_buffer(gctx, contbuf); fz_catch(gctx) return NULL; @@ -13347,6 +13344,10 @@ SWIGINTERN PyObject *Tools__union_rect(struct Tools *self,PyObject *r1,PyObject return JM_py_from_rect(fz_union_rect(JM_rect_from_py(r1), JM_rect_from_py(r2))); } +SWIGINTERN PyObject *Tools__concat_matrix(struct Tools *self,PyObject *m1,PyObject *m2){ + return JM_py_from_matrix(fz_concat(JM_matrix_from_py(m1), + JM_matrix_from_py(m2))); + } SWIGINTERN PyObject *Tools__invert_matrix(struct Tools *self,PyObject *matrix){ fz_matrix src = JM_matrix_from_py(matrix); float a = src.a; @@ -17149,6 +17150,28 @@ SWIGINTERN PyObject *_wrap_Page__insertFont(PyObject *SWIGUNUSEDPARM(self), PyOb } +SWIGINTERN PyObject *_wrap_Page__getTransformation(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { + PyObject *resultobj = 0; + struct fz_page_s *arg1 = (struct fz_page_s *) 0 ; + void *argp1 = 0 ; + int res1 = 0 ; + PyObject * obj0 = 0 ; + PyObject *result = 0 ; + + if (!PyArg_ParseTuple(args,(char *)"O:Page__getTransformation",&obj0)) SWIG_fail; + res1 = SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_fz_page_s, 0 | 0 ); + if (!SWIG_IsOK(res1)) { + SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "Page__getTransformation" "', argument " "1"" of type '" "struct fz_page_s *""'"); + } + arg1 = (struct fz_page_s *)(argp1); + result = (PyObject *)fz_page_s__getTransformation(arg1); + resultobj = result; + return resultobj; +fail: + return NULL; +} + + SWIGINTERN PyObject *_wrap_Page__getContents(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { PyObject *resultobj = 0; struct fz_page_s *arg1 = (struct fz_page_s *) 0 ; @@ -21319,6 +21342,34 @@ SWIGINTERN PyObject *_wrap_Tools__union_rect(PyObject *SWIGUNUSEDPARM(self), PyO } +SWIGINTERN PyObject *_wrap_Tools__concat_matrix(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { + PyObject *resultobj = 0; + struct Tools *arg1 = (struct Tools *) 0 ; + PyObject *arg2 = (PyObject *) 0 ; + PyObject *arg3 = (PyObject *) 0 ; + void *argp1 = 0 ; + int res1 = 0 ; + PyObject * obj0 = 0 ; + PyObject * obj1 = 0 ; + PyObject * obj2 = 0 ; + PyObject *result = 0 ; + + if (!PyArg_ParseTuple(args,(char *)"OOO:Tools__concat_matrix",&obj0,&obj1,&obj2)) 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__concat_matrix" "', argument " "1"" of type '" "struct Tools *""'"); + } + arg1 = (struct Tools *)(argp1); + arg2 = obj1; + arg3 = obj2; + result = (PyObject *)Tools__concat_matrix(arg1,arg2,arg3); + resultobj = result; + return resultobj; +fail: + return NULL; +} + + SWIGINTERN PyObject *_wrap_Tools__invert_matrix(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { PyObject *resultobj = 0; struct Tools *arg1 = (struct Tools *) 0 ; @@ -21479,7 +21530,8 @@ static PyMethodDef SwigMethods[] = { { (char *)"Page__showPDFpage", _wrap_Page__showPDFpage, METH_VARARGS, (char *)"Page__showPDFpage(self, rect, docsrc, pno=0, overlay=1, keep_proportion=1, reuse_xref=0, clip=None, graftmap=None, _imgname=None) -> PyObject *"}, { (char *)"Page_insertImage", _wrap_Page_insertImage, METH_VARARGS, (char *)"Insert a new image into a rectangle."}, { (char *)"Page__insertFont", _wrap_Page__insertFont, METH_VARARGS, (char *)"Page__insertFont(self, fontname, bfname, fontfile, fontbuffer, set_simple, idx, wmode, serif, encoding, ordering) -> PyObject *"}, - { (char *)"Page__getContents", _wrap_Page__getContents, METH_VARARGS, (char *)"Page__getContents(self) -> PyObject *"}, + { (char *)"Page__getTransformation", _wrap_Page__getTransformation, METH_VARARGS, (char *)"Page__getTransformation(self) -> PyObject *"}, + { (char *)"Page__getContents", _wrap_Page__getContents, METH_VARARGS, (char *)"Return list of /Contents objects as xref integers."}, { (char *)"Page__setContents", _wrap_Page__setContents, METH_VARARGS, (char *)"Set the /Contents object in page definition"}, { (char *)"Page_swigregister", Page_swigregister, METH_VARARGS, NULL}, { (char *)"Pixmap_x_set", _wrap_Pixmap_x_set, METH_VARARGS, (char *)"Pixmap_x_set(self, x)"}, @@ -21628,6 +21680,7 @@ static PyMethodDef SwigMethods[] = { { (char *)"Tools__intersect_rect", _wrap_Tools__intersect_rect, METH_VARARGS, (char *)"Intersect two rectangles."}, { (char *)"Tools__include_point_in_rect", _wrap_Tools__include_point_in_rect, METH_VARARGS, (char *)"Include point in a rect."}, { (char *)"Tools__union_rect", _wrap_Tools__union_rect, METH_VARARGS, (char *)"Replace r1 with smallest rect containing both."}, + { (char *)"Tools__concat_matrix", _wrap_Tools__concat_matrix, METH_VARARGS, (char *)"Concatenate matrices m1, m2."}, { (char *)"Tools__invert_matrix", _wrap_Tools__invert_matrix, METH_VARARGS, (char *)"Invert a matrix."}, { (char *)"new_Tools", _wrap_new_Tools, METH_VARARGS, (char *)"new_Tools() -> Tools"}, { (char *)"delete_Tools", _wrap_delete_Tools, METH_VARARGS, (char *)"delete_Tools(self)"}, diff --git a/fitz/helper-geo-py.i b/fitz/helper-geo-py.i index 0629f2ffe..f080c61e8 100644 --- a/fitz/helper-geo-py.i +++ b/fitz/helper-geo-py.i @@ -18,8 +18,8 @@ class Matrix(): if len(args) == 1: # either an angle or a sequ if hasattr(args[0], "__float__"): theta = args[0] * math.pi / 180.0 - c = math.cos(theta) - s = math.sin(theta) + c = round(math.cos(theta), 10) + s = round(math.sin(theta), 10) self.a = self.d = c self.b = s self.c = -s @@ -128,19 +128,13 @@ class Matrix(): def concat(self, one, two): """Multiply two matrices and replace current one.""" - dst = Matrix() - dst.a = one[0] * two[0] + one[1] * two[2] - dst.b = one[0] * two[1] + one[1] * two[3] - dst.c = one[2] * two[0] + one[3] * two[2] - dst.d = one[2] * two[1] + one[3] * two[3] - dst.e = one[4] * two[0] + one[5] * two[2] + two[4] - dst.f = one[4] * two[1] + one[5] * two[3] + two[5] - self.a = dst.a - self.b = dst.b - self.c = dst.c - self.d = dst.d - self.e = dst.e - self.f = dst.f + dst = TOOLS._concat_matrix(one, two) + self.a = dst[0] + self.b = dst[1] + self.c = dst[2] + self.d = dst[3] + self.e = dst[4] + self.f = dst[5] return self def __getitem__(self, i): @@ -173,13 +167,7 @@ class Matrix(): if hasattr(m, "__float__"): return Matrix(self.a * m, self.b * m, self.c * m, self.d * m, self.e * m, self.f * m) - a = self.a * m[0] + self.b * m[2] - b = self.a * m[1] + self.b * m[3] - c = self.c * m[0] + self.d * m[2] - d = self.c * m[1] + self.d * m[3] - e = self.e * m[0] + self.f * m[2] + m[4] - f = self.e * m[1] + self.f * m[3] + m[5] - return Matrix(a, b, c, d, e, f) + return self.concat(self, m) def __truediv__(self, m): if hasattr(m, "__float__"): diff --git a/fitz/helper-python.i b/fitz/helper-python.i index 50a347232..48accb5ed 100644 --- a/fitz/helper-python.i +++ b/fitz/helper-python.i @@ -284,11 +284,11 @@ def getPDFstr(x): # require full unicode: make a UTF-16BE hex string with BOM "feff" r = hexlify(bytearray([254, 255]) + bytearray(x, "UTF-16BE")) # r is 'bytes', so convert to 'str' if Python 3 - t = r if str is bytes else r.decode() + t = r if fitz_py2 else r.decode() return "<" + t + ">" # brackets indicate hex s = x.replace("\x00", " ") - if str is bytes: + if fitz_py2: if type(s) is str: s = unicode(s, "utf-8", "replace") @@ -425,9 +425,9 @@ def CheckMorph(o): if not bool(o): return False if not (type(o) in (list, tuple) and len(o) == 2): raise ValueError("morph must be a sequence of length 2") - if not (type(o[0]) == Point and issubclass(type(o[1]), Matrix)): + if not (len(o[0]) == 2 and len(o[1]) == 6): raise ValueError("invalid morph parm 0") - if not o[1].e == o[1].f == 0: + if not o[1][4] == o[1][5] == 0: raise ValueError("invalid morph parm 1") return True diff --git a/fitz/helper-xobject.i b/fitz/helper-xobject.i index dad010344..fdd048d27 100644 --- a/fitz/helper-xobject.i +++ b/fitz/helper-xobject.i @@ -13,12 +13,12 @@ pdf_obj *JM_xobject_from_page(fz_context *ctx, pdf_document *pdfout, pdf_documen if (pno < 0 || pno >= pdf_count_pages(ctx, pdfsrc)) THROWMSG("invalid page number(s)"); spageref = pdf_lookup_page_obj(ctx, pdfsrc, pno); - pdf_obj *mb = pdf_dict_get(ctx, spageref, PDF_NAME(MediaBox)); + pdf_obj *mb = pdf_dict_get_inheritable(ctx, spageref, PDF_NAME(MediaBox)); if (mb) *mediabox = pdf_to_rect(ctx, mb); else *mediabox = pdf_bound_page(ctx, pdf_load_page(ctx, pdfsrc, pno)); - o = pdf_dict_get(ctx, spageref, PDF_NAME(CropBox)); + o = pdf_dict_get_inheritable(ctx, spageref, PDF_NAME(CropBox)); if (!o) { cropbox->x0 = mediabox->x0; @@ -101,16 +101,16 @@ int JM_insert_contents(fz_context *ctx, pdf_document *pdf, } else // make new array { - pdf_obj *carr = pdf_new_array(ctx, pdf, 2); + pdf_obj *carr = pdf_new_array(ctx, pdf, 5); if (overlay) { - pdf_array_push(ctx, carr, contents); + if (contents) pdf_array_push(ctx, carr, contents); pdf_array_push_drop(ctx, carr, newconts); } else { pdf_array_push_drop(ctx, carr, newconts); - pdf_array_push(ctx, carr, contents); + if (contents) pdf_array_push(ctx, carr, contents); } pdf_dict_put_drop(ctx, pageref, PDF_NAME(Contents), carr); } diff --git a/fitz/utils.py b/fitz/utils.py index 1f3b21a92..001760634 100644 --- a/fitz/utils.py +++ b/fitz/utils.py @@ -1765,14 +1765,17 @@ def __init__(self, page): self.doc = page.parent if not self.doc.isPDF: raise ValueError("not a PDF") - self.height = page.MediaBoxSize.y - self.width = page.MediaBoxSize.x - self.x = page.CropBoxPosition.x - self.y = page.CropBoxPosition.y - self.contents = "" - self.totalcont = "" - self.lastPoint = None - self.rect = None + self.height = page.MediaBoxSize.y + self.width = page.MediaBoxSize.x + self.x = page.CropBoxPosition.x + self.y = page.CropBoxPosition.y + self.p_trans = page._getTransformation() # page transformation matr + self.p_invtrans = ~self.p_trans # inverted transf. matrix + self.draw_cont = "" + self.text_cont = "" + self.totalcont = "" + self.lastPoint = None + self.rect = None def updateRect(self, x): if self.rect is None: @@ -1800,10 +1803,10 @@ def drawLine(self, p1, p2): p1 = Point(p1) p2 = Point(p2) if not (self.lastPoint == p1): - self.contents += "%g %g m\n" % (p1.x + self.x, + self.draw_cont += "%g %g m\n" % (p1.x + self.x, self.height - p1.y - self.y) self.lastPoint = p1 - self.contents += "%g %g l\n" % (p2.x + self.x, + self.draw_cont += "%g %g l\n" % (p2.x + self.x, self.height - p2.y - self.y) self.updateRect(p1) self.updateRect(p2) @@ -1816,11 +1819,11 @@ def drawPolyline(self, points): for i, p in enumerate(points): if i == 0: if not (self.lastPoint == Point(p)): - self.contents += "%g %g m\n" % (p[0] + self.x, + self.draw_cont += "%g %g m\n" % (p[0] + self.x, self.height - p[1] - self.y) self.lastPoint = Point(p) else: - self.contents += "%g %g l\n" % (p[0] + self.x, + self.draw_cont += "%g %g l\n" % (p[0] + self.x, self.height - p[1] - self.y) self.updateRect(p) self.lastPoint = Point(points[-1]) @@ -1834,9 +1837,9 @@ def drawBezier(self, p1, p2, p3, p4): p3 = Point(p3) p4 = Point(p4) if not (self.lastPoint == Point(p1)): - self.contents += "%g %g m\n" % (p1[0] + self.x, + self.draw_cont += "%g %g m\n" % (p1[0] + self.x, self.height - p1[1] - self.y) - self.contents += "%g %g %g %g %g %g c\n" % (p2[0] + self.x, + self.draw_cont += "%g %g %g %g %g %g c\n" % (p2[0] + self.x, self.height - p2[1] - self.y, p3[0] + self.x, self.height - p3[1] - self.y, @@ -1860,7 +1863,7 @@ def drawOval(self, rect): mb = rect.bl + (rect.br - rect.bl)*0.5 ml = rect.tl + (rect.bl - rect.tl)*0.5 if not (self.lastPoint == ml): - self.contents += "%g %g m\n" % (ml.x + self.x, self.height - ml.y - self.y) + self.draw_cont += "%g %g m\n" % (ml.x + self.x, self.height - ml.y - self.y) self.lastPoint = ml self.drawCurve(ml, rect.bl, mb) self.drawCurve(mb, rect.br, mr) @@ -1905,14 +1908,14 @@ def drawSector(self, center, point, beta, fullSector = True): while abs(betar) > 2 * math.pi: betar += w360 # bring angle below 360 degrees if not (self.lastPoint == point): - self.contents += l3 % (point.x + self.x, h - point.y - self.y) + self.draw_cont += l3 % (point.x + self.x, h - point.y - self.y) self.lastPoint = point Q = Point(0, 0) # just make sure it exists C = center P = point S = P - C # vector 'center' -> 'point' rad = abs(S) # circle radius - assert rad > 1e-7, "radius must be positive" + assert rad > 1e-5, "radius must be positive" alfa = self.horizontal_angle(center, point) while abs(betar) > abs(w90): # draw 90 degree arcs q1 = C.x + math.cos(alfa + w90) * rad @@ -1925,7 +1928,7 @@ def drawSector(self, center, point, beta, fullSector = True): kappa = kappah * abs(P - Q) cp1 = P + (R - P) * kappa # control point 1 cp2 = Q + (R - Q) * kappa # control point 2 - self.contents += l4 % (cp1.x + self.x, h - cp1.y - self.y, + self.draw_cont += l4 % (cp1.x + self.x, h - cp1.y - self.y, cp2.x + self.x, h - cp2.y - self.y, Q.x + self.x, h - Q.y - self.y) # draw betar -= w90 # reduce parm angle by 90 deg @@ -1945,13 +1948,13 @@ def drawSector(self, center, point, beta, fullSector = True): kappa = kappah * abs(P - Q) / (1 - math.cos(betar)) cp1 = P + (R - P) * kappa # control point 1 cp2 = Q + (R - Q) * kappa # control point 2 - self.contents += l4 % (cp1.x + self.x, h - cp1.y - self.y, + self.draw_cont += l4 % (cp1.x + self.x, h - cp1.y - self.y, cp2.x + self.x, h - cp2.y - self.y, Q.x + self.x, h - Q.y -self.y) # draw if fullSector: - self.contents += l3 % (point.x + self.x, h - point.y - self.y) - self.contents += l5 % (center.x + self.x, h - center.y - self.y) - self.contents += l5 % (Q.x + self.x, h - Q.y - self.y) + self.draw_cont += l3 % (point.x + self.x, h - point.y - self.y) + self.draw_cont += l5 % (center.x + self.x, h - center.y - self.y) + self.draw_cont += l5 % (Q.x + self.x, h - Q.y - self.y) self.lastPoint = Q return self.lastPoint @@ -1959,7 +1962,7 @@ def drawRect(self, rect): """Draw a rectangle. """ r = Rect(rect) - self.contents += "%g %g %g %g re\n" % (r.x0 + self.x, + self.draw_cont += "%g %g %g %g re\n" % (r.x0 + self.x, self.height - r.y1 - self.y, r.width, r.height) self.updateRect(r) @@ -2058,7 +2061,10 @@ def insertText(self, point, buffer, return 0 point = Point(point) - maxcode = max([ord(c) for c in " ".join(text)]) + try: + maxcode = max([ord(c) for c in " ".join(text)]) + except: + return 0 # ensure valid 'fontname' fname = fontname @@ -2091,7 +2097,7 @@ def insertText(self, point, buffer, while rot < 0: rot += 360 rot = rot % 360 # text rotate = 0, 90, 270, 180 red, green, blue = color if color else (0,0,0) - templ1 = "\nn q BT\n%s1 0 0 1 %g %g Tm /%s %g Tf %g %g %g rg " + templ1 = "\nq BT\n%s1 0 0 1 %g %g Tm /%s %g Tf %g %g %g rg " templ2 = "TJ\n0 -%g TD\n" cmp90 = "0 1 -1 0 0 0 cm\n" # rotates 90 deg counter-clockwise cmm90 = "0 -1 1 0 0 0 cm\n" # rotates 90 deg clockwise @@ -2105,7 +2111,7 @@ def insertText(self, point, buffer, m1 = Matrix(1, 0, 0, 1, morph[0].x + self.x, height - morph[0].y - self.y) mat = ~m1 * morph[1] * m1 - cm = "%g %g %g %g %g %g cm\n" % tuple(mat) + cm = "%g %g %g %g %g %g cm\n" % tuple(map(lambda x: round(x, 5), mat)) else: cm = "" top = height - point.y - self.y # start of 1st char @@ -2153,13 +2159,13 @@ def insertText(self, point, buffer, space -= lheight nlines += 1 - nres += "\nET Q\n" + nres += " ET Q\n" # ========================================================================= # end of text insertion # ========================================================================= # update the /Contents object - self.totalcont += nres + self.text_cont += nres return nlines #============================================================================== @@ -2257,7 +2263,7 @@ def pixlen(x): m1 = Matrix(1, 0, 0, 1, morph[0].x + self.x, self.height - morph[0].y - self.y) mat = ~m1 * morph[1] * m1 - cm = "%g %g %g %g %g %g cm\n" % tuple(mat) + cm = "%g %g %g %g %g %g cm\n" % tuple(map(lambda x: round(x, 5), mat)) else: cm = "" @@ -2356,7 +2362,7 @@ def pixlen(x): more = abs(more) if more < 1e-5: more = 0 # don't bother with epsilons - nres = "\nn q BT\n" + cm # initialize output buffer + nres = "\nq BT\n" + cm # initialize output buffer templ = "1 0 0 1 %g %g Tm /%s %g Tf %g Tw %g %g %g rg %sTJ\n" # center, right, justify: output each line with its own specifics spacing = 0 @@ -2395,7 +2401,7 @@ def pixlen(x): spacing, red, green, blue, getTJstr(t, tj_glyphs, simple, ordering)) nres += "ET Q\n" - self.totalcont += nres + self.text_cont += nres self.updateRect(rect) return more @@ -2409,46 +2415,51 @@ def finish(self, width = 1, closePath = True): """Finish this drawing segment by applying stroke and fill colors, dashes, line style and width, or morphing. Also determines whether any open path should be closed by a connecting line to its start point. """ - if self.contents == "": # treat empty contents as no-op + if self.draw_cont == "": # treat empty contents as no-op return - CheckColor(color) - CheckColor(fill) - self.contents += "%g w\n%i J\n%i j\n" % (width, roundCap, + + CheckColor(color) # ensure proper colors + CheckColor(fill) # ensure proper colors + + self.draw_cont += "%g w\n%i J\n%i j\n" % (width, roundCap, roundCap) + if dashes is not None and len(dashes) > 0: - self.contents += "%s d\n" % dashes + self.draw_cont += "%s d\n" % dashes + if closePath: - self.contents += "h\n" - self.contents += "%g %g %g RG\n" % color + self.draw_cont += "h\n" + self.lastPoint = None + + self.draw_cont += "%g %g %g RG\n" % color + if fill is not None: - self.contents += "%g %g %g rg\n" % fill + self.draw_cont += "%g %g %g rg\n" % fill if not even_odd: - self.contents += "B\n" + self.draw_cont += "B\n" else: - self.contents += "B*\n" + self.draw_cont += "B*\n" else: - self.contents += "S\n" - self.totalcont += "\nn q\n" + self.draw_cont += "S\n" + if CheckMorph(morph): m1 = Matrix(1, 0, 0, 1, morph[0].x + self.x, self.height - morph[0].y - self.y) mat = ~m1 * morph[1] * m1 - self.totalcont += "%g %g %g %g %g %g cm\n" % tuple(mat) - self.totalcont += self.contents + "Q\n" - self.contents = "" + self.draw_cont = "%g %g %g %g %g %g cm\n" % tuple(map(lambda x: round(x, 5), mat)) + self.draw_cont + + self.totalcont += "\nq\n" + self.draw_cont + "Q\n" + self.draw_cont = "" self.lastPoint = None return def commit(self, overlay = True): - """Update the page's /Contents object with Shape data. The argument controls, whether data appear in foreground (True, default) or background. + """Update the page's /Contents object with Shape data. The argument controls whether data appear in foreground (default) or background. """ - CheckParent(self.page) # doc may have died meanwhile - if self.totalcont == "": - return - if not self.totalcont.endswith("Q\n"): - raise ValueError("finish method missing") + CheckParent(self.page) # doc may have died meanwhile + self.totalcont += self.text_cont - if str is not bytes: # bytes object needed in Python 3 + if not fitz_py2: # need bytes if Python > 2 self.totalcont = bytes(self.totalcont, "utf-8") # make /Contents object with dummy stream @@ -2456,8 +2467,9 @@ def commit(self, overlay = True): # update it with potential compression self.doc._updateStream(xref, self.totalcont) - self.lastPoint = None # clean up ... - self.rect = None # - self.contents = "" # for possible ... - self.totalcont = "" # re-use + self.lastPoint = None # clean up ... + self.rect = None # + self.draw_cont = "" # for possible ... + self.text_cont = "" # ... + self.totalcont = "" # re-use return diff --git a/fitz/version.i b/fitz/version.i index 464c99f54..956df9d2e 100644 --- a/fitz/version.i +++ b/fitz/version.i @@ -1,6 +1,6 @@ %pythoncode %{ VersionFitz = "1.14.0" -VersionBind = "1.14.3" -VersionDate = "2018-12-01 18:33:20" -version = (VersionBind, VersionFitz, "20181201183320") +VersionBind = "1.14.4" +VersionDate = "2018-12-18 08:56:44" +version = (VersionBind, VersionFitz, "20181218085644") %} \ No newline at end of file diff --git a/setup.py b/setup.py index df38dac89..f3c950d5a 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ sources=['./fitz/fitz_wrap.c',]) setup(name = 'PyMuPDF', - version = "1.14.3", + version = "1.14.4", description = 'Python bindings for the PDF rendering library MuPDF', classifiers = ['Development Status :: 5 - Production/Stable', 'Environment :: Console', From 20d643ef0151515f7926afe91b05ac56c9d25c51 Mon Sep 17 00:00:00 2001 From: "Jorj X. McKie" Date: Tue, 18 Dec 2018 09:45:57 -0400 Subject: [PATCH 2/3] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b9bef8dc..23a5ab6a4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![logo](https://github.com/rk700/PyMuPDF/blob/master/demo/pymupdf.jpg) -Release date: December 2, 2018 +Release date: December 18, 2018 **Travis-CI:** [![Build Status](https://travis-ci.org/JorjMcKie/py-mupdf.svg?branch=master)](https://travis-ci.org/JorjMcKie/py-mupdf) From c458775df382c7ae3db16932a56ad59ef3d78289 Mon Sep 17 00:00:00 2001 From: "Jorj X. McKie" Date: Tue, 18 Dec 2018 10:05:23 -0400 Subject: [PATCH 3/3] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23a5ab6a4..4e48e48cb 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ You can now also create and update Form PDFs and form fields with support for te Have a look at the basic [demos](https://github.com/rk700/PyMuPDF/tree/master/demo), the [examples](https://github.com/rk700/PyMuPDF/tree/master/examples) (which contain complete, working programs), and the **recipes** section of our [Wiki](https://github.com/rk700/PyMuPDF/wiki) sidebar, which contains more than a dozen of guides in How-To-style. -Our documentation, written using Sphinx, is available in various formats from the following sources. +Our documentation, written using Sphinx, is available in various formats from the following sources. It currently is a combination of a reference guide and a user manual. For a quick start to using PyMuPDF look at the [tutorial](https://pymupdf.readthedocs.io/en/latest/tutorial/) and the [recipes](https://pymupdf.readthedocs.io/en/latest/faq/) chapters. * You can view it online at [Read the Docs](https://pymupdf.readthedocs.io/). For **best quality downloads** use the following links. * zipped [HTML](https://github.com/rk700/PyMuPDF/tree/master/doc/html.zip)