1 /*
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5  *
6  * Copyright (C) 2017, James R. Barlow (https://github.com/jbarlow83/)
7  */
8 
9 #pragma once
10 
11 #include <exception>
12 #include <vector>
13 #include <map>
14 
15 #include <qpdf/PointerHolder.hh>
16 #include <qpdf/QPDF.hh>
17 #include <qpdf/QPDFObjectHandle.hh>
18 
19 #include <pybind11/pybind11.h>
20 #include <pybind11/stl.h>
21 #include <pybind11/stl_bind.h>
22 
23 using uint = unsigned int;
24 
25 namespace pybind11 {
26 PYBIND11_RUNTIME_EXCEPTION(attr_error, PyExc_AttributeError);
27 PYBIND11_RUNTIME_EXCEPTION(notimpl_error, PyExc_NotImplementedError);
28 }; // namespace pybind11
29 
30 // Declare PointerHolder<T> as a smart pointer
31 // https://pybind11.readthedocs.io/en/stable/advanced/smart_ptrs.html#custom-smart-pointers
32 PYBIND11_DECLARE_HOLDER_TYPE(T, PointerHolder<T>);
33 namespace pybind11 {
34 namespace detail {
35 template <typename T>
36 struct holder_helper<PointerHolder<T>> {
37     static const T *get(const PointerHolder<T> &p) { return p.getPointer(); }
38 };
39 } // namespace detail
40 } // namespace pybind11
41 
42 // From object_convert.cpp
43 pybind11::object decimal_from_pdfobject(QPDFObjectHandle h);
44 
45 namespace pybind11 {
46 namespace detail {
47 template <>
48 struct type_caster<QPDFObjectHandle> : public type_caster_base<QPDFObjectHandle> {
49     using base = type_caster_base<QPDFObjectHandle>;
50 
51 protected:
52     QPDFObjectHandle value;
53 
54 public:
55     /**
56      * Conversion part 1 (Python->C++): convert a PyObject into a Object
57      */
58     bool load(handle src, bool convert)
59     {
60         // Do whatever our base does
61         // Potentially we could convert some scalrs to QPDFObjectHandle here,
62         // but most of the interfaces just expect straight C++ types.
63         return base::load(src, convert);
64     }
65 
66     /**
67      * Conversion part 2 (C++ -> Python): convert an instance into
68      * a Python object.
69      * Purpose of this is to establish the indirect keep_alive relationship
70      * between QPDF and objects that refer back to in ways that pybind11
71      * can't trace on its own.
72      * We also convert several QPDFObjectHandle types to native Python
73      * objects here.
74      * The ==take_ownership code paths are currently unused but present
75      * for completeness. They are unused because pybind11 only sets
76      * take_ownership when a binding returns raw pointers to Python, and
77      * by making this caster private we prohibit that.
78      */
79 private:
80     // 'private': disallow returning pointers to QPDFObjectHandle from bindings
81     static handle cast(
82         const QPDFObjectHandle *csrc, return_value_policy policy, handle parent)
83     {
84         QPDFObjectHandle *src = const_cast<QPDFObjectHandle *>(csrc);
85         if (!csrc)
86             return none().release();
87 
88         bool primitive = true;
89         handle h;
90 
91         switch (src->getTypeCode()) {
92         case QPDFObject::object_type_e::ot_null:
93             h = pybind11::none().release();
94             break;
95         case QPDFObject::object_type_e::ot_integer:
96             h = pybind11::int_(src->getIntValue()).release();
97             break;
98         case QPDFObject::object_type_e::ot_boolean:
99             h = pybind11::bool_(src->getBoolValue()).release();
100             break;
101         case QPDFObject::object_type_e::ot_real:
102             h = decimal_from_pdfobject(*src).release();
103             break;
104         default:
105             primitive = false;
106             break;
107         }
108         if (primitive && h) {
109             if (policy == return_value_policy::take_ownership)
110                 // LCOV_EXCL_START
111                 // See explanation above - does not happen.
112                 delete csrc;
113             // LCOV_EXCL_STOP
114             return h;
115         }
116 
117         QPDF *owner = src->getOwningQPDF();
118         if (policy == return_value_policy::take_ownership) {
119             // LCOV_EXCL_START
120             // See explanation above - does not happen.
121             h = base::cast(std::move(*csrc), policy, parent);
122             delete csrc;
123             // LCOV_EXCL_STOP
124         } else {
125             h = base::cast(*csrc, policy, parent);
126         }
127         if (owner) {
128             // Find the Python object that refers to our owner
129             // Can do that by casting or more direct lookup
130             // auto pyqpdf = pybind11::cast(owner);
131             auto tinfo    = get_type_info(typeid(QPDF));
132             handle pyqpdf = get_object_handle(owner, tinfo);
133 
134             // Tell pybind11 that it must keep pyqpdf alive as long as h is
135             // alive
136             keep_alive_impl(h, pyqpdf);
137         }
138         return h;
139     }
140 
141 public:
142     static handle cast(
143         QPDFObjectHandle &&src, return_value_policy policy, handle parent)
144     {
145         return cast(&src, return_value_policy::move, parent);
146     }
147 
148     static handle cast(
149         const QPDFObjectHandle &src, return_value_policy policy, handle parent)
150     {
151         if (policy == return_value_policy::automatic ||
152             policy == return_value_policy::automatic_reference)
153             policy = return_value_policy::copy;
154         return cast(&src, policy, parent);
155     }
156 };
157 } // namespace detail
158 } // namespace pybind11
159 
160 namespace py = pybind11;
161 
162 PYBIND11_MAKE_OPAQUE(std::vector<QPDFObjectHandle>);
163 
164 typedef std::map<std::string, QPDFObjectHandle> ObjectMap;
165 PYBIND11_MAKE_OPAQUE(ObjectMap);
166 
167 // From qpdf.cpp
168 void init_qpdf(py::module_ &m);
169 
170 // From object.cpp
171 size_t list_range_check(QPDFObjectHandle h, int index);
172 void init_object(py::module_ &m);
173 
174 // From object_repr.cpp
175 std::string objecthandle_scalar_value(QPDFObjectHandle h, bool escaped = true);
176 std::string objecthandle_pythonic_typename(QPDFObjectHandle h);
177 std::string objecthandle_repr_typename_and_value(QPDFObjectHandle h);
178 std::string objecthandle_repr(QPDFObjectHandle h);
179 
180 // From object_convert.cpp
181 py::object decimal_from_pdfobject(QPDFObjectHandle h);
182 QPDFObjectHandle objecthandle_encode(const py::handle handle);
183 std::vector<QPDFObjectHandle> array_builder(const py::iterable iter);
184 std::map<std::string, QPDFObjectHandle> dict_builder(const py::dict dict);
185 
186 // From annotation.cpp
187 void init_annotation(py::module_ &m);
188 
189 // From page.cpp
190 void init_page(py::module_ &m);
191 size_t page_index(QPDF &owner, QPDFObjectHandle page);
192 
193 // From rectangle.cpp
194 void init_rectangle(py::module_ &m);
195 
196 inline char *fix_pypy36_const_char(const char *s)
197 {
198     // PyPy 7.3.1 (=Python 3.6) has a few functions incorrectly defined as requiring
199     // char* where CPython specifies const char*. PyPy corrected this in newer versions.
200     // So this harmless shim is needed to support some older PyPy's.
201     return const_cast<char *>(s);
202 }
203 
204 inline void deprecation_warning(const char *msg)
205 {
206     py::object warn = py::module_::import("warnings").attr("warn");
207     py::object DeprecationWarning =
208         py::module_::import("builtins").attr("DeprecationWarning");
209     warn(msg, DeprecationWarning, /*stackleverl=*/1);
210 }
211 
212 // Support for recursion checks
213 class StackGuard {
214 public:
215     StackGuard(const char *where)
216     {
217         Py_EnterRecursiveCall(fix_pypy36_const_char(where));
218     }
219     StackGuard(const StackGuard &) = delete;
220     StackGuard &operator=(const StackGuard &) = delete;
221     StackGuard(StackGuard &&)                 = delete;
222     StackGuard &operator=(StackGuard &&) = delete;
223     ~StackGuard() { Py_LeaveRecursiveCall(); }
224 };
225