Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pikepdf for openSUSE:Factory 
checked in at 2021-03-15 10:54:39
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pikepdf (Old)
 and      /work/SRC/openSUSE:Factory/.python-pikepdf.new.2401 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pikepdf"

Mon Mar 15 10:54:39 2021 rev:8 rq:878602 version:2.8.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pikepdf/python-pikepdf.changes    
2021-02-15 23:16:36.823423894 +0100
+++ /work/SRC/openSUSE:Factory/.python-pikepdf.new.2401/python-pikepdf.changes  
2021-03-15 10:54:41.565198646 +0100
@@ -1,0 +2,21 @@
+Thu Mar  4 19:17:02 UTC 2021 - Martin Hauke <mar...@gmx.de>
+
+- Update to version 2.8.0
+  * Fixed an issue with extracting data from images that had their
+    DecodeParms structured as a list of dictionaries.
+  * Fixed an issue where a dangling stream object is created if we
+    fail to create the requested stream dictionary.
+  * Calling Dictionary() and Array() on objects which are already
+    of that type returns a shallow copy rather than throwing an
+    exception, in keeping with Python semantics.
+- Update to version 2.7.0
+  * Added an option to tell Pdf.save to recompress flate streams,
+    and a global option to set the flate compression level. This
+    option can be use to force the recompression of flate streams
+    if they are not well compressed.
+  * Fixed "TypeError: only pages can be inserted" when attempting
+    to an insert an unowned page using QPDF 10.2.0 or later.
+- Update to version 2.6.0
+  * Rebuild wheels against QPDF 10.2.0.
+
+-------------------------------------------------------------------

Old:
----
  pikepdf-2.5.2.tar.gz

New:
----
  pikepdf-2.8.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pikepdf.spec ++++++
--- /var/tmp/diff_new_pack.TIY151/_old  2021-03-15 10:54:43.049200924 +0100
+++ /var/tmp/diff_new_pack.TIY151/_new  2021-03-15 10:54:43.053200930 +0100
@@ -2,7 +2,7 @@
 # spec file for package python-pikepdf
 #
 # Copyright (c) 2021 SUSE LLC
-# Copyright (c) 2020, Martin Hauke <mar...@gmx.de>
+# Copyright (c) 2020-2021, Martin Hauke <mar...@gmx.de>
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -20,7 +20,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-pikepdf
-Version:        2.5.2
+Version:        2.8.0
 Release:        0
 Summary:        Read and write PDFs with Python, powered by qpdf
 License:        MPL-2.0

++++++ pikepdf-2.5.2.tar.gz -> pikepdf-2.8.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/PKG-INFO new/pikepdf-2.8.0/PKG-INFO
--- old/pikepdf-2.5.2/PKG-INFO  2021-02-01 02:07:18.598896700 +0100
+++ new/pikepdf-2.8.0/PKG-INFO  2021-03-01 01:23:55.900248800 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pikepdf
-Version: 2.5.2
+Version: 2.8.0
 Summary: Read and write PDFs with Python, powered by qpdf
 Home-page: https://github.com/pikepdf/pikepdf
 Author: James R. Barlow
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/azure-pipelines.yml 
new/pikepdf-2.8.0/azure-pipelines.yml
--- old/pikepdf-2.5.2/azure-pipelines.yml       2021-02-01 02:05:12.000000000 
+0100
+++ new/pikepdf-2.8.0/azure-pipelines.yml       2021-03-01 01:21:28.000000000 
+0100
@@ -1,5 +1,5 @@
 variables:
