Fix the bytes output device

This commit is contained in:
Kovid Goyal 2023-05-11 13:57:50 +05:30
parent 76fbbef9d0
commit 2891687f6a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 41 additions and 25 deletions

View File

@ -189,7 +189,6 @@ def test_save_to(src, dest):
def test_podofo(): def test_podofo():
import tempfile import tempfile
from io import BytesIO
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.xmp import metadata_to_xmp_packet from calibre.ebooks.metadata.xmp import metadata_to_xmp_packet
# {{{ # {{{
@ -203,11 +202,13 @@ def test_podofo():
p.title = mi.title p.title = mi.title
p.author = mi.authors[0] p.author = mi.authors[0]
p.set_xmp_metadata(xmp_packet) p.set_xmp_metadata(xmp_packet)
buf = BytesIO()
p.save_to_fileobj(buf)
raw = buf.getvalue()
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f:
f.write(raw) p.save_to_fileobj(f)
f.seek(0)
fraw = f.read()
wraw = p.write()
if fraw != wraw:
raise ValueError("write() and save_to_fileobj() resulted in different output")
try: try:
p = podofo.PDFDoc() p = podofo.PDFDoc()
p.open(f.name) p.open(f.name)

View File

@ -8,6 +8,7 @@
#include "global.h" #include "global.h"
#include <iostream> #include <iostream>
#include <algorithm> #include <algorithm>
#include <new>
#include <string_view> #include <string_view>
using namespace pdf; using namespace pdf;
@ -97,7 +98,7 @@ class BytesOutputDevice : public OutputStreamDevice {
pyunique_ptr bytes; pyunique_ptr bytes;
size_t written; size_t written;
public: public:
BytesOutputDevice() : bytes(PyBytes_FromStringAndSize(NULL, 1 * 1024 *1024)) { SetAccess(DeviceAccess::Write); } BytesOutputDevice() : bytes(), written(0) { SetAccess(DeviceAccess::Write); }
size_t GetLength() const { return written; } size_t GetLength() const { return written; }
size_t GetPosition() const { return written; } size_t GetPosition() const { return written; }
size_t capacity() const { return bytes ? PyBytes_GET_SIZE(bytes.get()) : 0; } size_t capacity() const { return bytes ? PyBytes_GET_SIZE(bytes.get()) : 0; }
@ -106,19 +107,30 @@ class BytesOutputDevice : public OutputStreamDevice {
void writeBuffer(const char* src, size_t src_sz) { void writeBuffer(const char* src, size_t src_sz) {
if (written + src_sz > capacity()) { if (written + src_sz > capacity()) {
PyObject* old = bytes.release(); PyObject* old = bytes.release();
if (_PyBytes_Resize(&old, std::max(written + src_sz, 2 * capacity())) != 0) { static const size_t initial_capacity = 1024 * 1024;
return; if (old) {
if (_PyBytes_Resize(&old, std::max(written + src_sz, 2 * std::max(capacity(), initial_capacity))) != 0) {
throw std::bad_alloc();
}
} else {
old = PyBytes_FromStringAndSize(NULL, std::max(written + src_sz, initial_capacity));
if (!old) throw std::bad_alloc();
} }
bytes.reset(old); bytes.reset(old);
} }
if (bytes) { if (bytes) {
memcpy(PyBytes_AS_STRING(bytes.get()), src, src_sz); memcpy(PyBytes_AS_STRING(bytes.get()) + written, src, src_sz);
written += src_sz; written += src_sz;
} }
} }
void Flush() { } void Flush() { }
PyObject* Release() { return bytes.release(); } PyObject* Release() {
auto ans = bytes.release();
_PyBytes_Resize(&ans, written);
written = 0;
return ans;
}
}; };
static PyObject * static PyObject *

View File

