diff --git a/PKG-INFO b/PKG-INFO index d5888bcd8..e8487871e 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: PyMuPDF -Version: 1.17.3 +Version: 1.17.4 Author: Ruikai Liu Author-email: lrk700@gmail.com Maintainer: Jorj X. McKie @@ -9,7 +9,7 @@ Home-page: https://github.com/pymupdf/PyMuPDF Download-url: https://github.com/pymupdf/PyMuPDF Summary: PyMuPDF is a Python binding for the PDF rendering library MuPDF Description: - Release date: July 6, 2020 + Release date: July 31, 2020 Authors ======= @@ -20,7 +20,7 @@ Description: Introduction ============ - This is **version 1.17.3 of PyMuPDF**, a Python binding for `MuPDF `_ - "a lightweight PDF and XPS viewer". + This is **version 1.17.4 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 305807b3b..084c0780e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# PyMuPDF 1.17.3 +# PyMuPDF 1.17.4 ![logo](https://github.com/pymupdf/PyMuPDF/blob/master/demo/pymupdf.jpg) -Release date: July 6, 2020 +Release date: July 31, 2020 **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.17.3 of PyMuPDF**, a Python binding with support for [MuPDF 1.17.*](http://mupdf.com/) - "a lightweight PDF, XPS, and E-book viewer". +This is **version 1.17.4 of PyMuPDF**, a Python binding with support for [MuPDF 1.17.*](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/docs/annot.rst b/docs/annot.rst index 3bed5fbcb..c7a143a87 100644 --- a/docs/annot.rst +++ b/docs/annot.rst @@ -67,7 +67,7 @@ There is a parent-child relationship between an annotation and its page. If the :rtype: :ref:`Pixmap` - .. note:: If the annotation has just been created or modified + .. note:: If the annotation has just been created or modified, you should reload the page first via *page = doc.reload_page(page)*. .. method:: setInfo(info=None, content=None, title=None, creationDate=None, modDate=None, subject=None) diff --git a/docs/changes.rst b/docs/changes.rst index b99001af1..9e0e7e194 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -1,6 +1,18 @@ Change Logs =============== +Changes in Version 1.17.4 +--------------------------- +* **Fixed** issue `#561 `_. Handling of more than 10 :ref:`Font` objects on one page should now work correctly. +* **Fixed** issue `#562 `_. Annotation pixmaps are no longer derived from the page pixmap, thus avoiding unintended inclusion of page content. +* **Fixed** issue `#559 `_. This **MuPDF** bug is being temporarily fixed with a pre-version of MuPDF's next release. +* **Added** utility function :meth:`repair_mono_font` for correcting displayed character spacing for some mono-spaced fonts. +* **Added** utility method :meth:`Document.need_appearances` for fine-controlling Form PDF behavior. Addresses issue `#563 `_. +* **Added** utility function :meth:`sRGB_to_pdf` to recover the PDF color triple for a given color integer in sRGB format. +* **Added** utility function :meth:`sRGB_to_rgb` to recover the (R, G, B) color triple for a given color integer in sRGB format. +* **Added** utility function :meth:`make_table` which delivers table cells for a given rectangle and desired numbers of columns and rows. +* **Added** support for optional fonts in repository `pymupdf-fonts `_. + Changes in Version 1.17.3 --------------------------- * **Fixed** an undocumented issue, which prevented fully cleaning a PDF page when using :meth:`Page.cleanContents`. diff --git a/docs/conf.py b/docs/conf.py index 3573fd7ee..88966e418 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,7 +12,7 @@ # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = "2.0" +# needs_sphinx = "3.1" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -46,7 +46,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = "1.17.3" +release = "1.17.4" # The short X.Y version version = release @@ -207,7 +207,7 @@ latex_show_pagerefs = False # If true, show URL addresses after external links. -latex_show_urls = True +# latex_show_urls = True # latex_use_xindy = True # Documents to append as an appendix to all manuals. # latex_appendices = [] diff --git a/docs/document.rst b/docs/document.rst index c6a900b15..816720585 100644 --- a/docs/document.rst +++ b/docs/document.rst @@ -53,7 +53,8 @@ For details on **embedded files** refer to Appendix 3. :meth:`Document.loadPage` read a page :meth:`Document.makeBookmark` create a page pointer in reflowable documents :meth:`Document.metadataXML` PDF only: :data:`xref` of XML metadata -:meth:`Document.movePage` PDF only: move a page to another location +:meth:`Document.movePage` PDF only: move a page to different location in doc +:meth:`Document.need_appearances` PDF only: get/set */NeedAppearances* property :meth:`Document.newPage` PDF only: insert a new empty page :meth:`Document.nextLocation` return (chapter, pno) of following page :meth:`Document.pages` iterator over a page range @@ -70,10 +71,10 @@ For details on **embedded files** refer to Appendix 3. :meth:`Document.setToC` PDF only: set the table of contents (TOC) :meth:`Document.updateObject` PDF only: replace object source :meth:`Document.updateStream` PDF only: replace stream source -:meth:`Document.write` PDF only: writes the document to memory +:meth:`Document.write` PDF only: writes document to memory :meth:`Document.xrefObject` PDF only: object source at the :data:`xref` -:meth:`Document.xrefStream` PDF only: stream source at the :data:`xref` -:meth:`Document.xrefStreamRaw` PDF only: raw stream source at the :data:`xref` +:meth:`Document.xrefStream` PDF only: decompressed stream source at :data:`xref` +:meth:`Document.xrefStreamRaw` PDF only: raw stream source at :data:`xref` :attr:`Document.chapterCount` number of chapters :attr:`Document.FormFonts` PDF only: list of global widget fonts :attr:`Document.isClosed` has document been closed? @@ -719,7 +720,11 @@ For details on **embedded files** refer to Appendix 3. :arg int to: the page number in front of which to copy. The default inserts **after** the last page. - .. note:: In contrast to :meth:`copyPage`, this method creates a completely identical new page object -- with the exception of :attr:`Page.xref` of course, which will be different. So changes to a copy will only show there. + .. note:: + + * In contrast to :meth:`copyPage`, this method creates a new page object (with a new :data:`xref`), which can be changed independently from the original. + + * Any Popup and "IRT" ("in response to") annotations are **not copied** to avoid potentially incorrect situations. .. method:: movePage(pno, to=-1) @@ -729,9 +734,26 @@ For details on **embedded files** refer to Appendix 3. :arg int to: the page number in front of which to insert the moved page. The default moves **after** the last page. + + .. method:: need_appearances(value=None) + + *(New in v1.17.4)* + + PDF only: Get or set the */NeedAppearances* property of Form PDFs. Quote: *"(Optional) A flag specifying whether to construct appearance streams and appearance dictionaries for all widget annotations in the document ... Default value: false."* This may help controlling the behavior of some readers / viewers. + + :arg bool value: set the property to this value. If omitted or *None*, inquire the current value. + + :rtype: bool + :returns: + * None: not a Form PDF or property not defined. + * True / False: the value of the property (either just set or existing for inquiries). + + Once set, the property cannot be removed again (which is no problem). + + .. method:: getSigFlags() - PDF only: Return whether the document contains signature fields. This is an optional PDF property: if not present, no conclusions can be drawn, because the PDF creator may just not have bothered to use it. + PDF only: Return whether the document contains signature fields. This is an optional PDF property: if not present (return value -1), no conclusions can be drawn -- the PDF creator may just not have bothered to use it. :rtype: int :returns: diff --git a/docs/faq.rst b/docs/faq.rst index 9b654225f..db6cc13d7 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -1046,7 +1046,9 @@ In v1.14.0, annotation handling has been considerably extended: How to Add and Modify Annotations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In PyMuPDF, new annotations are added via :ref:`Page` methods. To keep code duplication effort small, we only offer a minimal set of options here. For example, to add a 'Circle' annotation, only the containing rectangle can be specified. The result is a circle (or ellipsis) with white interior, black border and a line width of 1, exactly fitting into the rectangle. To adjust the annot's appearance, :ref:`Annot` methods must then be used. After having made all required changes, the annot's :meth:`Annot.update` methods must be invoked to finalize all your changes. +In PyMuPDF, new annotations can be added added via :ref:`Page` methods. Once an annotation exists, it can be modified to a large extent using methods of the :ref:`Annot` class. + +In contrast to many other tools, initial insert of annotations happens with a minimum number of properties. We leave it to the programmer to e.g. set attributes like author, creation date or subject. As an overview for these capabilities, look at the following script that fills a PDF page with most of the available annotations. Look in the next sections for more special situations: diff --git a/docs/font.rst b/docs/font.rst index 0148d96c2..519ce9b2b 100644 --- a/docs/font.rst +++ b/docs/font.rst @@ -19,6 +19,11 @@ A Font object also contains useful general information, like the font bbox, the Font constructor. The large number of parameters are used to locate font, which most closely resembles the requirements. Not all parameters are ever required -- see the below pseudo code explaining the logic how the parameters are evaluated. :arg str fontname: one of the :ref:`Base-14-Fonts` or CJK fontnames. Also possible are a select few of other names like (watch the correct spelling): "Arial", "Times", "Times Roman". + + *(Changed in v1.17.4)* + + If you have installed `pymupdf-fonts `_, you can also use the following new "reserved" fontnames: "figo", "figbo", "figit", "figbi", "fimo", and "fimbo". This will provide one of the "FiraGo" or resp. "FiraMono" fonts, created by Mozilla.org. + :arg str filename: the filename of a fontfile somewhere on your system [#f1]_. :arg bytes,bytearray,io.BytesIO fontbuffer: a fontfile loaded in memory [#f1]_. :arg in script: the number of a UCDN script. Currently supported in PyMuPDF are numbers 24, and 32 through 35. @@ -48,7 +53,7 @@ A Font object also contains useful general information, like the font bbox, the look for fallback font .. note:: - + With the usual abbreviations "helv", "tiro", etc., you will create fonts with the expected names "Helvetica", "Times-Roman" and so on. Using *ordering >= 0*, or fontnames starting with "china", "japan" or "korea" will always create the same **"universal"** font **"Droid Sans Fallback Regular"**. This font supports **all CJK and all Latin characters**. @@ -57,11 +62,22 @@ A Font object also contains useful general information, like the font bbox, the If you **know** you have a mixture of CJK and Latin text, consider just using ``Font(ordering=0)`` because this supports everything and also significantly (by a factor of two to three) speeds up execution: MuPDF will always find any character in this single font and need not check fallbacks. - But if you do specify a Base-14 fontname, you will still be able to also write CJK characters! MuPDF automatically detects this situation and silently falls back to the universal font (which will then of course also be included in your PDF). + But if you do specify a Base-14 fontname, you will still be able to also write CJK characters! MuPDF automatically detects this situation and silently falls back to the universal font (which will then of course also be embedded in your PDF). - **All fonts mentioned here** also support Greek and Cyrillic letters. + *(New in v1.17.4)* Optionally, a set of new "reserved" fontnames becomes available if you install `pymupdf-fonts `_. The currently available fonts are from the Fira fonts family created by Mozilla. "Fira Mono" is a nice mono-spaced sans font set and FiraGO is another non-serifed "universal" font, set which supports all European languages (including Cyrillic and Greek) plus Thai, Arabian, Hewbrew and Devanagari -- however none of the CJK languages. The size of a FiraGO font is only a quarter of the "Droid Sans Fallback" size (compressed 400 KB vs. 1.65 MB) -- and the style variants bold and italic are available..The following table maps a fontname to the corresponding font: - *Monospaced* fonts are an issue: They are written with a too large width, e.g. ``" a"`` instead of ``"a"``. This applies to "cour" variants as well as most other mono fonts. The only exception we know of so far is ``consola.ttf``. If you want to output monospaced text, we recommend using the Consolas font for the time being. + =========== ======================================= + Fontname Font + =========== ======================================= + figo FiraGO Regular + figbo FiraGO Bold + figit FiraGO Italic + figbi FiraGO Bold Italic + fimo Fira Mono Regular + fimbo Fira Mono Bold + =========== ======================================= + + **All fonts mentioned here** also support Greek and Cyrillic letters. .. method:: has_glyph(chr, language=None, script=0) diff --git a/docs/functions.rst b/docs/functions.rst index f943f8cdb..10453e47d 100644 --- a/docs/functions.rst +++ b/docs/functions.rst @@ -55,6 +55,9 @@ Yet others are handy, general-purpose utilities. :meth:`planishLine` matrix to map a line to the x-axis :meth:`PaperSize` return width, height for a known paper format :meth:`PaperRect` return rectangle for a known paper format +:meth:`sRGB_to_pdf` return PDF RGB color tuple from a sRGB integer +:meth:`sRGB_to_rgb` return (R, G, B) color tuple from a sRGB integer +:meth:`make_table` return list of table cells for a given rectangle :attr:`paperSizes` dictionary of pre-defined paper formats ==================================== ============================================================== @@ -87,6 +90,49 @@ Yet others are handy, general-purpose utilities. fitz.Rect(0.0, 0.0, 792.0, 612.0) >>> +----- + + .. method:: sRGB_to_pdf(srgb) + + *New in v1.17.4* + + Convenience function returning a PDF color triple (red, green, blue) for a given sRGB color integer as it occurs in :meth:`Page.getText` dictionaries "dict" and "rawdict". + + :arg int srgb: an integer of format RRGGBB, where each color component is an integer in range(255). + + :returns: a tuple (red, green, blue) with float items in intervall *0 <= item <= 1* representing the same color. + +----- + + .. method:: sRGB_to_rgb(srgb) + + *New in v1.17.4* + + Convenience function returning a color (red, green, blue) for a given sRGB color integer . + + :arg int srgb: an integer of format RRGGBB, where each color component is an integer in range(255). + + :returns: a tuple (red, green, blue) with integer items in intervall *0 <= item <= 255* representing the same color. + +----- + + .. method:: make_table(rect=(0, 0, 1, 1), cols=1, rows=1) + + *New in v1.17.4* + + Convenience function returning a list of :ref:`Rect` objects representing equal sized table cells for the given rectangle. + + :arg rect_like rect: the rectangle to contain the table. + :arg int cols: the desired number of columns. + :arg int rows: the desired number of rows. + :returns: a list of :ref:`Rect` objects of equal size, whose union equals *rect*:: + + [ + [cell00, cell01, ...] # row 0 + ... + [...] # last row + ] + ----- .. method:: planishLine(p1, p2) diff --git a/docs/images/img-annots.jpg b/docs/images/img-annots.jpg index fdb1d9d68..1ff17aff2 100644 Binary files a/docs/images/img-annots.jpg and b/docs/images/img-annots.jpg differ diff --git a/docs/new-annots.py b/docs/new-annots.py index 45e68e787..11a38d3d3 100644 --- a/docs/new-annots.py +++ b/docs/new-annots.py @@ -1,143 +1,172 @@ # -*- coding: utf-8 -*- -from __future__ import print_function -import sys - -import fitz - -print(fitz.__doc__) -if fitz.VersionBind.split(".") < ["1", "16", "0"]: - sys.exit("PyMuPDF v1.16.0+ is needed.") """ ------------------------------------------------------------------------------- Demo script showing how annotations can be added to a PDF using PyMuPDF. It contains the following annotation types: -Text ("sticky note"), FreeText, text markers (underline, strike-out, -highlight), Circle, Square, Line, PolyLine, Polygon, FileAttachment and Stamp. - +Caret, Text, FreeText, text markers (underline, strike-out, highlight, +squiggle), Circle, Square, Line, PolyLine, Polygon, FileAttachment, Stamp +and Redaction. +There is some effort to vary appearances by adding colors, line ends, +opacity, rotation, dashed lines, etc. Dependencies ------------ -PyMuPDF v1.16.0 +PyMuPDF v1.17.0 ------------------------------------------------------------------------------- """ -text = "text in line\ntext in line\ntext in line\ntext in line" +from __future__ import print_function + +import gc +import os +import sys + +import fitz + +print(fitz.__doc__) +if fitz.VersionBind.split(".") < ["1", "17", "0"]: + sys.exit("PyMuPDF v1.17.0+ is needed.") + +gc.set_debug(gc.DEBUG_UNCOLLECTABLE) + +highlight = "this text is highlighted" +underline = "this text is underlined" +strikeout = "this text is striked out" +squiggled = "this text is zigzag-underlined" red = (1, 0, 0) blue = (0, 0, 1) gold = (1, 1, 0) green = (0, 1, 0) displ = fitz.Rect(0, 50, 0, 50) -r = fitz.Rect(72, 100, 220, 135) +r = fitz.Rect(72, 72, 220, 100) t1 = u"têxt üsès Lätiñ charß,\nEUR: €, mu: µ, super scripts: ²³!" -def print_descr(rect, annot): - """Print a short description to the right of an annot rect.""" +def print_descr(annot): + """Print a short description to the right of each annot rect.""" annot.parent.insertText( - rect.br + (10, -5), "'%s' annotation" % annot.type[1], color=red + annot.rect.br + (10, -5), "%s annotation" % annot.type[1], color=red ) doc = fitz.open() page = doc.newPage() -annot = page.addCaretAnnot(r.tl) # 'Caret' -print_descr(annot.rect, annot) +page.setRotation(0) + +annot = page.addCaretAnnot(r.tl) +print_descr(annot) r = r + displ -annot = page.addFreetextAnnot( # 'FreeText' - r, t1, fontsize=10, rotate=90, text_color=blue, fill_color=gold +annot = page.addFreetextAnnot( + r, + t1, + fontsize=10, + rotate=90, + text_color=blue, + fill_color=gold, + align=fitz.TEXT_ALIGN_CENTER, ) annot.setBorder(width=0.3, dashes=[2]) -annot.update() +annot.update(text_color=blue, fill_color=gold) -print_descr(annot.rect, annot) +print_descr(annot) r = annot.rect + displ annot = page.addTextAnnot(r.tl, t1) -print_descr(annot.rect, annot) +print_descr(annot) -#------------------------------------------------------------------------------ -# prepare insertion of 4 text highlight annotations -#------------------------------------------------------------------------------ +# Adding text marker annotations: +# first insert a unique text, then search for it, then mark it pos = annot.rect.tl + displ.tl -# 1. insert 4 rotated text lines -page.insertText(pos, text, fontsize=11, morph=(pos, fitz.Matrix(-15))) -# 2. search text to get the quads -rl = page.searchFor("text in line", quads=True) -r0 = rl[0] # these are the 4 quads -r1 = rl[1] -r2 = rl[2] -r3 = rl[3] -annot = page.addHighlightAnnot(r0) -# need to convert quad to rect for descriptive text ... -print_descr(r0.rect, annot) - -annot = page.addStrikeoutAnnot(r1) -print_descr(r1.rect, annot) - -annot = page.addUnderlineAnnot(r2) -print_descr(r2.rect, annot) - -annot = page.addSquigglyAnnot(r3) -print_descr(r3.rect, annot) -# end of text highlight annot code -#------------------------------------------------------------------------------ - -r = r3.rect + displ +page.insertText( + pos, # insertion point + highlight, # inserted text + morph=(pos, fitz.Matrix(-5)), # rotate around insertion point +) +rl = page.searchFor(highlight, quads=True) # need a quad b/o tilted text +annot = page.addHighlightAnnot(rl[0]) +print_descr(annot) +pos = annot.rect.bl # next insertion point + +page.insertText(pos, underline, morph=(pos, fitz.Matrix(-10))) +rl = page.searchFor(underline, quads=True) +annot = page.addUnderlineAnnot(rl[0]) +print_descr(annot) +pos = annot.rect.bl + +page.insertText(pos, strikeout, morph=(pos, fitz.Matrix(-15))) +rl = page.searchFor(strikeout, quads=True) +annot = page.addStrikeoutAnnot(rl[0]) +print_descr(annot) +pos = annot.rect.bl + +page.insertText(pos, squiggled, morph=(pos, fitz.Matrix(-20))) +rl = page.searchFor(squiggled, quads=True) +annot = page.addSquigglyAnnot(rl[0]) +print_descr(annot) +pos = annot.rect.bl + +r = fitz.Rect(pos, pos.x + 75, pos.y + 35) + (0, 20, 0, 20) annot = page.addPolylineAnnot([r.bl, r.tr, r.br, r.tl]) # 'Polyline' annot.setBorder(width=0.3, dashes=[2]) -annot.setColors(stroke=blue, fill=gold) -annot.setLineEnds(fitz.PDF_ANNOT_LE_CLOSED_ARROW, - fitz.PDF_ANNOT_LE_R_CLOSED_ARROW) -annot.update() -print_descr(annot.rect, annot) +annot.setColors(stroke=blue, fill=green) +annot.setLineEnds(fitz.PDF_ANNOT_LE_CLOSED_ARROW, fitz.PDF_ANNOT_LE_R_CLOSED_ARROW) +annot.update(fill_color=(1, 1, 0)) +print_descr(annot) r += displ annot = page.addPolygonAnnot([r.bl, r.tr, r.br, r.tl]) # 'Polygon' annot.setBorder(width=0.3, dashes=[2]) annot.setColors(stroke=blue, fill=gold) -annot.setLineEnds(fitz.PDF_ANNOT_LE_DIAMOND, - fitz.PDF_ANNOT_LE_CIRCLE) +annot.setLineEnds(fitz.PDF_ANNOT_LE_DIAMOND, fitz.PDF_ANNOT_LE_CIRCLE) annot.update() -print_descr(annot.rect, annot) +print_descr(annot) r += displ annot = page.addLineAnnot(r.tr, r.bl) # 'Line' annot.setBorder(width=0.3, dashes=[2]) annot.setColors(stroke=blue, fill=gold) -annot.setLineEnds(fitz.PDF_ANNOT_LE_DIAMOND, - fitz.PDF_ANNOT_LE_CIRCLE) +annot.setLineEnds(fitz.PDF_ANNOT_LE_DIAMOND, fitz.PDF_ANNOT_LE_CIRCLE) annot.update() -print_descr(annot.rect, annot) +print_descr(annot) r += displ annot = page.addRectAnnot(r) # 'Square' annot.setBorder(width=1, dashes=[1, 2]) annot.setColors(stroke=blue, fill=gold) -annot.setOpacity(0.5) -annot.update() -print_descr(annot.rect, annot) +annot.update(opacity=0.5) +print_descr(annot) r += displ annot = page.addCircleAnnot(r) # 'Circle' annot.setBorder(width=0.3, dashes=[2]) annot.setColors(stroke=blue, fill=gold) annot.update() -print_descr(annot.rect, annot) +print_descr(annot) r += displ -annot = page.addFileAnnot(r.tl, # 'FileAttachment' - b"just anything for testing", - "testdata.txt") -print_descr(annot.rect, annot) +annot = page.addFileAnnot( + r.tl, b"just anything for testing", "testdata.txt" # 'FileAttachment' +) +print_descr(annot) # annot.rect r += displ annot = page.addStampAnnot(r, stamp=10) # 'Stamp' annot.setColors(stroke=green) annot.update() -print_descr(annot.rect, annot) - +print_descr(annot) + +r += displ + (0, 0, 50, 10) +rc = page.insertTextbox( + r, + "This content will be removed upon applying the redaction.", + color=blue, + align=fitz.TEXT_ALIGN_CENTER, +) +annot = page.addRedactAnnot(r) +print_descr(annot) -doc.save("new-annots.pdf") +outfile = os.path.abspath(__file__).replace(".py", "-%i.pdf" % page.rotation) +doc.save(outfile, deflate=True) diff --git a/docs/textwriter.rst b/docs/textwriter.rst index b3b1e6359..dc62f689a 100644 --- a/docs/textwriter.rst +++ b/docs/textwriter.rst @@ -94,7 +94,7 @@ There also exists :meth:`Page.writeText` which lets you combine one or more Text The page rectangle for which this TextWriter was created. Must not be modified. -To see some demo scripts dealing with TextWriter, have a look at `this `_ rpository. +To see some demo scripts dealing with TextWriter, have a look at `this `_ repository. .. note:: @@ -103,4 +103,4 @@ To see some demo scripts dealing with TextWriter, have a look at `this = 0) { + return JM_BOOL(oldval); + } + return_none; + } + //--------------------------------------------------------------------- // Return the /SigFlags value //--------------------------------------------------------------------- @@ -2217,16 +2260,20 @@ if len(pyliste) == 0 or min(pyliste) not in range(len(self)) or max(pyliste) not pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self); pdf_obj *obj = NULL; PyObject *text = NULL; + + fz_buffer *res=NULL; fz_try(gctx) { ASSERT_PDF(pdf); int xreflen = pdf_xref_len(gctx, pdf); if (!INRANGE(xref, 1, xreflen-1)) THROWMSG("xref out of range"); obj = pdf_load_object(gctx, pdf, xref); - text = JM_object_to_string(gctx, pdf_resolve_indirect(gctx, obj), compressed, ascii); + res = JM_object_to_buffer(gctx, pdf_resolve_indirect(gctx, obj), compressed, ascii); + text = JM_EscapeStrFromBuffer(gctx, res); } fz_always(gctx) { pdf_drop_obj(gctx, obj); + fz_drop_buffer(gctx, res); } fz_catch(gctx) return PyUnicode_FromString(""); return text; @@ -2242,8 +2289,13 @@ if len(pyliste) == 0 or min(pyliste) not in range(len(self)) or max(pyliste) not pdf_document *pdf = pdf_specifics(gctx, (fz_document *) $self); if (!pdf) return_none; PyObject *text = NULL; + fz_buffer *res=NULL; fz_try(gctx) { - text = JM_object_to_string(gctx, pdf_trailer(gctx, pdf), compressed, ascii); + res = JM_object_to_buffer(gctx, pdf_trailer(gctx, pdf), compressed, ascii); + text = JM_EscapeStrFromBuffer(gctx, res); + } + fz_always(gctx) { + fz_drop_buffer(gctx, res); } fz_catch(gctx) { return PyUnicode_FromString("PDF trailer damaged"); @@ -2450,7 +2502,7 @@ if len(pyliste) == 0 or min(pyliste) not in range(len(self)) or max(pyliste) not // full (deep) copy of one page //--------------------------------------------------------------------- FITZEXCEPTION(fullcopyPage, !result) - CLOSECHECK0(fullcopyPage, """Make full page duplicate.""") + CLOSECHECK0(fullcopyPage, """Make full page duplication.""") %pythonappend fullcopyPage %{self._reset_page_refs()%} PyObject *fullcopyPage(int pno, int to = -1) { @@ -2468,8 +2520,31 @@ if len(pyliste) == 0 or min(pyliste) not in range(len(self)) or max(pyliste) not pdf_lookup_page_obj(gctx, pdf, pno)); pdf_obj *page2 = pdf_deep_copy_obj(gctx, page1); + pdf_obj *old_annots = pdf_dict_get(gctx, page2, PDF_NAME(Annots)); - // read the old contents stream(s) + // copy annotations, but remove Popup and IRT types + if (old_annots) { + int i, n = pdf_array_len(gctx, old_annots); + pdf_obj *new_annots = pdf_new_array(gctx, pdf, n); + for (i = 0; i < n; i++) { + pdf_obj *o = pdf_array_get(gctx, old_annots, i); + pdf_obj *subtype = pdf_dict_get(gctx, o, PDF_NAME(Subtype)); + if (pdf_name_eq(gctx, subtype, PDF_NAME(Popup))) continue; + if (pdf_dict_gets(gctx, o, "IRT")) continue; + pdf_obj *copy_o = pdf_deep_copy_obj(gctx, + pdf_resolve_indirect(gctx, o)); + int xref = pdf_create_object(gctx, pdf); + pdf_update_object(gctx, pdf, xref, copy_o); + pdf_drop_obj(gctx, copy_o); + copy_o = pdf_new_indirect(gctx, pdf, xref, 0); + pdf_dict_del(gctx, copy_o, PDF_NAME(Popup)); + pdf_dict_del(gctx, copy_o, PDF_NAME(P)); + pdf_array_push_drop(gctx, new_annots, copy_o); + } + pdf_dict_put_drop(gctx, page2, PDF_NAME(Annots), new_annots); + } + + // copy the old contents stream(s) res = JM_read_contents(gctx, page1); // create new /Contents object for page2 @@ -4714,7 +4789,7 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, pdf_page *page = pdf_page_from_fz_page(gctx, (fz_page *) $self); pdf_document *pdf; pdf_obj *resources, *fonts, *font_obj; - fz_font *font; + fz_font *font = NULL; fz_buffer *res = NULL; const unsigned char *data = NULL; int size, ixref = 0, index = 0, simple = 0; @@ -4794,13 +4869,12 @@ def insertFont(self, fontname="helv", fontfile=None, fontbuffer=None, Py_CLEAR(name); Py_CLEAR(subt); - // resources and fonts objects will contain named reference to font - pdf_dict_puts(gctx, fonts, fontname, font_obj); - pdf_drop_obj(gctx, font_obj); - fz_drop_font(gctx, font); + // store font in resources and fonts objects will contain named reference to font + pdf_dict_puts_drop(gctx, fonts, fontname, font_obj); } fz_always(gctx) { fz_drop_buffer(gctx, res); + fz_drop_font(gctx, font); } fz_catch(gctx) { return NULL; @@ -5369,8 +5443,7 @@ if not self.colorspace or self.colorspace.n > 3: THROWMSG("cannot copy pixmap with NULL colorspace"); if (pm->alpha != src_pix->alpha) THROWMSG("source and target alpha must be equal"); - JM_Warning("Not implemented in MuPDF v1.17"); - // fz_copy_pixmap_rect(gctx, pm, src_pix, JM_irect_from_py(bbox), NULL); + fz_copy_pixmap_rect(gctx, pm, src_pix, JM_irect_from_py(bbox), NULL); } fz_catch(gctx) { return NULL; @@ -7553,22 +7626,40 @@ struct Annot } - %pythoncode %{ - def getPixmap(self, matrix=None, colorspace="rgb", alpha=False): - """Return the Pixmap of the annotation. - """ - page = self.parent - if page is None: - raise ValueError("orphaned object: parent is None") - return page.getPixmap( - matrix=matrix, - colorspace=colorspace, - alpha=alpha, - clip=self.rect, - annots=True, - ) + //--------------------------------------------------------------------- + // annotation pixmap + //--------------------------------------------------------------------- + FITZEXCEPTION(getPixmap, !result) + PARENTCHECK(getPixmap, """Annotation Pixmap.""") + %pythonprepend getPixmap +%{"""Annotation Pixmap.""" + +CheckParent(self) +cspaces = {"gray": csGRAY, "rgb": csRGB, "cmyk": csCMYK} +if type(colorspace) is str: + colorspace = cspaces.get(colorspace.lower(), None) +%} + struct Pixmap * + getPixmap(PyObject *matrix = NULL, struct Colorspace *colorspace = NULL, int alpha = 0) + { + fz_matrix ctm = JM_matrix_from_py(matrix); + fz_colorspace *cs = (fz_colorspace *) colorspace; + fz_pixmap *pix = NULL; + if (!cs) { + cs = fz_device_rgb(gctx); + } + + fz_try(gctx) { + pix = pdf_new_pixmap_from_annot(gctx, (pdf_annot *) $self, ctm, cs, NULL, alpha); + } + fz_catch(gctx) { + return NULL; + } + return (struct Pixmap *) pix; + } + %pythoncode %{ def _erase(self): try: self.parent._forget_annot(self) @@ -7894,6 +7985,13 @@ struct DisplayList { //----------------------------------------------------------------------------- struct TextPage { %extend { + ~TextPage() + { + DEBUGMSG1("TextPage"); + fz_drop_stext_page(gctx, (fz_stext_page *) $self); + DEBUGMSG2; + } + FITZEXCEPTION(TextPage, !result) TextPage(PyObject *mediabox) { @@ -7907,12 +8005,6 @@ struct TextPage { return (struct TextPage *) tp; } - ~TextPage() - { - DEBUGMSG1("TextPage"); - fz_drop_stext_page(gctx, (fz_stext_page *) $self); - DEBUGMSG2; - } //--------------------------------------------------------------------- // method search() //--------------------------------------------------------------------- @@ -7962,7 +8054,8 @@ struct TextPage { // Get list of all blocks with block type and bbox as a Python list //--------------------------------------------------------------------- FITZEXCEPTION(_getNewBlockList, !result) - PyObject *_getNewBlockList(PyObject *page_dict, int raw) + PyObject * + _getNewBlockList(PyObject *page_dict, int raw) { fz_try(gctx) { JM_make_textpage_dict(gctx, (fz_stext_page *) $self, page_dict, raw); @@ -7988,7 +8081,8 @@ struct TextPage { FITZEXCEPTION(extractBLOCKS, !result) %pythonprepend extractBLOCKS %{"""Fill a given list with text block information."""%} - PyObject *extractBLOCKS(PyObject *lines) + PyObject * + extractBLOCKS(PyObject *lines) { fz_stext_block *block; fz_stext_line *line; @@ -8060,7 +8154,8 @@ struct TextPage { FITZEXCEPTION(extractWORDS, !result) %pythonprepend extractWORDS %{"""Fill a list with text word information."""%} - PyObject *extractWORDS(PyObject *lines) + PyObject * + extractWORDS(PyObject *lines) { fz_stext_block *block; fz_stext_line *line; @@ -8220,6 +8315,10 @@ struct TextPage { def extractRAWDICT(self): """Return page content as a Python dict of images and text characters.""" return self._textpage_dict(raw=True) + + def __del__(self): + if not type(self) is TextPage: return + self.__swig_destroy__(self) %} } }; @@ -8252,6 +8351,12 @@ struct Graftmap } return (struct Graftmap *) map; } + %pythoncode %{ + def __del__(self): + if not type(self) is Graftmap: + return + self.__swig_destroy__(self) + %} } }; @@ -8261,8 +8366,7 @@ struct Graftmap //----------------------------------------------------------------------------- struct TextWriter { - %extend - { + %extend { ~TextWriter() { DEBUGMSG1("TextWriter"); @@ -8283,6 +8387,7 @@ struct TextWriter self.lastPoint.__doc__ = "Position following last text insertion." self.textRect = Rect(0, 0, -1, -1) self.textRect.__doc__ = "Accumulated area of text spans." + self.used_fonts = set() %} TextWriter(PyObject *page_rect, int opacity=1, PyObject *color=NULL ) { @@ -8307,6 +8412,8 @@ struct TextWriter self.lastPoint = Point(val[-2:]) * self.ctm self.textRect = self._bbox * self.ctm val = self.textRect, self.lastPoint + if font.flags["mono"] == 1: + self.used_fonts.add(font) %} PyObject * append(PyObject *pos, char *text, struct Font *font=NULL, float fontsize=11, char *language=NULL, int wmode=0, int bidi_level=0) @@ -8333,7 +8440,7 @@ struct TextWriter FITZEXCEPTION(writeText, !result) %pythonprepend writeText%{ - """Write the text to a PDF page with the TextWriter's page size. + """Write the text to a PDF page having the TextWriter's page size. Args: page: a PDF page having same size. @@ -8341,6 +8448,7 @@ struct TextWriter opacity: override transparency. overlay: put in foreground or background. morph: tuple(Point, Matrix), apply Matrix with fixpoint Point. + render_mode: (int) PDF render mode operator 'Tr'. """ CheckParent(page) @@ -8374,6 +8482,10 @@ struct TextWriter for line in old_cont_lines: if line.endswith(" cm"): continue + if line == "BT": + new_cont_lines.append(line) + new_cont_lines.append("%i Tr" % render_mode) + continue if line.endswith(" gs"): alp = int(line.split()[0][4:]) + max_alp line = "/Alp%i gs" % alp @@ -8381,13 +8493,21 @@ struct TextWriter temp = line.split() font = int(temp[0][2:]) + max_font line = " ".join(["/F%i" % font] + temp[1:]) + elif line.endswith(" rg"): + new_cont_lines.append(line.replace("rg", "RG")) + elif line.endswith(" g"): + new_cont_lines.append(line.replace(" g", " G")) + elif line.endswith(" k"): + new_cont_lines.append(line.replace(" k", " K")) new_cont_lines.append(line) new_cont_lines.append("Q\n") content = "\n".join(new_cont_lines).encode("utf-8") TOOLS._insert_contents(page, content, overlay=overlay) val = None + for font in self.used_fonts: + repair_mono_font(page, font) %} - PyObject *writeText(struct Page *page, PyObject *color=NULL, float opacity=-1, int overlay=1, PyObject *morph=NULL) + PyObject *writeText(struct Page *page, PyObject *color=NULL, float opacity=-1, int overlay=1, PyObject *morph=NULL, int render_mode=0) { pdf_page *pdfpage = pdf_page_from_fz_page(gctx, (fz_page *) page); fz_rect mediabox = fz_bound_page(gctx, (fz_page *) page); @@ -8435,6 +8555,12 @@ struct TextWriter } return result; } + %pythoncode %{ + def __del__(self): + if not type(self) is TextWriter: + return + self.__swig_destroy__(self) + %} } }; @@ -8462,13 +8588,24 @@ struct Font ordering = ("china-t", "china-s", "japan", "korea","china-ts", "china-ss", "japan-s", "korea-s").index(fontname.lower()) % 4 except ValueError: ordering = -1 - if ordering < 0: + if fontname.lower().startswith(("fig", "fim")): + try: + import pymupdf_fonts # optional fonts + fontbuffer = pymupdf_fonts.myfont(fontname)[:] # make a copy + fontname = None # ensure using fontbuffer only + del pymupdf_fonts # remove package again + except Exception as exc: + if repr(exc).startswith(("ImportError", "AttributeError")): + raise ImportError("Optional package 'pymupdf_fonts' not installed") + else: + raise exc + elif ordering < 0: fontname = Base14_fontdict.get(fontname.lower(), fontname) %} Font(char *fontname=NULL, char *fontfile=NULL, - PyObject *fontbuffer=NULL, int script=0, - char *language=NULL, int ordering=-1, int is_bold=0, - int is_italic=0, int is_serif=0) + PyObject *fontbuffer=NULL, int script=0, + char *language=NULL, int ordering=-1, int is_bold=0, + int is_italic=0, int is_serif=0) { fz_font *font = NULL; fz_try(gctx) { @@ -8525,6 +8662,7 @@ struct Font Py_RETURN_FALSE; } + %pythoncode %{@property%} PyObject *flags() { @@ -8567,6 +8705,11 @@ struct Font def __repr__(self): return "Font('%s')" % self.name + + def __del__(self): + if type(self) is not Font: + return None + self.__swig_destroy__(self) %} } }; @@ -9040,6 +9183,35 @@ struct Tools } + FITZEXCEPTION(set_font_width, !result) + PyObject * + set_font_width(struct Document *doc, int xref, int width) + { + pdf_document *pdf = pdf_specifics(gctx, (fz_document *) doc); + if (!pdf) Py_RETURN_FALSE; + pdf_obj *font=NULL, *dfonts=NULL; + fz_try(gctx) { + font = pdf_load_object(gctx, pdf, xref); + dfonts = pdf_dict_get(gctx, font, PDF_NAME(DescendantFonts)); + if (pdf_is_array(gctx, dfonts)) { + int i, n = pdf_array_len(gctx, dfonts); + for (i = 0; i < n; i++) { + pdf_obj *dfont = pdf_array_get(gctx, dfonts, i); + pdf_obj *warray = pdf_new_array(gctx, pdf, 3); + pdf_array_push(gctx, warray, pdf_new_int(gctx, 0)); + pdf_array_push(gctx, warray, pdf_new_int(gctx, 65532)); + pdf_array_push(gctx, warray, pdf_new_int(gctx, width)); + pdf_dict_put_drop(gctx, dfont, PDF_NAME(W), warray); + } + } + } + fz_catch(gctx) { + return NULL; + } + Py_RETURN_TRUE; + } + + %pythoncode %{ def _le_annot_parms(self, annot, p1, p2, fill_color): """Get common parameters for making line end symbols. diff --git a/fitz/helper-other.i b/fitz/helper-other.i index 42cfc4224..e04bb8504 100644 --- a/fitz/helper-other.i +++ b/fitz/helper-other.i @@ -432,7 +432,8 @@ char *JM_Python_str_AsChar(PyObject *str) // Modified copy of function of pdfmerge.c: we also copy annotations, but // we skip **link** annotations. In addition we rotate output. //---------------------------------------------------------------------------- -static void page_merge(fz_context *ctx, pdf_document *doc_des, pdf_document *doc_src, int page_from, int page_to, int rotate, int links, int copy_annots, pdf_graft_map *graft_map) +static void +page_merge(fz_context *ctx, pdf_document *doc_des, pdf_document *doc_src, int page_from, int page_to, int rotate, int links, int copy_annots, pdf_graft_map *graft_map) { pdf_obj *page_ref = NULL; pdf_obj *page_dict = NULL; @@ -469,24 +470,26 @@ static void page_merge(fz_context *ctx, pdf_document *doc_des, pdf_document *doc pdf_dict_put_drop(ctx, page_dict, known_page_objs[i], pdf_graft_mapped_object(ctx, graft_map, obj)); } - if (copy_annots) { // we shall copy annotations also + // Copy the annotations, but skip types Link and Popup. + // Also skip IRT annotations ("in response to"). + // Remove dict keys P (parent) and Popup from copyied annot. + if (copy_annots) { pdf_obj *old_annots = pdf_dict_get(ctx, page_ref, PDF_NAME(Annots)); if (old_annots) { n = pdf_array_len(ctx, old_annots); pdf_obj *new_annots = pdf_new_array(ctx, doc_des, n); for (i = 0; i < n; i++) { pdf_obj *o = pdf_array_get(ctx, old_annots, i); - if (!pdf_name_eq(ctx, pdf_dict_get(ctx, o, PDF_NAME(Subtype)), - PDF_NAME(Link))) { - pdf_array_push_drop(ctx, new_annots, - pdf_graft_mapped_object(ctx, graft_map, o)); - } - } - if (pdf_array_len(ctx, new_annots)) { - pdf_dict_put_drop(ctx, page_dict, PDF_NAME(Annots), new_annots); - } else { - pdf_drop_obj(ctx, new_annots); + pdf_obj *subtype = pdf_dict_get(ctx, o, PDF_NAME(Subtype)); + if (pdf_name_eq(ctx, subtype, PDF_NAME(Link))) continue; + if (pdf_name_eq(ctx, subtype, PDF_NAME(Popup))) continue; + if (pdf_dict_gets(ctx, o, "IRT")) continue; + pdf_obj *copy_o = pdf_graft_mapped_object(ctx, graft_map, o); + pdf_dict_del(gctx, copy_o, PDF_NAME(Popup)); + pdf_dict_del(gctx, copy_o, PDF_NAME(P)); + pdf_array_push_drop(ctx, new_annots, copy_o); } + pdf_dict_put_drop(ctx, page_dict, PDF_NAME(Annots), new_annots); } } // rotate the page as requested @@ -699,46 +702,6 @@ pdf_obj *JM_pdf_obj_from_str(fz_context *ctx, pdf_document *doc, char *src) } -//----------------------------------------------------------------------------- -// dummy structure for various tools and utilities -//----------------------------------------------------------------------------- -struct Tools {int index;}; - -typedef struct fz_item fz_item; - -struct fz_item -{ - void *key; - fz_storable *val; - size_t size; - fz_item *next; - fz_item *prev; - fz_store *store; - const fz_store_type *type; -}; - -struct fz_store -{ - int refs; - - /* Every item in the store is kept in a doubly linked list, ordered - * by usage (so LRU entries are at the end). */ - fz_item *head; - fz_item *tail; - - /* We have a hash table that allows to quickly find a subset of the - * entries (those whose keys are indirect objects). */ - fz_hash_table *hash; - - /* We keep track of the size of the store, and keep it below max. */ - size_t max; - size_t size; - - int defer_reap_count; - int needs_reaping; -}; - - //---------------------------------------------------------------------------- // return normalized /Rotate value //---------------------------------------------------------------------------- @@ -860,4 +823,46 @@ fz_matrix JM_derotate_page_matrix(fz_context *ctx, pdf_page *page) { // just the inverse of rotation return fz_invert_matrix(JM_rotate_page_matrix(ctx, page)); } + + +//----------------------------------------------------------------------------- +// dummy structure for various tools and utilities +//----------------------------------------------------------------------------- +struct Tools {int index;}; + +typedef struct fz_item fz_item; + +struct fz_item +{ + void *key; + fz_storable *val; + size_t size; + fz_item *next; + fz_item *prev; + fz_store *store; + const fz_store_type *type; +}; + +struct fz_store +{ + int refs; + + /* Every item in the store is kept in a doubly linked list, ordered + * by usage (so LRU entries are at the end). */ + fz_item *head; + fz_item *tail; + + /* We have a hash table that allows to quickly find a subset of the + * entries (those whose keys are indirect objects). */ + fz_hash_table *hash; + + /* We keep track of the size of the store, and keep it below max. */ + size_t max; + size_t size; + + int defer_reap_count; + int needs_reaping; +}; + + %} diff --git a/fitz/helper-pixmap.i b/fitz/helper-pixmap.i index 042e661b3..815d40d1a 100644 --- a/fitz/helper-pixmap.i +++ b/fitz/helper-pixmap.i @@ -309,13 +309,11 @@ JM_pixmap_from_page(fz_context *ctx, else for (i = 0; i < n; i++) fz_set_separation_behavior(ctx, seps, i, FZ_SEPARATION_COMPOSITE); - } - else if (fz_page_uses_overprint(ctx, page)) { + } else if (fz_page_uses_overprint(ctx, page)) { /* This page uses overprint, so we need an empty * sep object to force the overprint simulation on. */ seps = fz_new_separations(ctx, 0); - } - else if (oi && fz_colorspace_n(ctx, oi) != fz_colorspace_n(ctx, colorspace)) { + } else if (oi && fz_colorspace_n(ctx, oi) != fz_colorspace_n(ctx, colorspace)) { /* We have an output intent, and it's incompatible * with the colorspace our device needs. Force the * overprint simulation on, because this ensures that @@ -328,16 +326,14 @@ JM_pixmap_from_page(fz_context *ctx, if (alpha) { fz_clear_pixmap(ctx, pix); - } - else { + } else { fz_clear_pixmap_with_value(ctx, pix, 0xFF); } dev = fz_new_draw_device(ctx, matrix, pix); if (annots) { fz_run_page(ctx, page, dev, fz_identity, NULL); - } - else { + } else { fz_run_page_contents(ctx, page, dev, fz_identity, NULL); } fz_close_device(ctx, dev); diff --git a/fitz/helper-python.i b/fitz/helper-python.i index ca23ab27e..810ec970c 100644 --- a/fitz/helper-python.i +++ b/fitz/helper-python.i @@ -1,13 +1,13 @@ %pythoncode %{ -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ # link kinds and link flags -#------------------------------------------------------------------------------ -LINK_NONE = 0 -LINK_GOTO = 1 -LINK_URI = 2 +# ------------------------------------------------------------------------------ +LINK_NONE = 0 +LINK_GOTO = 1 +LINK_URI = 2 LINK_LAUNCH = 3 -LINK_NAMED = 4 -LINK_GOTOR = 5 +LINK_NAMED = 4 +LINK_GOTOR = 5 LINK_FLAG_L_VALID = 1 LINK_FLAG_T_VALID = 2 LINK_FLAG_R_VALID = 4 @@ -16,57 +16,68 @@ LINK_FLAG_FIT_H = 16 LINK_FLAG_FIT_V = 32 LINK_FLAG_R_IS_ZOOM = 64 -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ # Text handling flags -#------------------------------------------------------------------------------ -TEXT_ALIGN_LEFT = 0 -TEXT_ALIGN_CENTER = 1 -TEXT_ALIGN_RIGHT = 2 -TEXT_ALIGN_JUSTIFY = 3 - -TEXT_OUTPUT_TEXT = 0 -TEXT_OUTPUT_HTML = 1 -TEXT_OUTPUT_JSON = 2 -TEXT_OUTPUT_XML = 3 -TEXT_OUTPUT_XHTML = 4 - -TEXT_PRESERVE_LIGATURES = 1 +# ------------------------------------------------------------------------------ +TEXT_ALIGN_LEFT = 0 +TEXT_ALIGN_CENTER = 1 +TEXT_ALIGN_RIGHT = 2 +TEXT_ALIGN_JUSTIFY = 3 + +TEXT_OUTPUT_TEXT = 0 +TEXT_OUTPUT_HTML = 1 +TEXT_OUTPUT_JSON = 2 +TEXT_OUTPUT_XML = 3 +TEXT_OUTPUT_XHTML = 4 + +TEXT_PRESERVE_LIGATURES = 1 TEXT_PRESERVE_WHITESPACE = 2 -TEXT_PRESERVE_IMAGES = 4 -TEXT_INHIBIT_SPACES = 8 +TEXT_PRESERVE_IMAGES = 4 +TEXT_INHIBIT_SPACES = 8 -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ # Simple text encoding options -#------------------------------------------------------------------------------ -TEXT_ENCODING_LATIN = 0 -TEXT_ENCODING_GREEK = 1 +# ------------------------------------------------------------------------------ +TEXT_ENCODING_LATIN = 0 +TEXT_ENCODING_GREEK = 1 TEXT_ENCODING_CYRILLIC = 2 -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ # Stamp annotation icon numbers -#------------------------------------------------------------------------------ -STAMP_Approved = 0 -STAMP_AsIs = 1 -STAMP_Confidential = 2 -STAMP_Departmental = 3 -STAMP_Experimental = 4 -STAMP_Expired = 5 -STAMP_Final = 6 -STAMP_ForComment = 7 -STAMP_ForPublicRelease = 8 -STAMP_NotApproved = 9 +# ------------------------------------------------------------------------------ +STAMP_Approved = 0 +STAMP_AsIs = 1 +STAMP_Confidential = 2 +STAMP_Departmental = 3 +STAMP_Experimental = 4 +STAMP_Expired = 5 +STAMP_Final = 6 +STAMP_ForComment = 7 +STAMP_ForPublicRelease = 8 +STAMP_NotApproved = 9 STAMP_NotForPublicRelease = 10 -STAMP_Sold = 11 -STAMP_TopSecret = 12 -STAMP_Draft = 13 +STAMP_Sold = 11 +STAMP_TopSecret = 12 +STAMP_Draft = 13 -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ # Base 14 font names and dictionary -#------------------------------------------------------------------------------ -Base14_fontnames = ("Courier", "Courier-Oblique", "Courier-Bold", - "Courier-BoldOblique", "Helvetica", "Helvetica-Oblique", - "Helvetica-Bold", "Helvetica-BoldOblique", - "Times-Roman", "Times-Italic", "Times-Bold", - "Times-BoldItalic", "Symbol", "ZapfDingbats") +# ------------------------------------------------------------------------------ +Base14_fontnames = ( + "Courier", + "Courier-Oblique", + "Courier-Bold", + "Courier-BoldOblique", + "Helvetica", + "Helvetica-Oblique", + "Helvetica-Bold", + "Helvetica-BoldOblique", + "Times-Roman", + "Times-Italic", + "Times-Bold", + "Times-BoldItalic", + "Symbol", + "ZapfDingbats", +) Base14_fontdict = {} for f in Base14_fontnames: @@ -96,6 +107,7 @@ annot_skel = { "named": "<>/Rect[%s]/BS<>/Subtype/Link>>", } + def _toc_remove_page(toc, first, last): """ Remove all ToC entries pointing to certain pages. @@ -161,129 +173,553 @@ def getTextlength(text, fontname="helv", fontsize=11, encoding=0): return w * fontsize if fontname in Base14_fontdict.keys(): - return TOOLS._measure_string(text, Base14_fontdict[fontname], fontsize, encoding) - - if fontname in ("china-t", "china-s", - "china-ts", "china-ss", - "japan", "japan-s", - "korea", "korea-s"): + return TOOLS._measure_string( + text, Base14_fontdict[fontname], fontsize, encoding + ) + + if fontname in ( + "china-t", + "china-s", + "china-ts", + "china-ss", + "japan", + "japan-s", + "korea", + "korea-s", + ): return len(text) * fontsize raise ValueError("Font '%s' is unsupported" % fontname) -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ # Glyph list for the built-in font 'ZapfDingbats' -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ zapf_glyphs = ( - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), - (183, 0.788), (183, 0.788), (32, 0.278), (33, 0.974), (34, 0.961), (35, 0.974), - (36, 0.98), (37, 0.719), (38, 0.789), (39, 0.79), (40, 0.791), (41, 0.69), - (42, 0.96), (43, 0.939), (44, 0.549), (45, 0.855), (46, 0.911), (47, 0.933), - (48, 0.911), (49, 0.945), (50, 0.974), (51, 0.755), (52, 0.846), (53, 0.762), - (54, 0.761), (55, 0.571), (56, 0.677), (57, 0.763), (58, 0.76), (59, 0.759), - (60, 0.754), (61, 0.494), (62, 0.552), (63, 0.537), (64, 0.577), (65, 0.692), - (66, 0.786), (67, 0.788), (68, 0.788), (69, 0.79), (70, 0.793), (71, 0.794), - (72, 0.816), (73, 0.823), (74, 0.789), (75, 0.841), (76, 0.823), (77, 0.833), - (78, 0.816), (79, 0.831), (80, 0.923), (81, 0.744), (82, 0.723), (83, 0.749), - (84, 0.79), (85, 0.792), (86, 0.695), (87, 0.776), (88, 0.768), (89, 0.792), - (90, 0.759), (91, 0.707), (92, 0.708), (93, 0.682), (94, 0.701), (95, 0.826), - (96, 0.815), (97, 0.789), (98, 0.789), (99, 0.707), (100, 0.687), (101, 0.696), - (102, 0.689), (103, 0.786), (104, 0.787), (105, 0.713), (106, 0.791), - (107, 0.785), (108, 0.791), (109, 0.873), (110, 0.761), (111, 0.762), - (112, 0.762), (113, 0.759), (114, 0.759), (115, 0.892), (116, 0.892), - (117, 0.788), (118, 0.784), (119, 0.438), (120, 0.138), (121, 0.277), - (122, 0.415), (123, 0.392), (124, 0.392), (125, 0.668), (126, 0.668), - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), - (183, 0.788), (183, 0.788), (183, 0.788), (183, 0.788), (161, 0.732), (162, 0.544), - (163, 0.544), (164, 0.91), (165, 0.667), (166, 0.76), (167, 0.76), - (168, 0.776), (169, 0.595), (170, 0.694), (171, 0.626), (172, 0.788), - (173, 0.788), (174, 0.788), (175, 0.788), (176, 0.788), (177, 0.788), - (178, 0.788), (179, 0.788), (180, 0.788), (181, 0.788), (182, 0.788), - (183, 0.788), (184, 0.788), (185, 0.788), (186, 0.788), (187, 0.788), - (188, 0.788), (189, 0.788), (190, 0.788), (191, 0.788), (192, 0.788), - (193, 0.788), (194, 0.788), (195, 0.788), (196, 0.788), (197, 0.788), - (198, 0.788), (199, 0.788), (200, 0.788), (201, 0.788), (202, 0.788), - (203, 0.788), (204, 0.788), (205, 0.788), (206, 0.788), (207, 0.788), - (208, 0.788), (209, 0.788), (210, 0.788), (211, 0.788), (212, 0.894), - (213, 0.838), (214, 1.016), (215, 0.458), (216, 0.748), (217, 0.924), - (218, 0.748), (219, 0.918), (220, 0.927), (221, 0.928), (222, 0.928), - (223, 0.834), (224, 0.873), (225, 0.828), (226, 0.924), (227, 0.924), - (228, 0.917), (229, 0.93), (230, 0.931), (231, 0.463), (232, 0.883), - (233, 0.836), (234, 0.836), (235, 0.867), (236, 0.867), (237, 0.696), - (238, 0.696), (239, 0.874), (183, 0.788), (241, 0.874), (242, 0.76), - (243, 0.946), (244, 0.771), (245, 0.865), (246, 0.771), (247, 0.888), - (248, 0.967), (249, 0.888), (250, 0.831), (251, 0.873), (252, 0.927), - (253, 0.97), (183, 0.788), (183, 0.788) - ) - -#------------------------------------------------------------------------------ + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (32, 0.278), + (33, 0.974), + (34, 0.961), + (35, 0.974), + (36, 0.98), + (37, 0.719), + (38, 0.789), + (39, 0.79), + (40, 0.791), + (41, 0.69), + (42, 0.96), + (43, 0.939), + (44, 0.549), + (45, 0.855), + (46, 0.911), + (47, 0.933), + (48, 0.911), + (49, 0.945), + (50, 0.974), + (51, 0.755), + (52, 0.846), + (53, 0.762), + (54, 0.761), + (55, 0.571), + (56, 0.677), + (57, 0.763), + (58, 0.76), + (59, 0.759), + (60, 0.754), + (61, 0.494), + (62, 0.552), + (63, 0.537), + (64, 0.577), + (65, 0.692), + (66, 0.786), + (67, 0.788), + (68, 0.788), + (69, 0.79), + (70, 0.793), + (71, 0.794), + (72, 0.816), + (73, 0.823), + (74, 0.789), + (75, 0.841), + (76, 0.823), + (77, 0.833), + (78, 0.816), + (79, 0.831), + (80, 0.923), + (81, 0.744), + (82, 0.723), + (83, 0.749), + (84, 0.79), + (85, 0.792), + (86, 0.695), + (87, 0.776), + (88, 0.768), + (89, 0.792), + (90, 0.759), + (91, 0.707), + (92, 0.708), + (93, 0.682), + (94, 0.701), + (95, 0.826), + (96, 0.815), + (97, 0.789), + (98, 0.789), + (99, 0.707), + (100, 0.687), + (101, 0.696), + (102, 0.689), + (103, 0.786), + (104, 0.787), + (105, 0.713), + (106, 0.791), + (107, 0.785), + (108, 0.791), + (109, 0.873), + (110, 0.761), + (111, 0.762), + (112, 0.762), + (113, 0.759), + (114, 0.759), + (115, 0.892), + (116, 0.892), + (117, 0.788), + (118, 0.784), + (119, 0.438), + (120, 0.138), + (121, 0.277), + (122, 0.415), + (123, 0.392), + (124, 0.392), + (125, 0.668), + (126, 0.668), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (183, 0.788), + (161, 0.732), + (162, 0.544), + (163, 0.544), + (164, 0.91), + (165, 0.667), + (166, 0.76), + (167, 0.76), + (168, 0.776), + (169, 0.595), + (170, 0.694), + (171, 0.626), + (172, 0.788), + (173, 0.788), + (174, 0.788), + (175, 0.788), + (176, 0.788), + (177, 0.788), + (178, 0.788), + (179, 0.788), + (180, 0.788), + (181, 0.788), + (182, 0.788), + (183, 0.788), + (184, 0.788), + (185, 0.788), + (186, 0.788), + (187, 0.788), + (188, 0.788), + (189, 0.788), + (190, 0.788), + (191, 0.788), + (192, 0.788), + (193, 0.788), + (194, 0.788), + (195, 0.788), + (196, 0.788), + (197, 0.788), + (198, 0.788), + (199, 0.788), + (200, 0.788), + (201, 0.788), + (202, 0.788), + (203, 0.788), + (204, 0.788), + (205, 0.788), + (206, 0.788), + (207, 0.788), + (208, 0.788), + (209, 0.788), + (210, 0.788), + (211, 0.788), + (212, 0.894), + (213, 0.838), + (214, 1.016), + (215, 0.458), + (216, 0.748), + (217, 0.924), + (218, 0.748), + (219, 0.918), + (220, 0.927), + (221, 0.928), + (222, 0.928), + (223, 0.834), + (224, 0.873), + (225, 0.828), + (226, 0.924), + (227, 0.924), + (228, 0.917), + (229, 0.93), + (230, 0.931), + (231, 0.463), + (232, 0.883), + (233, 0.836), + (234, 0.836), + (235, 0.867), + (236, 0.867), + (237, 0.696), + (238, 0.696), + (239, 0.874), + (183, 0.788), + (241, 0.874), + (242, 0.76), + (243, 0.946), + (244, 0.771), + (245, 0.865), + (246, 0.771), + (247, 0.888), + (248, 0.967), + (249, 0.888), + (250, 0.831), + (251, 0.873), + (252, 0.927), + (253, 0.97), + (183, 0.788), + (183, 0.788), +) + +# ------------------------------------------------------------------------------ # Glyph list for the built-in font 'Symbol' -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ symbol_glyphs = ( - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (32, 0.25), (33, 0.333), (34, 0.713), - (35, 0.5), (36, 0.549), (37, 0.833), (38, 0.778), (39, 0.439), - (40, 0.333), (41, 0.333), (42, 0.5), (43, 0.549), (44, 0.25), (45, 0.549), - (46, 0.25), (47, 0.278), (48, 0.5), (49, 0.5), (50, 0.5), (51, 0.5), - (52, 0.5), (53, 0.5), (54, 0.5), (55, 0.5), (56, 0.5), (57, 0.5), - (58, 0.278), (59, 0.278), (60, 0.549), (61, 0.549), (62, 0.549), - (63, 0.444), (64, 0.549), (65, 0.722), (66, 0.667), (67, 0.722), - (68, 0.612), (69, 0.611), (70, 0.763), (71, 0.603), (72, 0.722), - (73, 0.333), (74, 0.631), (75, 0.722), (76, 0.686), (77, 0.889), - (78, 0.722), (79, 0.722), (80, 0.768), (81, 0.741), (82, 0.556), - (83, 0.592), (84, 0.611), (85, 0.69), (86, 0.439), (87, 0.768), - (88, 0.645), (89, 0.795), (90, 0.611), (91, 0.333), (92, 0.863), - (93, 0.333), (94, 0.658), (95, 0.5), (96, 0.5), (97, 0.631), (98, 0.549), - (99, 0.549), (100, 0.494), (101, 0.439), (102, 0.521), (103, 0.411), - (104, 0.603), (105, 0.329), (106, 0.603), (107, 0.549), (108, 0.549), - (109, 0.576), (110, 0.521), (111, 0.549), (112, 0.549), (113, 0.521), - (114, 0.549), (115, 0.603), (116, 0.439), (117, 0.576), (118, 0.713), - (119, 0.686), (120, 0.493), (121, 0.686), (122, 0.494), (123, 0.48), - (124, 0.2), (125, 0.48), (126, 0.549), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), (183, 0.46), - (183, 0.46), (160, 0.25), (161, 0.62), (162, 0.247), (163, 0.549), - (164, 0.167), (165, 0.713), (166, 0.5), (167, 0.753), (168, 0.753), - (169, 0.753), (170, 0.753), (171, 1.042), (172, 0.713), (173, 0.603), - (174, 0.987), (175, 0.603), (176, 0.4), (177, 0.549), (178, 0.411), - (179, 0.549), (180, 0.549), (181, 0.576), (182, 0.494), (183, 0.46), - (184, 0.549), (185, 0.549), (186, 0.549), (187, 0.549), (188, 1), - (189, 0.603), (190, 1), (191, 0.658), (192, 0.823), (193, 0.686), - (194, 0.795), (195, 0.987), (196, 0.768), (197, 0.768), (198, 0.823), - (199, 0.768), (200, 0.768), (201, 0.713), (202, 0.713), (203, 0.713), - (204, 0.713), (205, 0.713), (206, 0.713), (207, 0.713), (208, 0.768), - (209, 0.713), (210, 0.79), (211, 0.79), (212, 0.89), (213, 0.823), - (214, 0.549), (215, 0.549), (216, 0.713), (217, 0.603), (218, 0.603), - (219, 1.042), (220, 0.987), (221, 0.603), (222, 0.987), (223, 0.603), - (224, 0.494), (225, 0.329), (226, 0.79), (227, 0.79), (228, 0.786), - (229, 0.713), (230, 0.384), (231, 0.384), (232, 0.384), (233, 0.384), - (234, 0.384), (235, 0.384), (236, 0.494), (237, 0.494), (238, 0.494), - (239, 0.494), (183, 0.46), (241, 0.329), (242, 0.274), (243, 0.686), - (244, 0.686), (245, 0.686), (246, 0.384), (247, 0.549), (248, 0.384), - (249, 0.384), (250, 0.384), (251, 0.384), (252, 0.494), (253, 0.494), - (254, 0.494), (183, 0.46) - ) + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (32, 0.25), + (33, 0.333), + (34, 0.713), + (35, 0.5), + (36, 0.549), + (37, 0.833), + (38, 0.778), + (39, 0.439), + (40, 0.333), + (41, 0.333), + (42, 0.5), + (43, 0.549), + (44, 0.25), + (45, 0.549), + (46, 0.25), + (47, 0.278), + (48, 0.5), + (49, 0.5), + (50, 0.5), + (51, 0.5), + (52, 0.5), + (53, 0.5), + (54, 0.5), + (55, 0.5), + (56, 0.5), + (57, 0.5), + (58, 0.278), + (59, 0.278), + (60, 0.549), + (61, 0.549), + (62, 0.549), + (63, 0.444), + (64, 0.549), + (65, 0.722), + (66, 0.667), + (67, 0.722), + (68, 0.612), + (69, 0.611), + (70, 0.763), + (71, 0.603), + (72, 0.722), + (73, 0.333), + (74, 0.631), + (75, 0.722), + (76, 0.686), + (77, 0.889), + (78, 0.722), + (79, 0.722), + (80, 0.768), + (81, 0.741), + (82, 0.556), + (83, 0.592), + (84, 0.611), + (85, 0.69), + (86, 0.439), + (87, 0.768), + (88, 0.645), + (89, 0.795), + (90, 0.611), + (91, 0.333), + (92, 0.863), + (93, 0.333), + (94, 0.658), + (95, 0.5), + (96, 0.5), + (97, 0.631), + (98, 0.549), + (99, 0.549), + (100, 0.494), + (101, 0.439), + (102, 0.521), + (103, 0.411), + (104, 0.603), + (105, 0.329), + (106, 0.603), + (107, 0.549), + (108, 0.549), + (109, 0.576), + (110, 0.521), + (111, 0.549), + (112, 0.549), + (113, 0.521), + (114, 0.549), + (115, 0.603), + (116, 0.439), + (117, 0.576), + (118, 0.713), + (119, 0.686), + (120, 0.493), + (121, 0.686), + (122, 0.494), + (123, 0.48), + (124, 0.2), + (125, 0.48), + (126, 0.549), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (183, 0.46), + (160, 0.25), + (161, 0.62), + (162, 0.247), + (163, 0.549), + (164, 0.167), + (165, 0.713), + (166, 0.5), + (167, 0.753), + (168, 0.753), + (169, 0.753), + (170, 0.753), + (171, 1.042), + (172, 0.713), + (173, 0.603), + (174, 0.987), + (175, 0.603), + (176, 0.4), + (177, 0.549), + (178, 0.411), + (179, 0.549), + (180, 0.549), + (181, 0.576), + (182, 0.494), + (183, 0.46), + (184, 0.549), + (185, 0.549), + (186, 0.549), + (187, 0.549), + (188, 1), + (189, 0.603), + (190, 1), + (191, 0.658), + (192, 0.823), + (193, 0.686), + (194, 0.795), + (195, 0.987), + (196, 0.768), + (197, 0.768), + (198, 0.823), + (199, 0.768), + (200, 0.768), + (201, 0.713), + (202, 0.713), + (203, 0.713), + (204, 0.713), + (205, 0.713), + (206, 0.713), + (207, 0.713), + (208, 0.768), + (209, 0.713), + (210, 0.79), + (211, 0.79), + (212, 0.89), + (213, 0.823), + (214, 0.549), + (215, 0.549), + (216, 0.713), + (217, 0.603), + (218, 0.603), + (219, 1.042), + (220, 0.987), + (221, 0.603), + (222, 0.987), + (223, 0.603), + (224, 0.494), + (225, 0.329), + (226, 0.79), + (227, 0.79), + (228, 0.786), + (229, 0.713), + (230, 0.384), + (231, 0.384), + (232, 0.384), + (233, 0.384), + (234, 0.384), + (235, 0.384), + (236, 0.494), + (237, 0.494), + (238, 0.494), + (239, 0.494), + (183, 0.46), + (241, 0.329), + (242, 0.274), + (243, 0.686), + (244, 0.686), + (245, 0.686), + (246, 0.384), + (247, 0.549), + (248, 0.384), + (249, 0.384), + (250, 0.384), + (251, 0.384), + (252, 0.494), + (253, 0.494), + (254, 0.494), + (183, 0.46), +) + class linkDest(object): """link or outline destination details""" + def __init__(self, obj, rlink): isExt = obj.isExternal isInt = not isExt @@ -300,7 +736,7 @@ class linkDest(object): self.rb = Point(0, 0) self.uri = obj.uri if rlink and not self.uri.startswith("#"): - self.uri = "#%i,%g,%g" % (rlink[0]+1, rlink[1], rlink[2]) + self.uri = "#%i,%g,%g" % (rlink[0] + 1, rlink[1], rlink[2]) if obj.isExternal: self.page = -1 self.kind = LINK_URI @@ -344,13 +780,17 @@ class linkDest(object): self.isUri = True self.kind = LINK_LAUNCH -#------------------------------------------------------------------------------- + +# ------------------------------------------------------------------------------- # "Now" timestamp in PDF Format -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------- def getPDFnow(): import time - tz = "%s'%s'" % (str(abs(time.altzone // 3600)).rjust(2, "0"), - str((abs(time.altzone // 60)%60)).rjust(2, "0")) + + tz = "%s'%s'" % ( + str(abs(time.altzone // 3600)).rjust(2, "0"), + str((abs(time.altzone // 60) % 60)).rjust(2, "0"), + ) tstamp = time.strftime("D:%Y%m%d%H%M%S", time.localtime()) if time.altzone > 0: tstamp += "-" + tz @@ -379,7 +819,6 @@ def getPDFstr(s): t = r if fitz_py2 else r.decode() return "<" + t + ">" # brackets indicate hex - # The following either returns the original string with mixed-in # octal numbers \nnn for chars outside the ASCII range, or returns # the UTF-16BE BOM version of the string. @@ -429,7 +868,7 @@ def getTJstr(text, glyphs, simple, ordering): not simple: ordering < 0: 4-chars, use glyphs not char codes ordering >=0: a CJK font! 4 chars, use char codes as glyphs """ - if text.startswith("[<") and text.endswith(">]"): # already done + if text.startswith("[<") and text.endswith(">]"): # already done return text if not bool(text): @@ -439,7 +878,9 @@ def getTJstr(text, glyphs, simple, ordering): if glyphs is None: # not Symbol, not ZapfDingbats: use char code otxt = "".join(["%02x" % ord(c) if ord(c) < 256 else "b7" for c in text]) else: # Symbol or ZapfDingbats: use glyphs - otxt = "".join(["%02x" % glyphs[ord(c)][0] if ord(c) < 256 else "b7" for c in text]) + otxt = "".join( + ["%02x" % glyphs[ord(c)][0] if ord(c) < 256 else "b7" for c in text] + ) return "[<" + otxt + ">]" # non-simple fonts: each char or its glyph is coded as 4-byte hex @@ -450,58 +891,59 @@ def getTJstr(text, glyphs, simple, ordering): return "[<" + otxt + ">]" + """ Information taken from the following web sites: www.din-formate.de www.din-formate.info/amerikanische-formate.html www.directtools.de/wissen/normen/iso.htm """ -paperSizes = { # known paper formats @ 72 dpi - 'a0': (2384, 3370), - 'a1': (1684, 2384), - 'a10': (74, 105), - 'a2': (1191, 1684), - 'a3': (842, 1191), - 'a4': (595, 842), - 'a5': (420, 595), - 'a6': (298, 420), - 'a7': (210, 298), - 'a8': (147, 210), - 'a9': (105, 147), - 'b0': (2835, 4008), - 'b1': (2004, 2835), - 'b10': (88, 125), - 'b2': (1417, 2004), - 'b3': (1001, 1417), - 'b4': (709, 1001), - 'b5': (499, 709), - 'b6': (354, 499), - 'b7': (249, 354), - 'b8': (176, 249), - 'b9': (125, 176), - 'c0': (2599, 3677), - 'c1': (1837, 2599), - 'c10': (79, 113), - 'c2': (1298, 1837), - 'c3': (918, 1298), - 'c4': (649, 918), - 'c5': (459, 649), - 'c6': (323, 459), - 'c7': (230, 323), - 'c8': (162, 230), - 'c9': (113, 162), - 'card-4x6': (288, 432), - 'card-5x7': (360, 504), - 'commercial': (297, 684), - 'executive': (522, 756), - 'invoice': (396, 612), - 'ledger': (792, 1224), - 'legal': (612, 1008), - 'legal-13': (612, 936), - 'letter': (612, 792), - 'monarch': (279, 540), - 'tabloid-extra': (864, 1296), - } +paperSizes = { # known paper formats @ 72 dpi + "a0": (2384, 3370), + "a1": (1684, 2384), + "a10": (74, 105), + "a2": (1191, 1684), + "a3": (842, 1191), + "a4": (595, 842), + "a5": (420, 595), + "a6": (298, 420), + "a7": (210, 298), + "a8": (147, 210), + "a9": (105, 147), + "b0": (2835, 4008), + "b1": (2004, 2835), + "b10": (88, 125), + "b2": (1417, 2004), + "b3": (1001, 1417), + "b4": (709, 1001), + "b5": (499, 709), + "b6": (354, 499), + "b7": (249, 354), + "b8": (176, 249), + "b9": (125, 176), + "c0": (2599, 3677), + "c1": (1837, 2599), + "c10": (79, 113), + "c2": (1298, 1837), + "c3": (918, 1298), + "c4": (649, 918), + "c5": (459, 649), + "c6": (323, 459), + "c7": (230, 323), + "c8": (162, 230), + "c9": (113, 162), + "card-4x6": (288, 432), + "card-5x7": (360, 504), + "commercial": (297, 684), + "executive": (522, 756), + "invoice": (396, 612), + "ledger": (792, 1224), + "legal": (612, 1008), + "legal-13": (612, 936), + "letter": (612, 792), + "monarch": (279, 540), + "tabloid-extra": (864, 1296), +} def PaperSize(s): @@ -618,7 +1060,8 @@ def CheckMarkerArg(quads): def CheckMorph(o): - if not bool(o): return False + 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 (len(o[0]) == 2 and len(o[1]) == 6): @@ -658,6 +1101,7 @@ def UpdateFontInfo(doc, info): else: doc.FontInfos.append(info) + def DUMMY(*args, **kw): return @@ -671,7 +1115,7 @@ def planishLine(p1, p2): Matrix which maps p1 to Point(0,0) and p2 to a point on the x axis at the same distance to Point(0,0). Will always combine a rotation and a transformation. - """ + """ p1 = Point(p1) p2 = Point(p2) return Matrix(TOOLS._hor_matrix(p1, p2)) @@ -699,7 +1143,7 @@ def ImageProperties(img): return TOOLS.image_profile(stream) -def ConversionHeader(i, filename = "unknown"): +def ConversionHeader(i, filename="unknown"): t = i.lower() html = """ @@ -713,8 +1157,11 @@ img{position:absolute} \n""" - xml = """ -\n""" % filename + xml = ( + """ +\n""" + % filename + ) xhtml = """ @@ -764,6 +1211,7 @@ def ConversionTrailer(i): return r + def DerotateRect(cropbox, rect, deg): """Calculate the non-rotated rect version. @@ -784,7 +1232,7 @@ def DerotateRect(cropbox, rect, deg): return rect points = [] # store the new rect points here for p in rect.quad: # run through the rect's quad points - if deg == 90: + if deg == 90: q = (p.y, cropbox.height - p.x) elif deg == 270: q = (cropbox.width - p.y, p.x) @@ -874,7 +1322,13 @@ def get_highlight_selection(page, start=None, stop=None, clip=None): return rectangles + def annot_preprocess(page): + """Prepare for annotation insertion on the page. + + Returns: + Old page rotation value. Temporarily sets rotation to 0 when required. + """ CheckParent(page) if not page.parent.isPDF: raise ValueError("not a PDF") @@ -885,7 +1339,101 @@ def annot_preprocess(page): def annot_postprocess(page, annot): + """Clean up after annotation inertion. + + Set ownership flag and store annotation in page annotation dictionary. + """ annot.parent = weakref.proxy(page) page._annot_refs[id(annot)] = annot annot.thisown = True + + +def sRGB_to_pdf(srgb): + """Convert sRGB color code to PDF color triple. + + There is **no error checking** for performance reasons! + + Args: + srgb: (int) RRGGBB (red, green, blue), each color in range(255). + Returns: + Tuple (red, green, blue) each item in intervall 0 <= item <= 1. + """ + r = srgb >> 16 + g = (srgb - (r << 16)) >> 8 + b = srgb - (r << 16) - (g << 8) + return (r / 255.0, g / 255.0, b / 255.0) + + +def make_table(rect=(0, 0, 1, 1), cols=1, rows=1): + """Return a list of (rows x cols) equal sized rectangles. + + Notes: + A utility to fill a given area with table cells of equal size. + Args: + rect: rect_like to use as the table area + rows: number of rows + cols: number of columns + Returns: + A list with items, where each item is a list of + PyMuPDF Rect objects of equal sizes. + """ + rect = Rect(rect) # ensure this is a Rect + if rect.isEmpty or rect.isInfinite: + raise ValueError("rect must be finite and not empty") + tl = rect.tl + + height = rect.height / rows # height of one table cell + width = rect.width / cols # width of one table cell + delta_h = (width, 0, width, 0) # diff to next right rect + delta_v = (0, height, 0, height) # diff to next lower rect + + r = Rect(tl, tl.x + width, tl.y + height) # first rectangle + + # make the first row + row = [r] + for i in range(1, cols): + r += delta_h # build next rect to the right + row.append(r) + + # make result, starts with first row + rects = [row] + for i in range(1, rows): + row = rects[i - 1] # take previously appended row + nrow = [] # the new row to append + for r in row: # for each previous cell add its downward copy + nrow.append(r + delta_v) + rects.append(nrow) # append new row to result + + return rects + + +def repair_mono_font(page, font): + """Repair character spacing for mono fonts. + + Notes: + Some mono-spaced fonts are displayed with a too large character + distance, e.g. "a b c" instead of "abc". This utility adds an entry + "/W[0 65532 w]" to the descendent font(s) of font. + This should enforce viewers to use 'w' as the character width. + + Args: + page: fitz.Page object. + font: fitz.Font object. + """ + if not font.flags["mono"]: + return None + doc = page.parent + fontlist = page.getFontList() # list of fonts on page + xrefs = [ # list of objects referring to font + f[0] + for f in fontlist + if (f[3] == font.name and f[4].startswith("F") and f[5].startswith("Identity")) + ] + if xrefs == []: # our font does not occur + return + xrefs = set(xrefs) # drop any double counts + width = int(font.glyph_advance(32) * 1000) + for xref in xrefs: + if not TOOLS.set_font_width(doc, xref, width): + print("Could set width for '%s' in xref %i" % (font.name, xref)) %} diff --git a/fitz/helper-stext.i b/fitz/helper-stext.i index 356fe9e2e..9b515c6e6 100644 --- a/fitz/helper-stext.i +++ b/fitz/helper-stext.i @@ -379,26 +379,24 @@ void JM_make_textpage_dict(fz_context *ctx, fz_stext_page *tp, PyObject *page_di fz_drop_buffer(ctx, text_buffer); } -PyObject *JM_object_to_string(fz_context *ctx, pdf_obj *what, int compress, int ascii) + +fz_buffer *JM_object_to_buffer(fz_context *ctx, pdf_obj *what, int compress, int ascii) { fz_buffer *res=NULL; fz_output *out=NULL; - PyObject *text=NULL; fz_try(ctx) { res = fz_new_buffer(ctx, 1024); out = fz_new_output_with_buffer(ctx, res); pdf_print_obj(ctx, out, what, compress, ascii); - text = JM_EscapeStrFromBuffer(ctx, res); } fz_always(ctx) { fz_drop_output(ctx, out); - fz_drop_buffer(ctx, res); - PyErr_Clear(); } fz_catch(ctx) { - return PyUnicode_FromString(""); + return NULL; } - return text; + fz_terminate_buffer(gctx, res); + return res; } //----------------------------------------------------------------------------- @@ -458,10 +456,11 @@ PyObject *JM_merge_resources(fz_context *ctx, pdf_page *page, pdf_obj *temp_res) if (pdf_is_dict(ctx, main_fonts)) // has page any fonts yet? { for (i = 0; i < pdf_dict_len(ctx, main_fonts); i++) - { // get highest number of fonts named /F? + { // get highest number of fonts named /Fxxx char *font = (char *) pdf_to_name(ctx, pdf_dict_get_key(ctx, main_fonts, i)); if (strncmp(font, "F", 1) != 0) continue; - if (strcmp(start_str, font) < 0) strcpy(start_str, font); + if (strcmp(start_str, font) < 0 || strlen(start_str) < strlen(font)) + strcpy(start_str, font); } while (strcmp(text, start_str) < 0) { // compute next available number diff --git a/fitz/utils.py b/fitz/utils.py index 251304f48..8ec031fd5 100644 --- a/fitz/utils.py +++ b/fitz/utils.py @@ -480,7 +480,7 @@ def getText(page, option="text", flags=None): return t -def getPageText(doc, pno, option="text"): +def getPageText(doc, pno, option="text", flags=None): """ Extract a document page's text by page number. Notes: @@ -491,7 +491,7 @@ def getPageText(doc, pno, option="text"): Returns: output from page.TextPage(). """ - return doc[pno].getText(option) + return doc[pno].getText(option, flags=flags) def getPixmap(page, matrix=None, colorspace=csRGB, clip=None, alpha=False, annots=True): @@ -2218,6 +2218,13 @@ def getColorInfoList(): ] +def getColorInfoDict(): + d = {} + for item in getColorInfoList(): + d[item[0].lower()] = item[1:] + return d + + def getColor(name): """Retrieve RGB color in PDF format by name. @@ -2776,9 +2783,6 @@ def insertText( space = abs(point.y + self.y) headroom = height - point.y - self.y - if headroom < fontsize: # at least 1 full line space required! - raise ValueError("text starts outside page") - nres = templ1 % (cm, left, top, fname, fontsize) if render_mode > 0: nres += "%i Tr " % render_mode @@ -3118,7 +3122,8 @@ def finish( return if roundCap is not None: warnings.warn( - "roundCap is replaced by lineCap / lineJoin", DeprecationWarning + "roundCap replaced by lineCap / lineJoin and removed in next version", + DeprecationWarning, ) lineCap = lineJoin = roundCap @@ -3203,13 +3208,14 @@ def center_rect(annot_rect, text, font, fsize): """Calculate minimal sub-rectangle for the overlay text. Notes: - We will use 'insertTextbox', which supports no vertical text - centering. We calculate an approximate number of lines here and - return a sub-rectangle, which should still contain the text. + Because 'insertTextbox' supports no vertical text centering, + we calculate an approximate number of lines here and return a + sub-rect with smaller height, which should still be sufficient. Args: annot_rect: the annotation rectangle text: the text to insert. - font: the fontname. Must be one of CJK or Base-14 set. + font: the fontname. Must be one of the CJK or Base-14 set, else + the rectangle is returned unchanged. fsize: the fontsize Returns: A rectangle to use instead of the annot rectangle. diff --git a/fitz/version.i b/fitz/version.i index 6609a3095..803263443 100644 --- a/fitz/version.i +++ b/fitz/version.i @@ -1,6 +1,6 @@ %pythoncode %{ VersionFitz = "1.17.0" -VersionBind = "1.17.3" -VersionDate = "2020-07-06 15:07:04" -version = (VersionBind, VersionFitz, "20200706150704") +VersionBind = "1.17.4" +VersionDate = "2020-07-20 18:09:40" +version = (VersionBind, VersionFitz, "20200720180940") %} \ No newline at end of file diff --git a/setup.py b/setup.py index 23cb50342..cf77a177c 100644 --- a/setup.py +++ b/setup.py @@ -81,7 +81,7 @@ def run(self): setup( name="PyMuPDF", - version="1.17.3", + version="1.17.4", description="Python bindings for the PDF rendering library MuPDF", long_description=long_desc, classifiers=classifier,