-  qpdf_version: "10.1.0"
+  qpdf_version: "10.2.0"
   qpdf_release: ${{ 
format('https://github.com/qpdf/qpdf/releases/download/release-qpdf-{0}/qpdf-{0}.tar.gz',
 variables.qpdf_version) }}
   qpdf_min_version: "10.0.3"
   qpdf_min_release: ${{ 
format('https://github.com/qpdf/qpdf/releases/download/release-qpdf-{0}/qpdf-{0}.tar.gz',
 variables.qpdf_min_version) }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/docs/release_notes.rst 
new/pikepdf-2.8.0/docs/release_notes.rst
--- old/pikepdf-2.5.2/docs/release_notes.rst    2021-02-01 02:05:12.000000000 
+0100
+++ new/pikepdf-2.8.0/docs/release_notes.rst    2021-03-01 01:21:28.000000000 
+0100
@@ -18,6 +18,31 @@
 ``pikepdf._qpdf`` is a private interface within pikepdf that applications
 should not access directly, along with any modules with a prefixed underscore.
 
+v2.8.0
+======
+
+-  Fixed an issue with extracting data from images that had their DecodeParms
+   structured as a list of dictionaries.
+-  Fixed an issue where a dangling stream object is created if we fail to 
create
+   the requested stream dictionary.
+-  Calling ``Dictionary()`` and ``Array()`` on objects which are already of 
that
+   type returns a shallow copy rather than throwing an exception, in keeping 
with
+   Python semantics.
+
+v2.7.0
+======
+
+-  Added an option to tell ``Pdf.save`` to recompress flate streams, and a 
global
+   option to set the flate compression level. This option can be use to force
+   the recompression of flate streams if they are not well compressed.
+-  Fixed "TypeError: only pages can be inserted" when attempting to an insert 
an
+   unowned page using QPDF 10.2.0 or later.
+
+v2.6.0
+======
+
+-  Rebuild wheels against QPDF 10.2.0.
+
 v2.5.2
 ======
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/src/pikepdf/_methods.py 
new/pikepdf-2.8.0/src/pikepdf/_methods.py
--- old/pikepdf-2.5.2/src/pikepdf/_methods.py   2021-02-01 02:05:12.000000000 
+0100
+++ new/pikepdf-2.8.0/src/pikepdf/_methods.py   2021-03-01 01:21:28.000000000 
+0100
@@ -418,14 +418,8 @@
         """
         Create a new pikepdf.Stream object that is attached to this PDF.
 
-        Args:
-            data (bytes): Binary data for the stream object
-            d: Dictionary portion of the stream object
-            kwargs: Keyword arguments to define the dictionary. Do not set
-                /Filter or /Length here as pikepdf will manage these.
-
-        Example:
-            pdf.make_stream(b"Binary data here", Type=Name.XObject, 
Subtype=Name.Image)
+        See:
+            :meth:`pikepdf.Stream.__new__`
 
         """
         return Stream(self, data, d, **kwargs)
@@ -656,7 +650,7 @@
             self.Root.PageMode = Name.UseAttachments
 
     def save(
-        self,
+        self,  # TODO mandatory kwargs
         filename_or_stream: Union[Path, str, BinaryIO, None] = None,
         static_id: bool = False,
         preserve_pdfa: bool = True,
@@ -671,6 +665,7 @@
         qdf: bool = False,
         progress: Callable[[int], None] = None,
         encryption: Optional[Union[Encryption, bool]] = None,
+        recompress_flate: bool = False,
     ) -> None:
         """
         Save all modifications to this :class:`pikepdf.Pdf`.
@@ -730,6 +725,12 @@
                 to encode stream objects. See documentation for
                 ``StreamDecodeLevel``.
 
+            recompress_flate: When disabled (the default), qpdf does not
+                uncompress and recompress streams compressed with the Flate
+                compression algorithm. If True, pikepdf will instruct qpdf to
+                do this, which may be useful if recompressing streams to a
+                higher compression level.
+
             normalize_content: Enables parsing and reformatting the
                 content stream within PDFs. This may debugging PDFs easier.
 
@@ -797,10 +798,11 @@
             progress=progress,
             encryption=encryption,
             samefile_check=getattr(self, '_tmp_stream', None) is None,
+            recompress_flate=recompress_flate,
         )
 
     @staticmethod
-    def open(
+    def open(  # TODO mandatory kwargs
         filename_or_stream: Union[Path, str, BinaryIO],
         password: Union[str, bytes] = "",
         hex_password: bool = False,
@@ -1021,7 +1023,7 @@
             The name of the object.
 
         Example:
-            resource_name = Page(pdf.pages[0]).add_resource(formxobj, 
Name.XObject)
+            >>> resource_name = Page(pdf.pages[0]).add_resource(formxobj, 
Name.XObject)
         """
         if not Name.Resources in self.obj:
             self.obj.Resources = Dictionary()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/src/pikepdf/models/image.py 
new/pikepdf-2.8.0/src/pikepdf/models/image.py
--- old/pikepdf-2.5.2/src/pikepdf/models/image.py       2021-02-01 
02:05:12.000000000 +0100
+++ new/pikepdf-2.8.0/src/pikepdf/models/image.py       2021-03-01 
01:21:28.000000000 +0100
@@ -69,7 +69,7 @@
     if isinstance(value, Dictionary):
         return [value.as_dict()]
     if isinstance(value, Array):
-        return [v.as_list() for v in value]
+        return [v for v in value.as_list()]
     raise NotImplementedError(value)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/src/pikepdf/objects.py 
new/pikepdf-2.8.0/src/pikepdf/objects.py
--- old/pikepdf-2.5.2/src/pikepdf/objects.py    2021-02-01 02:05:12.000000000 
+0100
+++ new/pikepdf-2.8.0/src/pikepdf/objects.py    2021-03-01 01:21:28.000000000 
+0100
@@ -169,6 +169,8 @@
             raise TypeError('Strings cannot be converted to arrays of chars')
         if a is None:
             a = []
+        if isinstance(a, Array):
+            return a.__copy__()
         return _qpdf._new_array(a)
 
 
@@ -198,11 +200,14 @@
             pikepdf.Object
         """
         if kwargs and d is not None:
-            raise ValueError('Unsupported parameters')
+            raise ValueError('Cannot use both a mapping object and keyword 
args')
         if kwargs:
             # Add leading slash
             # Allows Dictionary(MediaBox=(0,0,1,1), Type=Name('/Page')...
             return _qpdf._new_dictionary({('/' + k): v for k, v in 
kwargs.items()})
+        if isinstance(d, Dictionary):
+            # Already a dictionary
+            return d.__copy__()
         if not d:
             d = {}
         if d and any(not key.startswith('/') for key in d.keys()):
@@ -217,14 +222,44 @@
 
     def __new__(cls, owner: 'Pdf', data: bytes = None, d=None, **kwargs):
         """
+        Create a new stream object, which stores arbitrary binary data and may
+        or may not be compressed.
+
+        A stream dictionary is like a pikepdf.Dictionary or Python dict, except
+        it has a binary payload of data attached. The dictionary describes
+        how the data is compressed or encoded.
+
+        The dictionary may be initialized just like pikepdf.Dictionary is 
initialized,
+        using a mapping object or keyword arguments.
+
         Args:
             owner: The Pdf to which this stream shall be attached.
-            obj: The data bytes for the stream.
-            d: A mapping object that will be used to construct a 
``Dictionary``.
-            kwargs: Keyword arguments that will define the dictionary. Do not 
set
-                /Filter or /Length here as pikepdf will manage these.
+            data: The data bytes for the stream.
+            d: An optional mapping object that will be used to construct the 
stream's
+                dictionary.
+            kwargs: Keyword arguments that will define the stream dictionary. 
Do not set
+                /Length here as pikepdf will manage this value. Set /Filter
+                if the data is already encoded in some format.
+            obj: Deprecated alias for *data*.
         Returns:
             pikepdf.Object
+
+        Examples:
+            Using kwargs:
+                >>> s1 = pikepdf.Stream(
+                        pdf,
+                        b"uncompressed image data",
+                        BitsPerComponent=8,
+                        ColorSpace=Name.DeviceRGB,
+                        ...
+                    )
+            Using dict:
+                >>> d = pikepdf.Dictionary(...)
+                >>> s2 = pikepdf.Stream(
+                        pdf,
+                        b"data",
+                        d
+                    )
         """
 
         # Support __new__(...obj=bytes) which should have been data=bytes,
@@ -238,8 +273,11 @@
         if data is None:
             raise TypeError("Must make Stream from binary data")
 
-        stream = _qpdf._new_stream(owner, data)
+        stream_dict = None
         if d or kwargs:
             stream_dict = Dictionary(d, **kwargs)
+
+        stream = _qpdf._new_stream(owner, data)
+        if stream_dict:
             stream.stream_dict = stream_dict
         return stream
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/src/pikepdf.egg-info/PKG-INFO 
new/pikepdf-2.8.0/src/pikepdf.egg-info/PKG-INFO
--- old/pikepdf-2.5.2/src/pikepdf.egg-info/PKG-INFO     2021-02-01 
02:07:18.000000000 +0100
+++ new/pikepdf-2.8.0/src/pikepdf.egg-info/PKG-INFO     2021-03-01 
01:23:55.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pikepdf
-Version: 2.5.2
+Version: 2.8.0
 Summary: Read and write PDFs with Python, powered by qpdf
 Home-page: https://github.com/pikepdf/pikepdf
 Author: James R. Barlow
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/src/qpdf/object_repr.cpp 
new/pikepdf-2.8.0/src/qpdf/object_repr.cpp
--- old/pikepdf-2.5.2/src/qpdf/object_repr.cpp  2021-02-01 02:05:12.000000000 
+0100
+++ new/pikepdf-2.8.0/src/qpdf/object_repr.cpp  2021-03-01 01:21:28.000000000 
+0100
@@ -36,7 +36,7 @@
 
 std::string objecthandle_scalar_value(QPDFObjectHandle h, bool escaped)
 {
-    std::stringstream ss;
+    std::ostringstream ss;
     switch (h.getTypeCode()) {
     case QPDFObject::object_type_e::ot_null:
         ss << "None";
@@ -67,53 +67,53 @@
 
 std::string objecthandle_pythonic_typename(QPDFObjectHandle h, std::string 
prefix)
 {
-    std::string s;
+    std::ostringstream ss;
 
-    s += prefix;
+    ss << prefix;
     switch (h.getTypeCode()) {
     case QPDFObject::object_type_e::ot_null:
-        s += "NoneType";
+        ss << "NoneType";
         break;
     case QPDFObject::object_type_e::ot_boolean:
-        s += "Boolean";
+        ss << "Boolean";
         break;
     case QPDFObject::object_type_e::ot_integer:
-        s += "Integer";
+        ss << "Integer";
         break;
     case QPDFObject::object_type_e::ot_real:
-        s += "Real";
+        ss << "Real";
         break;
     case QPDFObject::object_type_e::ot_name:
-        s += "Name";
+        ss << "Name";
         break;
     case QPDFObject::object_type_e::ot_string:
-        s += "String";
+        ss << "String";
         break;
     case QPDFObject::object_type_e::ot_operator:
-        s += "Operator";
+        ss << "Operator";
         break;
     case QPDFObject::object_type_e::ot_inlineimage:
-        s += "InlineImage";
+        ss << "InlineImage";
         break;
     case QPDFObject::object_type_e::ot_array:
-        s += "Array";
+        ss << "Array";
         break;
     case QPDFObject::object_type_e::ot_dictionary:
         if (h.hasKey("/Type")) {
-            s += std::string("Dictionary(type_=\"") + 
h.getKey("/Type").getName() + "\")";
+            ss << "Dictionary(Type=\"" << h.getKey("/Type").getName() << "\")";
         } else {
-            s += "Dictionary";
+            ss << "Dictionary";
         }
         break;
     case QPDFObject::object_type_e::ot_stream:
-        s += "Stream";
+        ss << "Stream";
         break;
     default:
-        s += "<Error>";
+        ss << "<Error>";
         break;
     }
 
-    return s;
+    return ss.str();
 }
 
 
@@ -130,13 +130,13 @@
 std::string objecthandle_repr_inner(QPDFObjectHandle h, uint depth, 
std::set<QPDFObjGen>* visited, bool* pure_expr)
 {
     StackGuard sg(" objecthandle_repr_inner");
-    std::ostringstream oss;
+    std::ostringstream ss;
 
     if (!h.isScalar()) {
         if (visited->count(h.getObjGen()) > 0) {
             *pure_expr = false;
-            oss << "<.get_object(" << h.getObjGen().getObj() << ", " << 
h.getObjGen().getGen() << ")>";
-            return oss.str();
+            ss << "<.get_object(" << h.getObjGen().getObj() << ", " << 
h.getObjGen().getGen() << ")>";
+            return ss.str();
         }
 
         if (!(h.getObjGen() == QPDFObjGen(0, 0)))
@@ -150,68 +150,68 @@
     case QPDFObject::object_type_e::ot_real:
     case QPDFObject::object_type_e::ot_name:
     case QPDFObject::object_type_e::ot_string:
-        oss << objecthandle_scalar_value(h);
+        ss << objecthandle_scalar_value(h);
         break;
     case QPDFObject::object_type_e::ot_operator:
-        oss << objecthandle_repr_typename_and_value(h);
+        ss << objecthandle_repr_typename_and_value(h);
         break;
     case QPDFObject::object_type_e::ot_inlineimage:
-        oss << objecthandle_pythonic_typename(h);
-        oss << "(";
-        oss << "data=<...>";
-        oss << ")";
+        ss << objecthandle_pythonic_typename(h);
+        ss << "(";
+        ss << "data=<...>";
+        ss << ")";
         break;
     case QPDFObject::object_type_e::ot_array:
-        oss << "[";
+        ss << "[";
         {
             bool first = true;
-            oss << " ";
+            ss << " ";
             for (auto item: h.getArrayAsVector()) {
-                if (!first) oss << ", ";
+                if (!first) ss << ", ";
                 first = false;
-                oss << objecthandle_repr_inner(item, depth, visited, 
pure_expr);
+                ss << objecthandle_repr_inner(item, depth, visited, pure_expr);
             }
-            oss << " ";
+            ss << " ";
         }
-        oss << "]";
+        ss << "]";
         break;
     case QPDFObject::object_type_e::ot_dictionary:
-        oss << "{"; // This will end the line
+        ss << "{"; // This will end the line
         {
             bool first = true;
-            oss << "\n";
+            ss << "\n";
             for (auto item: h.getDictAsMap()) {
-                if (!first) oss << ",\n";
+                if (!first) ss << ",\n";
                 first = false;
-                oss << std::string((depth + 1) * 2, ' '); // Indent each line
+                ss << std::string((depth + 1) * 2, ' '); // Indent each line
                 if (item.first == "/Parent" && item.second.isPagesObject()) {
                     // Don't visit /Parent keys since that just puts every 
page on the repr() of a single page
-                    oss << std::quoted(item.first) << ": <reference to 
/Pages>";
+                    ss << std::quoted(item.first) << ": <reference to /Pages>";
                 } else {
-                    oss << std::quoted(item.first) << ": " << 
objecthandle_repr_inner(item.second, depth + 1, visited, pure_expr);
+                    ss << std::quoted(item.first) << ": " << 
objecthandle_repr_inner(item.second, depth + 1, visited, pure_expr);
                 }
             }
-            oss << "\n";
+            ss << "\n";
         }
-        oss << std::string(depth * 2, ' '); // Restore previous indent level
-        oss << "}";
+        ss << std::string(depth * 2, ' '); // Restore previous indent level
+        ss << "}";
         break;
     case QPDFObject::object_type_e::ot_stream:
         *pure_expr = false;
-        oss << objecthandle_pythonic_typename(h);
-        oss << "(";
-        oss << "stream_dict=";
-        oss << objecthandle_repr_inner(h.getDict(), depth + 1, visited, 
pure_expr);
-        oss << ", ";
-        oss << "data=<...>";
-        oss << ")";
+        ss << objecthandle_pythonic_typename(h);
+        ss << "(";
+        ss << "stream_dict=";
+        ss << objecthandle_repr_inner(h.getDict(), depth + 1, visited, 
pure_expr);
+        ss << ", ";
+        ss << "data=<...>";
+        ss << ")";
         break;
     default:
-        oss << "???";
+        ss << "???";
         break;
     }
 
-    return oss.str();
+    return ss.str();
 }
 
 std::string objecthandle_repr(QPDFObjectHandle h)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/src/qpdf/pikepdf.cpp 
new/pikepdf-2.8.0/src/qpdf/pikepdf.cpp
--- old/pikepdf-2.5.2/src/qpdf/pikepdf.cpp      2021-02-01 02:05:12.000000000 
+0100
+++ new/pikepdf-2.8.0/src/qpdf/pikepdf.cpp      2021-03-01 01:21:28.000000000 
+0100
@@ -20,6 +20,7 @@
 #include <qpdf/QPDFExc.hh>
 #include <qpdf/QPDFSystemError.hh>
 #include <qpdf/QUtil.hh>
+#include <qpdf/Pl_Flate.hh>
 
 #include <pybind11/stl.h>
 #include <pybind11/iostream.h>
@@ -139,6 +140,15 @@
         },
         "If set to true, ``pikepdf.open(...access_mode=access_default)`` will 
use mmap."
     );
+    m.def("set_flate_compression_level",
+        [](int level) {
+            if (0 <= level && level <= 9)
+                Pl_Flate::setCompressionLevel(level);
+            else
+                throw py::value_error("Flate compression level must be between 
0 and 9");
+        },
+        "Set the compression level whenever the Flate compression algorithm is 
used."
+    );
 
     static py::exception<QPDFExc> exc_main(m, "PdfError");
     static py::exception<QPDFExc> exc_password(m, "PasswordError");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/src/qpdf/qpdf.cpp 
new/pikepdf-2.8.0/src/qpdf/qpdf.cpp
--- old/pikepdf-2.5.2/src/qpdf/qpdf.cpp 2021-02-01 02:05:12.000000000 +0100
+++ new/pikepdf-2.8.0/src/qpdf/qpdf.cpp 2021-03-01 01:21:28.000000000 +0100
@@ -359,7 +359,8 @@
     bool qdf=false,
     py::object progress=py::none(),
     py::object encryption=py::none(),
-    bool samefile_check=true)
+    bool samefile_check=true,
+    bool recompress_flate=false)
 {
     std::string description;
     QPDFWriter w(q);
@@ -380,6 +381,7 @@
         
w.setDecodeLevel(stream_decode_level.cast<qpdf_stream_decode_level_e>());
     }
     w.setObjectStreamMode(object_stream_mode);
+    w.setRecompressFlate(recompress_flate);
 
     py::object stream;
     bool should_close_stream = false;
@@ -661,7 +663,8 @@
             py::arg("qdf")=false,
             py::arg("progress")=py::none(),
             py::arg("encryption")=py::none(),
-            py::arg("samefile_check")=true
+            py::arg("samefile_check")=true,
+            py::arg("recompress_flate")=false
         )
         .def("_get_object_id", &QPDF::getObjectByID)
         .def("get_object",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/src/qpdf/qpdf_pagelist.cpp 
new/pikepdf-2.8.0/src/qpdf/qpdf_pagelist.cpp
--- old/pikepdf-2.5.2/src/qpdf/qpdf_pagelist.cpp        2021-02-01 
02:05:12.000000000 +0100
+++ new/pikepdf-2.8.0/src/qpdf/qpdf_pagelist.cpp        2021-03-01 
01:21:28.000000000 +0100
@@ -140,33 +140,51 @@
 
 void PageList::insert_page(size_t index, py::handle obj)
 {
-    QPDFObjectHandle page;
+    QPDFObjectHandle h;
     try {
-        page = obj.cast<QPDFObjectHandle>();
+        h = obj.cast<QPDFObjectHandle>();
     } catch (const py::cast_error&) {
         throw py::type_error("only pages can be inserted");
     }
-    if (!page.isPageObject())
-        throw py::type_error("only pages can be inserted");
 
-    this->insert_page(index, page);
+    this->insert_page(index, h);
 }
 
-void PageList::insert_page(size_t index, QPDFObjectHandle page)
+void PageList::insert_page(size_t index, QPDFObjectHandle h)
 {
     // Find out who owns us
-    QPDF *page_owner = page.getOwningQPDF();
+    QPDF *handle_owner = h.getOwningQPDF();
+    QPDFObjectHandle owned, page;
+    bool copied = false;
 
-    if (page_owner == this->qpdf.get()) {
+    if (handle_owner == this->qpdf.get() || !handle_owner) {
         // qpdf does not accept duplicating pages within the same file,
         // so manually create a copy
-        page = this->qpdf->makeIndirectObject(page.shallowCopy());
-    }
-    if (index != this->count()) {
-        QPDFObjectHandle refpage = this->get_page(index);
-        this->qpdf->addPageAt(page, true, refpage);
+        owned = this->qpdf->makeIndirectObject(h.shallowCopy());
+        copied = true;
     } else {
-        this->qpdf->addPage(page, false);
+        owned = h;
+    }
+
+    try {
+        if (!owned.isPageObject()) {
+            throw py::type_error("only pages can be inserted");
+        }
+
+        page = owned;
+        if (index != this->count()) {
+            QPDFObjectHandle refpage = this->get_page(index);
+            this->qpdf->addPageAt(page, true, refpage);
+        } else {
+            this->qpdf->addPage(page, false);
+        }
+    } catch (const std::runtime_error &e) {
+        if (copied) {
+            // If we created a new object to hold the page, and failed, delete
+            // the object we created.
+            this->qpdf->replaceObject(owned.getObjGen(), 
QPDFObjectHandle::newNull());
+        }
+        throw;
     }
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/tests/test_image_access.py 
new/pikepdf-2.8.0/tests/test_image_access.py
--- old/pikepdf-2.5.2/tests/test_image_access.py        2021-02-01 
02:05:12.000000000 +0100
+++ new/pikepdf-2.8.0/tests/test_image_access.py        2021-03-01 
01:21:28.000000000 +0100
@@ -10,6 +10,7 @@
 
 import pikepdf
 from pikepdf import (
+    Array,
     Dictionary,
     Name,
     Pdf,
@@ -485,3 +486,29 @@
     ):
         pim = PdfImage(xobj)
         _icc = pim.icc
+
+
+def test_dict_or_array_dict():
+    pdf = pikepdf.new()
+    imobj = Stream(
+        pdf,
+        b'dummy',
+        BitsPerComponent=1,
+        ColorSpace=Name.DeviceGray,
+        DecodeParms=Array(
+            [
+                Dictionary(
+                    BlackIs1=False,
+                    Columns=16,
+                    K=-1,
+                )
+            ]
+        ),
+        Filter=Array([Name.CCITTFaxDecode]),
+        Height=16,
+        Width=16,
+        Type=Name.XObject,
+        Subtype=Name.Image,
+    )
+    pim = pikepdf.PdfImage(imobj)
+    assert pim.decode_parms[0].K == -1  # Check that array of dict is unpacked 
properly
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/tests/test_object.py 
new/pikepdf-2.8.0/tests/test_object.py
--- old/pikepdf-2.5.2/tests/test_object.py      2021-02-01 02:05:12.000000000 
+0100
+++ new/pikepdf-2.8.0/tests/test_object.py      2021-03-01 01:21:28.000000000 
+0100
@@ -2,6 +2,7 @@
 import sys
 from copy import copy
 from decimal import Decimal, InvalidOperation
+from distutils.version import LooseVersion
 from math import isclose, isfinite
 from zlib import compress
 
@@ -285,9 +286,14 @@
                 '/Dictionary': Dictionary({'/Color': 'Red'}),
             }
         )
-        expected = """\
+        if LooseVersion(pikepdf.__libqpdf_version__) >= LooseVersion('10.2.0'):
+            short_pi = '3.14'
+        else:
+            short_pi = '3.140000'
+        expected = (
+            """\
             pikepdf.Dictionary({
-                "/Array": [ 1, 2, Decimal('3.140000') ],
+                "/Array": [ 1, 2, Decimal('%s') ],
                 "/Boolean": True,
                 "/Dictionary": {
                     "/Color": "Red"
@@ -298,6 +304,8 @@
                 "/String": "hi"
             })
         """
+            % short_pi
+        )
 
         def strip_all_whitespace(s):
             return ''.join(s.split())
@@ -420,7 +428,7 @@
     json_bytes = d.to_json(False)
     as_dict = json.loads(json_bytes)
     assert as_dict == {
-        "/Array": [1, 2, 3.140000],
+        "/Array": [1, 2, 3.14],
         "/Boolean": True,
         "/Dictionary": {"/Color": "Red"},
         "/Integer": 42,
@@ -605,3 +613,25 @@
     p = pikepdf.new()
     with pytest.raises(TypeError, match='data'):
         Stream(p)
+
+
+def test_stream_no_dangling_stream_on_failure():
+    p = pikepdf.new()
+    num_objects = len(p.objects)
+    with pytest.raises(AttributeError):
+        Stream(p, b'3.14159', ['Not a mapping object'])
+    assert len(p.objects) == num_objects, "A dangling object was created"
+
+
+def test_dict_of_dict():
+    d = Dictionary(One=1, Two=2)
+    d2 = Dictionary(d)
+    assert d == d2
+    assert d is not d2
+
+
+def test_array_of_array():
+    a = Array([1, 2])
+    a2 = Array(a)
+    assert a == a2
+    assert a is not a2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/tests/test_page.py 
new/pikepdf-2.8.0/tests/test_page.py
--- old/pikepdf-2.5.2/tests/test_page.py        2021-02-01 02:05:12.000000000 
+0100
+++ new/pikepdf-2.8.0/tests/test_page.py        2021-03-01 01:21:28.000000000 
+0100
@@ -56,3 +56,31 @@
     res2 = graph_page.add_resource(d, Name.XObject, prefix='Im')
     assert str(res2).startswith("/Im")
     assert graph_page.resources.XObject[res2].Height == 1
+
+
+def test_add_unowned_page():  # issue 174
+    pdf = pikepdf.new()
+    d = pikepdf.Dictionary(Type=pikepdf.Name.Page)
+    pdf.pages.append(d)
+
+
+def test_failed_add_page_cleanup():
+    pdf = pikepdf.new()
+    d = pikepdf.Dictionary(Type=pikepdf.Name.NotAPage)
+    num_objects = len(pdf.objects)
+    with pytest.raises(TypeError, match="only pages can be inserted"):
+        pdf.pages.append(d)
+    assert len(pdf.pages) == 0
+
+    # If we fail to add a new page, we expect one new null object handle to be
+    # be added (since QPDF does not remove the object outright)
+    assert len(pdf.objects) == num_objects + 1, "QPDF semantics changed"
+    assert pdf.objects[-1] is None, "Left a stale object behind without 
deleting"
+
+    # But we'd better not delete an existing object...
+    d2 = pdf.make_indirect(pikepdf.Dictionary(Type=pikepdf.Name.StillNotAPage))
+    with pytest.raises(TypeError, match="only pages can be inserted"):
+        pdf.pages.append(d2)
+    assert len(pdf.pages) == 0
+
+    assert d2.same_owner_as(pdf.Root)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/tests/test_pdf.py 
new/pikepdf-2.8.0/tests/test_pdf.py
--- old/pikepdf-2.5.2/tests/test_pdf.py 2021-02-01 02:05:12.000000000 +0100
+++ new/pikepdf-2.8.0/tests/test_pdf.py 2021-03-01 01:21:28.000000000 +0100
@@ -5,6 +5,7 @@
 import locale
 import shutil
 import sys
+import zlib
 from io import BytesIO, StringIO
 from os import fspath
 from pathlib import Path
@@ -284,3 +285,28 @@
 
 def test_repr(trivial):
     assert repr(trivial).startswith('<')
+
+
+def test_recompress(resources, outdir):
+    with pikepdf.open(resources / 'image-mono-inline.pdf') as pdf:
+        obj = pdf.get_object((7, 0))
+        assert isinstance(obj, pikepdf.Stream)
+
+        data = obj.read_bytes()
+        data_z1 = zlib.compress(data, level=0)  # No compression but zlib 
wrapper
+
+        obj.write(data_z1, filter=pikepdf.Name.FlateDecode)
+
+        bigger = outdir / 'a.pdf'
+        smaller = outdir / 'b.pdf'
+        pdf.save(bigger, recompress_flate=False)
+        pdf.save(smaller, recompress_flate=True)
+        assert smaller.stat().st_size < bigger.stat().st_size
+
+
+def test_flate_compression_level():
+    # We don't want to change the compression level because it's global state
+    # and will change subsequent test results, so just ping it with an invalid
+    # value to get partial code coverage.
+    with pytest.raises(ValueError):
+        pikepdf._qpdf.set_flate_compression_level(99)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-2.5.2/tests/test_sanity.py 
new/pikepdf-2.8.0/tests/test_sanity.py
--- old/pikepdf-2.5.2/tests/test_sanity.py      2021-02-01 02:05:12.000000000 
+0100
+++ new/pikepdf-2.8.0/tests/test_sanity.py      2021-03-01 01:21:28.000000000 
+0100
@@ -16,7 +16,7 @@
 def test_minimum_qpdf_version():
     from pikepdf import _qpdf
 
-    assert LooseVersion(_qpdf.qpdf_version()) >= LooseVersion('10.0.0')
+    assert LooseVersion(_qpdf.qpdf_version()) >= LooseVersion('10.0.3')
 
 
 def test_open_pdf(resources):

Reply via email to