@ -12,8 +12,6 @@ using namespace PoDoFo;
#define NUKE(x) { Py_XDECREF(x); x = NULL; } #define NUKE(x) { Py_XDECREF(x); x = NULL; }
#define PODOFO_RAISE_ERROR(code) throw ::PoDoFo::PdfError(code, __FILE__, __LINE__) #define PODOFO_RAISE_ERROR(code) throw ::PoDoFo::PdfError(code, __FILE__, __LINE__)
class pyerr : public std::exception {
};
class MyOutputDevice : public OutputStreamDevice { class MyOutputDevice : public OutputStreamDevice {
@ -34,7 +32,7 @@ class MyOutputDevice : public OutputStreamDevice {
public: public:
MyOutputDevice(PyObject *file) : tell_func(0), seek_func(0), read_func(0), write_func(0), flush_func(0), written(0) { MyOutputDevice(PyObject *file) : tell_func(0), seek_func(0), read_func(0), write_func(0), flush_func(0), written(0) {
SetAccess(DeviceAccess::Write); SetAccess(DeviceAccess::Write);
#define GA(f, a) { if((f = PyObject_GetAttrString(file, a)) == NULL) throw pyerr(); } #define GA(f, a) { if((f = PyObject_GetAttrString(file, a)) == NULL) throw std::exception(); }
GA(tell_func, "tell"); GA(tell_func, "tell");
GA(seek_func, "seek"); GA(seek_func, "seek");
GA(read_func, "read"); GA(read_func, "read");
@ -65,7 +63,7 @@ class MyOutputDevice : public OutputStreamDevice {
if( !pszFormat ) { PODOFO_RAISE_ERROR(PdfErrorCode::InvalidHandle); } if( !pszFormat ) { PODOFO_RAISE_ERROR(PdfErrorCode::InvalidHandle); }
buf = new (std::nothrow) char[lBytes+1]; buf = new (std::nothrow) char[lBytes+1];
if (buf == NULL) { PyErr_NoMemory(); throw pyerr(); } if (buf == NULL) { PyErr_NoMemory(); throw std::exception(); }
// Note: PyOS_vsnprintf produces broken output on windows // Note: PyOS_vsnprintf produces broken output on windows
res = vsnprintf(buf, lBytes, pszFormat, args); res = vsnprintf(buf, lBytes, pszFormat, args);
@ -73,7 +71,7 @@ class MyOutputDevice : public OutputStreamDevice {
if (res < 0) { if (res < 0) {
PyErr_SetString(PyExc_Exception, "Something bad happened while calling vsnprintf"); PyErr_SetString(PyExc_Exception, "Something bad happened while calling vsnprintf");
delete[] buf; delete[] buf;
throw pyerr(); throw std::exception();
} }
Write(buf, static_cast<size_t>(res)); Write(buf, static_cast<size_t>(res));
@ -99,7 +97,7 @@ class MyOutputDevice : public OutputStreamDevice {
char *buf = NULL; char *buf = NULL;
Py_ssize_t len = 0; Py_ssize_t len = 0;
if ((temp = PyLong_FromSize_t(lLen)) == NULL) throw pyerr(); if ((temp = PyLong_FromSize_t(lLen)) == NULL) throw std::exception();
ret = PyObject_CallFunctionObjArgs(read_func, temp, NULL); ret = PyObject_CallFunctionObjArgs(read_func, temp, NULL);
NUKE(temp); NUKE(temp);
if (ret != NULL) { if (ret != NULL) {
@ -114,19 +112,19 @@ class MyOutputDevice : public OutputStreamDevice {
if (PyErr_Occurred() == NULL) if (PyErr_Occurred() == NULL)
PyErr_SetString(PyExc_Exception, "Failed to read data from python file object"); PyErr_SetString(PyExc_Exception, "Failed to read data from python file object");
throw pyerr(); throw std::exception();
} }
void Seek(size_t offset) { void Seek(size_t offset) {
PyObject *ret, *temp; PyObject *ret, *temp;
if ((temp = PyLong_FromSize_t(offset)) == NULL) throw pyerr(); if ((temp = PyLong_FromSize_t(offset)) == NULL) throw std::exception();
ret = PyObject_CallFunctionObjArgs(seek_func, temp, NULL); ret = PyObject_CallFunctionObjArgs(seek_func, temp, NULL);
NUKE(temp); NUKE(temp);
if (ret == NULL) { if (ret == NULL) {
if (PyErr_Occurred() == NULL) if (PyErr_Occurred() == NULL)
PyErr_SetString(PyExc_Exception, "Failed to seek in python file object"); PyErr_SetString(PyExc_Exception, "Failed to seek in python file object");
throw pyerr(); throw std::exception();
} }
Py_DECREF(ret); Py_DECREF(ret);
} }
@ -139,16 +137,16 @@ class MyOutputDevice : public OutputStreamDevice {
if (ret == NULL) { if (ret == NULL) {
if (PyErr_Occurred() == NULL) if (PyErr_Occurred() == NULL)
PyErr_SetString(PyExc_Exception, "Failed to call tell() on python file object"); PyErr_SetString(PyExc_Exception, "Failed to call tell() on python file object");
throw pyerr(); throw std::exception();
} }
if (!PyNumber_Check(ret)) { if (!PyNumber_Check(ret)) {
Py_DECREF(ret); Py_DECREF(ret);
PyErr_SetString(PyExc_Exception, "tell() method did not return a number"); PyErr_SetString(PyExc_Exception, "tell() method did not return a number");
throw pyerr(); throw std::exception();
} }
ans = PyLong_AsUnsignedLongMask(ret); ans = PyLong_AsUnsignedLongMask(ret);
Py_DECREF(ret); Py_DECREF(ret);
if (PyErr_Occurred() != NULL) throw pyerr(); if (PyErr_Occurred() != NULL) throw std::exception();
return static_cast<size_t>(ans); return static_cast<size_t>(ans);
} }
@ -159,7 +157,7 @@ class MyOutputDevice : public OutputStreamDevice {
PyObject *ret, *temp = NULL; PyObject *ret, *temp = NULL;
temp = PyBytes_FromStringAndSize(pBuffer, static_cast<Py_ssize_t>(lLen)); temp = PyBytes_FromStringAndSize(pBuffer, static_cast<Py_ssize_t>(lLen));
if (temp == NULL) throw pyerr(); if (temp == NULL) throw std::exception();
ret = PyObject_CallFunctionObjArgs(write_func, temp, NULL); ret = PyObject_CallFunctionObjArgs(write_func, temp, NULL);
NUKE(temp); NUKE(temp);
@ -167,7 +165,7 @@ class MyOutputDevice : public OutputStreamDevice {
if (ret == NULL) { if (ret == NULL) {
if (PyErr_Occurred() == NULL) if (PyErr_Occurred() == NULL)
PyErr_SetString(PyExc_Exception, "Failed to call write() on python file object"); PyErr_SetString(PyExc_Exception, "Failed to call write() on python file object");
throw pyerr(); throw std::exception();
} }
Py_DECREF(ret); Py_DECREF(ret);
update_written(); update_written();
@ -185,6 +183,7 @@ PyObject* pdf::write_doc(PdfMemDocument *doc, PyObject *f) {
try { try {
doc->Save(d); doc->Save(d);
d.Flush();
} catch(const PdfError & err) { } catch(const PdfError & err) {
podofo_set_exception(err); return NULL; podofo_set_exception(err); return NULL;
} catch (...) { } catch (...) {

View File

@ -7,6 +7,7 @@
#include "global.h" #include "global.h"
#include <sstream> #include <sstream>
#include <stdexcept>
using namespace pdf; using namespace pdf;
@ -29,5 +30,8 @@ pdf::podofo_convert_pdfstring(const PdfString &s) {
const PdfString const PdfString
pdf::podofo_convert_pystring(PyObject *val) { pdf::podofo_convert_pystring(PyObject *val) {
return PdfString(reinterpret_cast<const char*>(PyUnicode_AsUTF8(val))); Py_ssize_t len;
const char *data = PyUnicode_AsUTF8AndSize(val, &len);
if (data == NULL) throw std::runtime_error("Failed to convert python string to UTF-8, possibly not a string object");
return PdfString::FromRaw(bufferview(data, len));
} }