Hello community, here is the log from the commit of package python-PyPDF2 for openSUSE:Factory checked in at 2017-04-24 09:49:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-PyPDF2 (Old) and /work/SRC/openSUSE:Factory/.python-PyPDF2.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-PyPDF2" Mon Apr 24 09:49:40 2017 rev:4 rq:489795 version:1.26.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-PyPDF2/python-PyPDF2.changes 2015-05-16 19:01:48.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python-PyPDF2.new/python-PyPDF2.changes 2017-04-24 09:49:42.136162938 +0200 @@ -1,0 +2,57 @@ +Thu Apr 20 04:22:33 UTC 2017 - [email protected] + +- Convert to single spec +- Update to version 1.26.0 + * NOTE: Active maintenance on PyPDF2 is resuming after a hiatus + * Fixed a bug where image resources where incorrectly overwritten + when merging pages + * Added dictionary for JavaScript actions to the root (louib) + * Added unit tests for the JS functionality (louib) + * Add more Python 3 compatibility when reading inline images (im2703 + and (VyacheslavHashov) + * Return NullObject instead of raising error when failing to resolve + object (ctate) + * Don't output warning for non-zeroed xref table when strict=False + (BenRussert) + * Remove extraneous zeroes from output formatting (speedplane) + * Fix bug where reading an inline image would cut off prematurely in + certain cases (speedplane) +- Changes for 1.25 +BUGFIXES: + * Added Python 3 algorithm for ASCII85Decode. Fixes issue when + reading reportlab-generated files with Py 3 (jerickbixly) + * Recognize more escape sequence which would otherwise throw an + exception (manuelzs, robertsoakes) + * Fixed overflow error in generic.py. Occurred + when reading a too-large int in Python 2 (by Raja Jamwal) + * Allow access to files which were encrypted with an empty + password. Previously threw a "File has not been decrypted" + exception (Elena Williams) + * Do not attempt to decode an empty data stream. Previously + would cause an error in decode algorithms (vladir) + * Fixed some type issues specific to Py 2 or Py 3 + * Fix issue when stream data begins with whitespace (soloma83) + * Recognize abbreviated filter names (AlmightyOatmeal and + Matthew Weiss) + * Copy decryption key from PdfFileReader to PdfFileMerger. + Allows usage of PdfFileMerger with encrypted files (twolfson) + * Fixed bug which occurred when a NameObject is present at end + of a file stream. Threw a "Stream has ended unexpectedly" + exception (speedplane) +FEATURES: + * Initial work on a test suite; to be expanded in future. + Tests and Resources directory added, README updated (robertsoakes) + * Added document cloning methods to PdfFileWriter: + appendPagesFromReader, cloneReaderDocumentRoot, and + cloneDocumentFromReader. See official documentation (robertsoakes) + * Added method for writing to form fields: updatePageFormFieldValues. + This will be enhanced in the future. See official documentation + (robertsoakes) + * New addAttachment method. See documentation. Support for adding + and extracting embedded files to be enhanced in the future + (moshekaplan) + * Added methods to get page number of given PageObject or + Destination: getPageNumber and getDestinationPageNumber. + See documentation (mozbugbox) + +------------------------------------------------------------------- Old: ---- PyPDF2-1.24.tar.gz New: ---- PyPDF2-1.26.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-PyPDF2.spec ++++++ --- /var/tmp/diff_new_pack.FuoijM/_old 2017-04-24 09:49:43.224009080 +0200 +++ /var/tmp/diff_new_pack.FuoijM/_new 2017-04-24 09:49:43.228008514 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-PyPDF2 # -# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,22 +16,23 @@ # +%{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-PyPDF2 -Version: 1.24 +Version: 1.26.0 Release: 0 Url: http://mstamy2.github.com/PyPDF2 Summary: PDF toolkit License: BSD-3-Clause Group: Development/Languages/Python -Source: https://pypi.python.org/packages/source/P/PyPDF2/PyPDF2-%{version}.tar.gz +Source: https://pypi.io/packages/source/P/PyPDF2/PyPDF2-%{version}.tar.gz +BuildRequires: %{python_module devel} +BuildRequires: %{python_module setuptools} +BuildRequires: fdupes +BuildRequires: python-rpm-macros BuildRoot: %{_tmppath}/%{name}-%{version}-build -BuildRequires: python-devel -BuildRequires: python-setuptools -%if 0%{?suse_version} && 0%{?suse_version} <= 1110 -%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} -%else BuildArch: noarch -%endif + +%python_subpackages %description A Pure-Python library built as a PDF toolkit. It is capable of: @@ -54,15 +55,16 @@ sed -i '/^#!/ d' PyPDF2/pagerange.py %build -python setup.py build +%python_build %install -python setup.py install --prefix=%{_prefix} --root=%{buildroot} +%python_install +%fdupes %{buildroot}%{_prefix} chmod a-x CHANGELOG LICENSE README.md -%files +%files %{python_files} %defattr(-,root,root,-) %doc CHANGELOG LICENSE README.md -%{python_sitelib} +%{python_sitelib}/* %changelog ++++++ PyPDF2-1.24.tar.gz -> PyPDF2-1.26.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/.gitignore new/PyPDF2-1.26.0/.gitignore --- old/PyPDF2-1.24/.gitignore 2014-12-31 22:45:23.000000000 +0100 +++ new/PyPDF2-1.26.0/.gitignore 2016-05-18 18:47:24.000000000 +0200 @@ -1,3 +1,6 @@ *.pyc *.swp .DS_Store +build +.idea/* + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/CHANGELOG new/PyPDF2-1.26.0/CHANGELOG --- old/PyPDF2-1.24/CHANGELOG 2014-12-31 22:45:23.000000000 +0100 +++ new/PyPDF2-1.26.0/CHANGELOG 2016-05-18 18:47:24.000000000 +0200 @@ -1,4 +1,108 @@ -Version 1.24, 2014-12-31 +Version 1.26.0, 2016-05-18 +-------------------------- + + - NOTE: Active maintenance on PyPDF2 is resuming after a hiatus + + - Fixed a bug where image resources where incorrectly + overwritten when merging pages + + - Added dictionary for JavaScript actions to the root (louib) + + - Added unit tests for the JS functionality (louib) + + - Add more Python 3 compatibility when reading inline images (im2703 + and (VyacheslavHashov) + + - Return NullObject instead of raising error when failing to resolve + object (ctate) + + - Don't output warning for non-zeroed xref table when strict=False + (BenRussert) + + - Remove extraneous zeroes from output formatting (speedplane) + + - Fix bug where reading an inline image would cut off prematurely + in certain cases (speedplane) + + +Patch 1.25.1, 2015-07-20 + + - Fix bug when parsing inline images. Occurred when merging + certain pages with inline images + + - Fixed type error when creating outlines by utilizing the + isString() test + +Version 1.25, 2015-07-07 +------------------------ + +BUGFIXES: + + - Added Python 3 algorithm for ASCII85Decode. Fixes issue when + reading reportlab-generated files with Py 3 (jerickbixly) + + - Recognize more escape sequence which would otherwise throw an + exception (manuelzs, robertsoakes) + + - Fixed overflow error in generic.py. Occurred + when reading a too-large int in Python 2 (by Raja Jamwal) + + - Allow access to files which were encrypted with an empty + password. Previously threw a "File has not been decrypted" + exception (Elena Williams) + + - Do not attempt to decode an empty data stream. Previously + would cause an error in decode algorithms (vladir) + + - Fixed some type issues specific to Py 2 or Py 3 + + - Fix issue when stream data begins with whitespace (soloma83) + + - Recognize abbreviated filter names (AlmightyOatmeal and + Matthew Weiss) + + - Copy decryption key from PdfFileReader to PdfFileMerger. + Allows usage of PdfFileMerger with encrypted files (twolfson) + + - Fixed bug which occurred when a NameObject is present at end + of a file stream. Threw a "Stream has ended unexpectedly" + exception (speedplane) + +FEATURES: + + - Initial work on a test suite; to be expanded in future. + Tests and Resources directory added, README updated (robertsoakes) + + - Added document cloning methods to PdfFileWriter: + appendPagesFromReader, cloneReaderDocumentRoot, and + cloneDocumentFromReader. See official documentation (robertsoakes) + + - Added method for writing to form fields: updatePageFormFieldValues. + This will be enhanced in the future. See official documentation + (robertsoakes) + + - New addAttachment method. See documentation. Support for adding + and extracting embedded files to be enhanced in the future + (moshekaplan) + + - Added methods to get page number of given PageObject or + Destination: getPageNumber and getDestinationPageNumber. + See documentation (mozbugbox) + +OTHER ENHANCEMENTS: + + - Enhanced type handling (Brent Amrhein) + + - Enhanced exception handling in NameObject (sbywater) + + - Enhanced extractText method output (peircej) + + - Better exception handling + + - Enhanced regex usage in NameObject class (speedplane) + + +Version 1.24, 2014-12-31 ------------------------ - Bugfixes for reading files in Python 3 (by Anthony Tuininga and diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/PyPDF2/_version.py new/PyPDF2-1.26.0/PyPDF2/_version.py --- old/PyPDF2-1.24/PyPDF2/_version.py 2014-12-31 22:45:23.000000000 +0100 +++ new/PyPDF2-1.26.0/PyPDF2/_version.py 2016-05-18 18:47:24.000000000 +0200 @@ -1 +1 @@ -__version__ = '1.24' +__version__ = '1.26.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/PyPDF2/filters.py new/PyPDF2-1.26.0/PyPDF2/filters.py --- old/PyPDF2-1.24/PyPDF2/filters.py 2014-12-31 22:45:23.000000000 +0100 +++ new/PyPDF2-1.26.0/PyPDF2/filters.py 2016-05-18 18:47:24.000000000 +0200 @@ -40,6 +40,7 @@ from cStringIO import StringIO else: from io import StringIO + import struct try: import zlib @@ -256,55 +257,78 @@ class ASCII85Decode(object): def decode(data, decodeParms=None): - retval = "" - group = [] - x = 0 - hitEod = False - # remove all whitespace from data - data = [y for y in data if not (y in ' \n\r\t')] - while not hitEod: - c = data[x] - if len(retval) == 0 and c == "<" and data[x+1] == "~": - x += 2 - continue - #elif c.isspace(): - # x += 1 - # continue - elif c == 'z': - assert len(group) == 0 - retval += '\x00\x00\x00\x00' - x += 1 - continue - elif c == "~" and data[x+1] == ">": - if len(group) != 0: - # cannot have a final group of just 1 char - assert len(group) > 1 - cnt = len(group) - 1 - group += [ 85, 85, 85 ] - hitEod = cnt + if version_info < ( 3, 0 ): + retval = "" + group = [] + x = 0 + hitEod = False + # remove all whitespace from data + data = [y for y in data if not (y in ' \n\r\t')] + while not hitEod: + c = data[x] + if len(retval) == 0 and c == "<" and data[x+1] == "~": + x += 2 + continue + #elif c.isspace(): + # x += 1 + # continue + elif c == 'z': + assert len(group) == 0 + retval += '\x00\x00\x00\x00' + x += 1 + continue + elif c == "~" and data[x+1] == ">": + if len(group) != 0: + # cannot have a final group of just 1 char + assert len(group) > 1 + cnt = len(group) - 1 + group += [ 85, 85, 85 ] + hitEod = cnt + else: + break else: + c = ord(c) - 33 + assert c >= 0 and c < 85 + group += [ c ] + if len(group) >= 5: + b = group[0] * (85**4) + \ + group[1] * (85**3) + \ + group[2] * (85**2) + \ + group[3] * 85 + \ + group[4] + assert b < (2**32 - 1) + c4 = chr((b >> 0) % 256) + c3 = chr((b >> 8) % 256) + c2 = chr((b >> 16) % 256) + c1 = chr(b >> 24) + retval += (c1 + c2 + c3 + c4) + if hitEod: + retval = retval[:-4+hitEod] + group = [] + x += 1 + return retval + else: + if isinstance(data, str): + data = data.encode('ascii') + n = b = 0 + out = bytearray() + for c in data: + if ord('!') <= c and c <= ord('u'): + n += 1 + b = b*85+(c-33) + if n == 5: + out += struct.pack(b'>L',b) + n = b = 0 + elif c == ord('z'): + assert n == 0 + out += b'\0\0\0\0' + elif c == ord('~'): + if n: + for _ in range(5-n): + b = b*85+84 + out += struct.pack(b'>L',b)[:n-1] break - else: - c = ord(c) - 33 - assert c >= 0 and c < 85 - group += [ c ] - if len(group) >= 5: - b = group[0] * (85**4) + \ - group[1] * (85**3) + \ - group[2] * (85**2) + \ - group[3] * 85 + \ - group[4] - assert b < (2**32 - 1) - c4 = chr((b >> 0) % 256) - c3 = chr((b >> 8) % 256) - c2 = chr((b >> 16) % 256) - c1 = chr(b >> 24) - retval += (c1 + c2 + c3 + c4) - if hitEod: - retval = retval[:-4+hitEod] - group = [] - x += 1 - return retval + return bytes(out) decode = staticmethod(decode) @@ -315,22 +339,24 @@ # we have a single filter instance filters = (filters,) data = stream._data - for filterType in filters: - if filterType == "/FlateDecode": - data = FlateDecode.decode(data, stream.get("/DecodeParms")) - elif filterType == "/ASCIIHexDecode": - data = ASCIIHexDecode.decode(data) - elif filterType == "/LZWDecode": - data = LZWDecode.decode(data, stream.get("/DecodeParms")) - elif filterType == "/ASCII85Decode": - data = ASCII85Decode.decode(data) - elif filterType == "/Crypt": - decodeParams = stream.get("/DecodeParams", {}) - if "/Name" not in decodeParams and "/Type" not in decodeParams: - pass + # If there is not data to decode we should not try to decode the data. + if data: + for filterType in filters: + if filterType == "/FlateDecode" or filterType == "/Fl": + data = FlateDecode.decode(data, stream.get("/DecodeParms")) + elif filterType == "/ASCIIHexDecode" or filterType == "/AHx": + data = ASCIIHexDecode.decode(data) + elif filterType == "/LZWDecode" or filterType == "/LZW": + data = LZWDecode.decode(data, stream.get("/DecodeParms")) + elif filterType == "/ASCII85Decode" or filterType == "/A85": + data = ASCII85Decode.decode(data) + elif filterType == "/Crypt": + decodeParams = stream.get("/DecodeParams", {}) + if "/Name" not in decodeParams and "/Type" not in decodeParams: + pass + else: + raise NotImplementedError("/Crypt filter with /Name or /Type not supported yet") else: - raise NotImplementedError("/Crypt filter with /Name or /Type not supported yet") - else: - # unsupported filter - raise NotImplementedError("unsupported filter %s" % filterType) + # unsupported filter + raise NotImplementedError("unsupported filter %s" % filterType) return data diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/PyPDF2/generic.py new/PyPDF2-1.26.0/PyPDF2/generic.py --- old/PyPDF2-1.24/PyPDF2/generic.py 2014-12-31 22:45:23.000000000 +0100 +++ new/PyPDF2-1.26.0/PyPDF2/generic.py 2016-05-18 18:47:24.000000000 +0200 @@ -43,6 +43,7 @@ from . import utils import decimal import codecs +import sys #import debugging ObjectPrefix = b_('/<[tf(n%') @@ -233,8 +234,12 @@ if self == self.to_integral(): return str(self.quantize(decimal.Decimal(1))) else: - # XXX: this adds useless extraneous zeros. - return "%.5f" % self + # Standard formatting adds useless extraneous zeros. + o = "%.5f" % self + # Remove the zeros. + while o and o[-1] == '0': + o = o[:-1] + return o def as_numeric(self): return float(b_(repr(self))) @@ -248,7 +253,11 @@ ByteDot = b_(".") def __new__(cls, value): - return int.__new__(cls, value) + val = int(value) + try: + return int.__new__(cls, val) + except OverflowError: + return int.__new__(cls, 0) def as_numeric(self): return int(b_(repr(self))) @@ -340,13 +349,18 @@ tok = b_("\b") elif tok == b_("f"): tok = b_("\f") + elif tok == b_("c"): + tok = b_("\c") elif tok == b_("("): tok = b_("(") elif tok == b_(")"): tok = b_(")") + elif tok == b_("/"): + tok = b_("/") elif tok == b_("\\"): tok = b_("\\") - elif tok in (b_(" "), b_("/"), b_("%"), b_("<"), b_(">"), b_("["), b_("]")): + elif tok in (b_(" "), b_("/"), b_("%"), b_("<"), b_(">"), b_("["), + b_("]"), b_("#"), b_("_"), b_("&"), b_('$')): # odd/unnessecary escape sequences we have encountered tok = b_(tok) elif tok.isdigit(): @@ -373,7 +387,7 @@ # line break was escaped: tok = b_('') else: - raise utils.PdfReadError("Unexpected escaped string") + raise utils.PdfReadError(r"Unexpected escaped string: %s" % tok) txt += tok return createStringObject(txt) @@ -451,7 +465,7 @@ class NameObject(str, PdfObject): - delimiterPattern = re.compile(b_("\s+|[()<>[\]{}/%]")) + delimiterPattern = re.compile(b_(r"\s+|[\(\)<>\[\]{}/%]")) surfix = b_("/") def writeToStream(self, stream, encryption_key): @@ -463,11 +477,12 @@ name = stream.read(1) if name != NameObject.surfix: raise utils.PdfReadError("name read error") - name += utils.readUntilRegex(stream, NameObject.delimiterPattern) + name += utils.readUntilRegex(stream, NameObject.delimiterPattern, + ignore_eof=True) if debug: print(name) try: return NameObject(name.decode('utf-8')) - except UnicodeDecodeError as e: + except (UnicodeEncodeError, UnicodeDecodeError) as e: # Name objects should represent irregular characters # with a '#' followed by the symbol's hex number if not pdf.strict: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/PyPDF2/merger.py new/PyPDF2-1.26.0/PyPDF2/merger.py --- old/PyPDF2-1.24/PyPDF2/merger.py 2014-12-31 22:45:23.000000000 +0100 +++ new/PyPDF2-1.26.0/PyPDF2/merger.py 2016-05-18 18:47:24.000000000 +0200 @@ -28,7 +28,7 @@ # POSSIBILITY OF SUCH DAMAGE. from .generic import * -from .utils import string_type, str_ +from .utils import isString, str_ from .pdf import PdfFileReader, PdfFileWriter from .pagerange import PageRange from sys import version_info @@ -109,7 +109,8 @@ # it is a PdfFileReader, copy that reader's stream into a # BytesIO (or StreamIO) stream. # If fileobj is none of the above types, it is not modified - if type(fileobj) == string_type: + decryption_key = None + if isString(fileobj): fileobj = file(fileobj, 'rb') my_file = True elif isinstance(fileobj, file): @@ -123,11 +124,15 @@ filecontent = StreamIO(fileobj.stream.read()) fileobj.stream.seek(orig_tell) # reset the stream to its original location fileobj = filecontent + if hasattr(fileobj, '_decryption_key'): + decryption_key = fileobj._decryption_key my_file = True # Create a new PdfFileReader instance using the stream # (either file or BytesIO or StringIO) created above pdfr = PdfFileReader(fileobj, strict=self.strict) + if decryption_key is not None: + pdfr._decryption_key = decryption_key # Find the range of pages to merge. if pages == None: @@ -205,7 +210,7 @@ file-like object. """ my_file = False - if type(fileobj) in (str, str): + if isString(fileobj): fileobj = file(fileobj, 'wb') my_file = True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/PyPDF2/pagerange.py new/PyPDF2-1.26.0/PyPDF2/pagerange.py --- old/PyPDF2-1.24/PyPDF2/pagerange.py 2014-12-31 22:45:23.000000000 +0100 +++ new/PyPDF2-1.26.0/PyPDF2/pagerange.py 2016-05-18 18:47:24.000000000 +0200 @@ -8,7 +8,7 @@ """ import re -from .utils import Str +from .utils import isString _INT_RE = r"(0|-?[1-9]\d*)" # A decimal int, don't allow "-0". PAGE_RANGE_RE = "^({int}|({int}?(:{int}?(:{int}?)?)))$".format(int=_INT_RE) @@ -68,7 +68,7 @@ self._slice = arg.to_slice() return - m = isinstance(arg, Str) and re.match(PAGE_RANGE_RE, arg) + m = isString(arg) and re.match(PAGE_RANGE_RE, arg) if not m: raise ParseError(arg) elif m.group(2): @@ -89,7 +89,7 @@ """ True if input is a valid initializer for a PageRange. """ return isinstance(input, slice) or \ isinstance(input, PageRange) or \ - (isinstance(input, Str) + (isString(input) and bool(re.match(PAGE_RANGE_RE, input))) def to_slice(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/PyPDF2/pdf.py new/PyPDF2-1.26.0/PyPDF2/pdf.py --- old/PyPDF2-1.24/PyPDF2/pdf.py 2014-12-31 22:45:23.000000000 +0100 +++ new/PyPDF2-1.26.0/PyPDF2/pdf.py 2016-05-18 18:47:24.000000000 +0200 @@ -46,6 +46,7 @@ import math import struct import sys +import uuid from sys import version_info if version_info < ( 3, 0 ): from cStringIO import StringIO @@ -63,7 +64,7 @@ import codecs from .generic import * from .utils import readNonWhitespace, readUntilWhitespace, ConvertFunctionsToVirtualList -from .utils import Str, b_, u_, ord_, chr_, str_, string_type, formatWarning +from .utils import isString, b_, u_, ord_, chr_, str_, formatWarning if version_info < ( 2, 4 ): from sets import ImmutableSet as frozenset @@ -225,10 +226,175 @@ NameObject("/S"): NameObject("/JavaScript"), NameObject("/JS"): NameObject("(%s)" % javascript) }) + js_indirect_object = self._addObject(js) + + # We need a name for parameterized javascript in the pdf file, but it can be anything. + js_string_name = str(uuid.uuid4()) + + js_name_tree = DictionaryObject() + js_name_tree.update({ + NameObject("/JavaScript"): DictionaryObject({ + NameObject("/Names"): ArrayObject([createStringObject(js_string_name), js_indirect_object]) + }) + }) + self._addObject(js_name_tree) + + self._root_object.update({ + NameObject("/OpenAction"): js_indirect_object, + NameObject("/Names"): js_name_tree + }) + + def addAttachment(self, fname, fdata): + """ + Embed a file inside the PDF. + + :param str fname: The filename to display. + :param str fdata: The data in the file. + + Reference: + https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf + Section 7.11.3 + """ + + # We need 3 entries: + # * The file's data + # * The /Filespec entry + # * The file's name, which goes in the Catalog + + + # The entry for the file + """ Sample: + 8 0 obj + << + /Length 12 + /Type /EmbeddedFile + >> + stream + Hello world! + endstream + endobj + """ + file_entry = DecodedStreamObject() + file_entry.setData(fdata) + file_entry.update({ + NameObject("/Type"): NameObject("/EmbeddedFile") + }) + + # The Filespec entry + """ Sample: + 7 0 obj + << + /Type /Filespec + /F (hello.txt) + /EF << /F 8 0 R >> + >> + """ + efEntry = DictionaryObject() + efEntry.update({ NameObject("/F"):file_entry }) + + filespec = DictionaryObject() + filespec.update({ + NameObject("/Type"): NameObject("/Filespec"), + NameObject("/F"): createStringObject(fname), # Perhaps also try TextStringObject + NameObject("/EF"): efEntry + }) + + # Then create the entry for the root, as it needs a reference to the Filespec + """ Sample: + 1 0 obj + << + /Type /Catalog + /Outlines 2 0 R + /Pages 3 0 R + /Names << /EmbeddedFiles << /Names [(hello.txt) 7 0 R] >> >> + >> + endobj + + """ + embeddedFilesNamesDictionary = DictionaryObject() + embeddedFilesNamesDictionary.update({ + NameObject("/Names"): ArrayObject([createStringObject(fname), filespec]) + }) + + embeddedFilesDictionary = DictionaryObject() + embeddedFilesDictionary.update({ + NameObject("/EmbeddedFiles"): embeddedFilesNamesDictionary + }) + # Update the root self._root_object.update({ - NameObject("/OpenAction"): self._addObject(js) + NameObject("/Names"): embeddedFilesDictionary }) + def appendPagesFromReader(self, reader, after_page_append=None): + """ + Copy pages from reader to writer. Includes an optional callback parameter + which is invoked after pages are appended to the writer. + + :param reader: a PdfFileReader object from which to copy page + annotations to this writer object. The writer's annots + will then be updated + :callback after_page_append (function): Callback function that is invoked after + each page is appended to the writer. Callback signature: + + :param writer_pageref (PDF page reference): Reference to the page + appended to the writer. + """ + # Get page count from writer and reader + reader_num_pages = reader.getNumPages() + writer_num_pages = self.getNumPages() + + # Copy pages from reader to writer + for rpagenum in range(0, reader_num_pages): + reader_page = reader.getPage(rpagenum) + self.addPage(reader_page) + writer_page = self.getPage(writer_num_pages+rpagenum) + # Trigger callback, pass writer page as parameter + if callable(after_page_append): after_page_append(writer_page) + + def updatePageFormFieldValues(self, page, fields): + ''' + Update the form field values for a given page from a fields dictionary. + Copy field texts and values from fields to page. + + :param page: Page reference from PDF writer where the annotations + and field data will be updated. + :param fields: a Python dictionary of field names (/T) and text + values (/V) + ''' + # Iterate through pages, update field values + for j in range(0, len(page['/Annots'])): + writer_annot = page['/Annots'][j].getObject() + for field in fields: + if writer_annot.get('/T') == field: + writer_annot.update({ + NameObject("/V"): TextStringObject(fields[field]) + }) + + def cloneReaderDocumentRoot(self, reader): + ''' + Copy the reader document root to the writer. + + :param reader: PdfFileReader from the document root should be copied. + :callback after_page_append + ''' + self._root_object = reader.trailer['/Root'] + + def cloneDocumentFromReader(self, reader, after_page_append=None): + ''' + Create a copy (clone) of a document from a PDF file reader + + :param reader: PDF file reader instance from which the clone + should be created. + :callback after_page_append (function): Callback function that is invoked after + each page is appended to the writer. Signature includes a reference to the + appended page (delegates to appendPagesFromReader). Callback signature: + + :param writer_pageref (PDF page reference): Reference to the page just + appended to the document. + ''' + self.cloneReaderDocumentRoot(reader) + self.appendPagesFromReader(reader, after_page_append) + def encrypt(self, user_pwd, owner_pwd = None, use_128bit = True): """ Encrypt this PDF file with the PDF Standard encryption handler. @@ -407,18 +573,22 @@ else: newobj = externMap.get(data.pdf, {}).get(data.generation, {}).get(data.idnum, None) if newobj == None: - newobj = data.pdf.getObject(data) - self._objects.append(None) # placeholder - idnum = len(self._objects) - newobj_ido = IndirectObject(idnum, 0, self) - if data.pdf not in externMap: - externMap[data.pdf] = {} - if data.generation not in externMap[data.pdf]: - externMap[data.pdf][data.generation] = {} - externMap[data.pdf][data.generation][data.idnum] = newobj_ido - newobj = self._sweepIndirectReferences(externMap, newobj) - self._objects[idnum-1] = newobj - return newobj_ido + try: + newobj = data.pdf.getObject(data) + self._objects.append(None) # placeholder + idnum = len(self._objects) + newobj_ido = IndirectObject(idnum, 0, self) + if data.pdf not in externMap: + externMap[data.pdf] = {} + if data.generation not in externMap[data.pdf]: + externMap[data.pdf][data.generation] = {} + externMap[data.pdf][data.generation][data.idnum] = newobj_ido + newobj = self._sweepIndirectReferences(externMap, newobj) + self._objects[idnum-1] = newobj + return newobj_ido + except ValueError: + # Unable to resolve the Object, returning NullObject instead. + return NullObject() return newobj else: return data @@ -758,7 +928,7 @@ else: borderArr = [NumberObject(0)] * 3 - if isinstance(rect, Str): + if isString(rect): rect = NameObject(rect) elif isinstance(rect, RectangleObject): pass @@ -904,9 +1074,10 @@ self.flattenedPages = None self.resolvedObjects = {} self.xrefIndex = 0 + self._pageId2Num = None # map page IndirectRef number to Page Number if hasattr(stream, 'mode') and 'b' not in stream.mode: warnings.warn("PdfFileReader stream/file object is not in binary mode. It may not be read correctly.", utils.PdfReadWarning) - if type(stream) in (string_type, str): + if isString(stream): fileobj = open(stream, 'rb') stream = BytesIO(b_(fileobj.read())) fileobj.close() @@ -973,6 +1144,7 @@ if self.isEncrypted: try: self._override_encryption = True + self.decrypt('') return self.trailer["/Root"]["/Pages"]["/Count"] except: raise utils.PdfReadError("File has not been decrypted") @@ -1101,6 +1273,16 @@ # Field attribute is N/A or unknown, so don't write anything pass + def getFormTextFields(self): + ''' Retrieves form fields from the document with textual data (inputs, dropdowns) + ''' + # Retrieve document form fields + formfields = self.getFields() + return dict( + (formfields[field]['/T'], formfields[field].get('/V')) for field in formfields \ + if formfields[field].get('/FT') == '/Tx' + ) + def getNamedDestinations(self, tree=None, retval=None): """ Retrieves the named destinations present in the document. @@ -1167,7 +1349,7 @@ # for an example of such a file, see https://unglueit-files.s3.amazonaws.com/ebf/7552c42e9280b4476e59e77acc0bc812.pdf # so continue to load the file without the Bookmarks return outlines - + if "/First" in lines: node = lines["/First"] self._namedDests = self.getNamedDestinations() @@ -1194,6 +1376,49 @@ return outlines + def _getPageNumberByIndirect(self, indirectRef): + """Generate _pageId2Num""" + if self._pageId2Num is None: + id2num = {} + for i, x in enumerate(self.pages): + id2num[x.indirectRef.idnum] = i + self._pageId2Num = id2num + + if isinstance(indirectRef, int): + idnum = indirectRef + else: + idnum = indirectRef.idnum + + ret = self._pageId2Num.get(idnum, -1) + return ret + + def getPageNumber(self, page): + """ + Retrieve page number of a given PageObject + + :param PageObject page: The page to get page number. Should be + an instance of :class:`PageObject<PyPDF2.pdf.PageObject>` + :return: the page number or -1 if page not found + :rtype: int + """ + indirectRef = page.indirectRef + ret = self._getPageNumberByIndirect(indirectRef) + return ret + + def getDestinationPageNumber(self, destination): + """ + Retrieve page number of a given Destination object + + :param Destination destination: The destination to get page number. + Should be an instance of + :class:`Destination<PyPDF2.pdf.Destination>` + :return: the page number or -1 if page not found + :rtype: int + """ + indirectRef = destination.page + ret = self._getPageNumberByIndirect(indirectRef) + return ret + def _buildDestination(self, title, array): page, typ = array[0:2] array = array[2:] @@ -1217,7 +1442,7 @@ if dest: if isinstance(dest, ArrayObject): outline = self._buildDestination(title, dest) - elif isinstance(dest, Str) and dest in self._namedDests: + elif isString(dest) and dest in self._namedDests: outline = self._namedDests[dest] outline[NameObject("/Title")] = title else: @@ -1317,6 +1542,8 @@ assert idx < objStm['/N'] streamData = BytesIO(b_(objStm.getData())) for i in range(objStm['/N']): + readNonWhitespace(streamData) + streamData.seek(-1, 1) objnum = NumberObject.readFromStream(streamData) readNonWhitespace(streamData) streamData.seek(-1, 1) @@ -1505,10 +1732,10 @@ num = readObject(stream, self) if firsttime and num != 0: self.xrefIndex = num - warnings.warn("Xref table not zero-indexed. ID numbers for objects will %sbe corrected." % \ - ("" if not self.strict else "not "), utils.PdfReadWarning) - #if table not zero indexed, could be due to error from when PDF was created - #which will lead to mismatched indices later on + if self.strict: + warnings.warn("Xref table not zero-indexed. ID numbers for objects will be corrected.", utils.PdfReadWarning) + #if table not zero indexed, could be due to error from when PDF was created + #which will lead to mismatched indices later on, only warned and corrected if self.strict=True firsttime = False readNonWhitespace(stream) stream.seek(-1, 1) @@ -1671,8 +1898,7 @@ if found: continue # no xref table found at specified location - assert False - break + raise utils.PdfReadError("Could not find xref table at specified location") #if not zero-indexed, verify that the table is correct; change it if necessary if self.xrefIndex and not self.strict: loc = stream.tell() @@ -1945,7 +2171,7 @@ page2Res = res2.get(resource, DictionaryObject()).getObject() renameRes = {} for key in list(page2Res.keys()): - if key in newRes and newRes[key] != page2Res[key]: + if key in newRes and newRes.raw_get(key) != page2Res.raw_get(key): newname = NameObject(key + str(uuid.uuid4())) renameRes[key] = newname newRes[newname] = page2Res[key] @@ -2391,6 +2617,7 @@ for i in operands[0]: if isinstance(i, TextStringObject): text += i + text += "\n" return text mediaBox = createRectangleAccessor("/MediaBox", ()) @@ -2498,38 +2725,45 @@ assert tmp[:2] == b_("ID") data = b_("") while True: + # Read the inline image, while checking for EI (End Image) operator. tok = stream.read(1) if tok == b_("E"): # Check for End Image - next1 = stream.read(1) - if next1 == b_("I"): - next2 = readNonWhitespace(stream) - if next2 == b_('Q'): + tok2 = stream.read(1) + if tok2 == b_("I"): + # Data can contain EI, so check for the Q operator. + tok3 = stream.read(1) + info = tok + tok2 + # We need to find whitespace between EI and Q. + has_q_whitespace = False + while tok3 in utils.WHITESPACES: + has_q_whitespace = True + info += tok3 + tok3 = stream.read(1) + if tok3 == b_("Q") and has_q_whitespace: stream.seek(-1, 1) break else: - stream.seek(-2, 1) - data += tok + stream.seek(-1,1) + data += info else: stream.seek(-1, 1) data += tok else: data += tok - readNonWhitespace(stream) - stream.seek(-1, 1) return {"settings": settings, "data": data} def _getData(self): newdata = BytesIO() for operands, operator in self.operations: - if operator == "INLINE IMAGE": - newdata.write("BI") - dicttext = StringIO() + if operator == b_("INLINE IMAGE"): + newdata.write(b_("BI")) + dicttext = BytesIO() operands["settings"].writeToStream(dicttext, None) newdata.write(dicttext.getvalue()[2:-2]) - newdata.write("ID ") + newdata.write(b_("ID ")) newdata.write(operands["data"]) - newdata.write("EI") + newdata.write(b_("EI")) else: for op in operands: op.writeToStream(newdata, None) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/PyPDF2/utils.py new/PyPDF2-1.26.0/PyPDF2/utils.py --- old/PyPDF2-1.24/PyPDF2/utils.py 2014-12-31 22:45:23.000000000 +0100 +++ new/PyPDF2-1.26.0/PyPDF2/utils.py 2016-05-18 18:47:24.000000000 +0200 @@ -33,25 +33,35 @@ import sys -# "Str" maintains compatibility with Python 2.x. -# The next line is obfuscated like this so 2to3 won't change it. + try: import __builtin__ as builtins except ImportError: # Py3 import builtins -if sys.version_info[0] < 3: - string_type = unicode - bytes_type = str - int_types = (int, long) -else: - string_type = str - bytes_type = bytes - int_types = (int,) +xrange_fn = getattr(builtins, "xrange", range) +_basestring = getattr(builtins, "basestring", str) + +bytes_type = type(bytes()) # Works the same in Python 2.X and 3.X +string_type = getattr(builtins, "unicode", str) +int_types = (int, long) if sys.version_info[0] < 3 else (int,) + + +# Make basic type tests more consistent +def isString(s): + """Test if arg is a string. Compatible with Python 2 and 3.""" + return isinstance(s, _basestring) + + +def isInt(n): + """Test if arg is an int. Compatible with Python 2 and 3.""" + return isinstance(n, int_types) + -Xrange = getattr(builtins, "xrange", range) -Str = getattr(builtins, "basestring", str) +def isBytes(b): + """Test if arg is a bytes instance. Compatible with Python 2 and 3.""" + return isinstance(b, bytes_type) #custom implementation of warnings.formatwarning @@ -141,10 +151,10 @@ def __getitem__(self, index): if isinstance(index, slice): - indices = Xrange(*index.indices(len(self))) + indices = xrange_fn(*index.indices(len(self))) cls = type(self) return cls(indices.__len__, lambda idx: self[indices[idx]]) - if not isinstance(index, int_types): + if not isInt(index): raise TypeError("sequence indices must be integers") len_self = len(self) if index < 0: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/README.md new/PyPDF2-1.26.0/README.md --- old/PyPDF2-1.24/README.md 2014-12-31 22:45:23.000000000 +0100 +++ new/PyPDF2-1.26.0/README.md 2016-05-18 18:47:24.000000000 +0200 @@ -23,3 +23,12 @@ ##FAQ Please see http://mstamy2.github.io/PyPDF2/FAQ.html + + +##Tests +PyPDF2 includes a test suite built on the unittest framework. All tests are located in the "Tests" folder. +Tests can be run from the command line by: + +```bash +python -m unittest Tests.tests +``` \ No newline at end of file Binary files old/PyPDF2-1.24/Resources/crazyones.pdf and new/PyPDF2-1.26.0/Resources/crazyones.pdf differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/Resources/crazyones.txt new/PyPDF2-1.26.0/Resources/crazyones.txt --- old/PyPDF2-1.24/Resources/crazyones.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/PyPDF2-1.26.0/Resources/crazyones.txt 2016-05-18 18:47:24.000000000 +0200 @@ -0,0 +1 @@ +TheCrazyOnesOctober14,1998Herestothecrazyones.Themis˝ts.Therebels.Thetroublemakers.Theroundpegsinthesquareholes.Theoneswhoseethingsdi˙erently.Theyrenotfondofrules.Andtheyhavenorespectforthestatusquo.Youcanquotethem,disagreewiththem,glorifyorvilifythem.Abouttheonlythingyoucantdoisignorethem.Becausetheychangethings.Theyinvent.Theyimagine.Theyheal.Theyexplore.Theycreate.Theyinspire.Theypushthehumanraceforward.Maybetheyhavetobecrazy.Howelsecanyoustareatanemptycanvasandseeaworkofart?Orsitinsilenceandhearasongthatsneverbeenwritten?Orgazeataredplanetandseealaboratoryonwheels?Wemaketoolsforthesekindsofpeople.Whilesomeseethemasthecrazyones,weseegenius.Becausethepeoplewhoarecrazyenoughtothinktheycanchangetheworld,aretheoneswhodo. \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyPDF2-1.24/Tests/tests.py new/PyPDF2-1.26.0/Tests/tests.py --- old/PyPDF2-1.24/Tests/tests.py 1970-01-01 01:00:00.000000000 +0100 +++ new/PyPDF2-1.26.0/Tests/tests.py 2016-05-18 18:47:24.000000000 +0200 @@ -0,0 +1,69 @@ +import os +import sys +import unittest + +from PyPDF2 import PdfFileReader, PdfFileWriter + + +# Configure path environment +TESTS_ROOT = os.path.abspath(os.path.dirname(__file__)) +PROJECT_ROOT = os.path.dirname(TESTS_ROOT) +RESOURCE_ROOT = os.path.join(PROJECT_ROOT, 'Resources') + +sys.path.append(PROJECT_ROOT) + + +class PdfReaderTestCases(unittest.TestCase): + + def test_PdfReaderFileLoad(self): + ''' + Test loading and parsing of a file. Extract text of the file and compare to expected + textual output. Expected outcome: file loads, text matches expected. + ''' + + with open(os.path.join(RESOURCE_ROOT, 'crazyones.pdf'), 'rb') as inputfile: + # Load PDF file from file + ipdf = PdfFileReader(inputfile) + ipdf_p1 = ipdf.getPage(0) + + # Retrieve the text of the PDF + pdftext_file = open(os.path.join(RESOURCE_ROOT, 'crazyones.txt'), 'r') + pdftext = pdftext_file.read() + ipdf_p1_text = ipdf_p1.extractText().replace('\n', '') + + # Compare the text of the PDF to a known source + self.assertEqual(ipdf_p1_text.encode('utf-8', errors='ignore'), pdftext, + msg='PDF extracted text differs from expected value.\n\nExpected:\n\n%r\n\nExtracted:\n\n%r\n\n' + % (pdftext, ipdf_p1_text.encode('utf-8', errors='ignore'))) + + +class AddJsTestCase(unittest.TestCase): + + def setUp(self): + ipdf = PdfFileReader(os.path.join(RESOURCE_ROOT, 'crazyones.pdf')) + self.pdf_file_writer = PdfFileWriter() + self.pdf_file_writer.appendPagesFromReader(ipdf) + + def test_add(self): + + self.pdf_file_writer.addJS("this.print({bUI:true,bSilent:false,bShrinkToFit:true});") + + self.assertIn('/Names', self.pdf_file_writer._root_object, "addJS should add a name catalog in the root object.") + self.assertIn('/JavaScript', self.pdf_file_writer._root_object['/Names'], "addJS should add a JavaScript name tree under the name catalog.") + self.assertIn('/OpenAction', self.pdf_file_writer._root_object, "addJS should add an OpenAction to the catalog.") + + def test_overwrite(self): + + self.pdf_file_writer.addJS("this.print({bUI:true,bSilent:false,bShrinkToFit:true});") + first_js = self.get_javascript_name() + + self.pdf_file_writer.addJS("this.print({bUI:true,bSilent:false,bShrinkToFit:true});") + second_js = self.get_javascript_name() + + self.assertNotEqual(first_js, second_js, "addJS should overwrite the previous script in the catalog.") + + def get_javascript_name(self): + self.assertIn('/Names', self.pdf_file_writer._root_object) + self.assertIn('/JavaScript', self.pdf_file_writer._root_object['/Names']) + self.assertIn('/Names', self.pdf_file_writer._root_object['/Names']['/JavaScript']) + return self.pdf_file_writer._root_object['/Names']['/JavaScript']['/Names'][0]
