diff --git a/setup/extensions.py b/setup/extensions.py index 2f2e2aa9ba..f4ed22687b 100644 --- a/setup/extensions.py +++ b/setup/extensions.py @@ -139,6 +139,7 @@ extensions = [ Extension('podofo', [ 'calibre/utils/podofo/utils.cpp', + 'calibre/utils/podofo/output.cpp', 'calibre/utils/podofo/doc.cpp', 'calibre/utils/podofo/outline.cpp', 'calibre/utils/podofo/podofo.cpp', diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index a547d3e650..a9cb951e35 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -15,12 +15,11 @@ from PyQt4.Qt import (QEventLoop, QObject, QPrinter, QSizeF, Qt, QPainter, QPixmap, QTimer, pyqtProperty, QString, QSize) from PyQt4.QtWebKit import QWebView, QWebPage, QWebSettings -from calibre.constants import filesystem_encoding from calibre.ptempfile import PersistentTemporaryDirectory from calibre.ebooks.pdf.pageoptions import (unit, paper_size, orientation) from calibre.ebooks.pdf.outline_writer import Outline from calibre.ebooks.metadata import authors_to_string -from calibre.ptempfile import PersistentTemporaryFile, TemporaryFile +from calibre.ptempfile import PersistentTemporaryFile from calibre import (__appname__, __version__, fit_image, isosx, force_unicode) from calibre.ebooks.oeb.display.webview import load_html @@ -352,12 +351,7 @@ class PDFWriter(QObject): # {{{ if self.metadata.tags: self.doc.keywords = self.metadata.tags self.outline(self.doc) - with TemporaryFile(u'pdf_out.pdf') as tf: - if isinstance(tf, unicode): - tf = tf.encode(filesystem_encoding) - self.doc.save(tf) - with open(tf, 'rb') as src: - shutil.copyfileobj(src, self.out_stream) + self.doc.save_to_fileobj(self.out_stream) self.render_succeeded = True finally: self._delete_tmpdir() diff --git a/src/calibre/utils/podofo/__init__.py b/src/calibre/utils/podofo/__init__.py index 3134dcd1ba..13c12a9bb3 100644 --- a/src/calibre/utils/podofo/__init__.py +++ b/src/calibre/utils/podofo/__init__.py @@ -94,9 +94,8 @@ def delete_all_but(path, pages): if page not in pages: p.delete_page(page) - raw = p.write() with open(path, 'wb') as f: - f.write(raw) + f.save_to_fileobj(path) def test_outline(src): podofo = get_podofo() @@ -114,7 +113,17 @@ def test_outline(src): f.write(raw) print 'Outlined PDF:', out +def test_save_to(src, dest): + podofo = get_podofo() + p = podofo.PDFDoc() + with open(src, 'rb') as f: + raw = f.read() + p.load(raw) + with open(dest, 'wb') as out: + p.save_to_fileobj(out) + print ('Wrote PDF of size:', out.tell()) + if __name__ == '__main__': import sys - test_outline(sys.argv[-1]) + test_save_to(sys.argv[-2], sys.argv[-1]) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 7166b2320e..90bfc27921 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -104,6 +104,15 @@ PDFDoc_write(PDFDoc *self, PyObject *args) { return ans; } + +static PyObject * +PDFDoc_save_to_fileobj(PDFDoc *self, PyObject *args) { + PyObject *f; + + if (!PyArg_ParseTuple(args, "O", &f)) return NULL; + return write_doc(self->doc, f); +} + // }}} // extract_first_page() {{{ @@ -453,6 +462,9 @@ static PyMethodDef PDFDoc_methods[] = { {"write", (PyCFunction)PDFDoc_write, METH_VARARGS, "Return the PDF document as a bytestring." }, + {"save_to_fileobj", (PyCFunction)PDFDoc_save_to_fileobj, METH_VARARGS, + "Write the PDF document to the soecified file-like object." + }, {"extract_first_page", (PyCFunction)PDFDoc_extract_first_page, METH_VARARGS, "extract_first_page() -> Remove all but the first page." }, diff --git a/src/calibre/utils/podofo/global.h b/src/calibre/utils/podofo/global.h index fa9a141b21..4a180d86a0 100644 --- a/src/calibre/utils/podofo/global.h +++ b/src/calibre/utils/podofo/global.h @@ -41,6 +41,7 @@ extern void podofo_set_exception(const PdfError &err); extern PyObject * podofo_convert_pdfstring(const PdfString &s); extern PdfString * podofo_convert_pystring(PyObject *py); extern PdfString * podofo_convert_pystring_single_byte(PyObject *py); +extern PyObject* write_doc(PdfMemDocument *doc, PyObject *f); } diff --git a/src/calibre/utils/podofo/output.cpp b/src/calibre/utils/podofo/output.cpp new file mode 100644 index 0000000000..63e1270af6 --- /dev/null +++ b/src/calibre/utils/podofo/output.cpp @@ -0,0 +1,172 @@ +/* + * output.cpp + * Copyright (C) 2012 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "global.h" + +using namespace PoDoFo; + +class pyerr : public std::exception { +}; + +class OutputDevice : public PdfOutputDevice { + + private: + PyObject *file; + size_t written; + + void update_written() { + size_t pos; + pos = Tell(); + if (pos > written) written = pos; + } + + public: + OutputDevice(PyObject *f) : file(f), written(0) { Py_XINCREF(file); } + ~OutputDevice() { Py_XDECREF(file); file = NULL; } + + size_t GetLength() const { return written; } + + long PrintVLen(const char* pszFormat, va_list args) { + char buf[10]; + int res; + + if( !pszFormat ) { PODOFO_RAISE_ERROR( ePdfError_InvalidHandle ); } + + res = PyOS_vsnprintf(buf, 1, pszFormat, args); + if (res < 0) { + PyErr_SetString(PyExc_Exception, "Something bad happend while calling PyOS_vsnprintf"); + throw pyerr(); + } + return static_cast(res+1); + } + + void PrintV( const char* pszFormat, long lBytes, va_list args ) { + char *buf; + int res; + + if( !pszFormat ) { PODOFO_RAISE_ERROR( ePdfError_InvalidHandle ); } + + buf = new (std::nothrow) char[lBytes+1]; + if (buf == NULL) { PyErr_NoMemory(); throw pyerr(); } + + res = PyOS_vsnprintf(buf, lBytes, pszFormat, args); + + if (res < 0) { + PyErr_SetString(PyExc_Exception, "Something bad happend while calling PyOS_vsnprintf"); + delete[] buf; + throw pyerr(); + } + + Write(buf, static_cast(res)); + delete[] buf; + } + + void Print( const char* pszFormat, ... ) + { + va_list args; + long lBytes; + + va_start( args, pszFormat ); + lBytes = PrintVLen(pszFormat, args); + va_end( args ); + + va_start( args, pszFormat ); + PrintV(pszFormat, lBytes, args); + va_end( args ); + } + + size_t Read( char* pBuffer, size_t lLen ) { + PyObject *ret; + char *buf = NULL; + Py_ssize_t len = 0; + + ret = PyObject_CallMethod(file, (char*)"read", (char*)"n", static_cast(lLen)); + if (ret != NULL) { + if (PyBytes_AsStringAndSize(ret, &buf, &len) != -1) { + memcpy(pBuffer, buf, len); + Py_DECREF(ret); + return static_cast(len); + } + Py_DECREF(ret); + } + + if (PyErr_Occurred() == NULL) + PyErr_SetString(PyExc_Exception, "Failed to read data from python file object"); + + throw pyerr(); + + } + + void Seek(size_t offset) { + PyObject *ret; + ret = PyObject_CallMethod(file, (char*)"seek", (char*)"n", static_cast(offset)); + if (ret == NULL) { + if (PyErr_Occurred() == NULL) + PyErr_SetString(PyExc_Exception, "Failed to seek in python file object"); + throw pyerr(); + } + Py_DECREF(ret); + } + + size_t Tell() const { + PyObject *ret; + unsigned long ans; + + ret = PyObject_CallMethod(file, (char*)"tell", NULL); + if (ret == NULL) { + if (PyErr_Occurred() == NULL) + PyErr_SetString(PyExc_Exception, "Failed to call tell() on python file object"); + throw pyerr(); + } + if (!PyNumber_Check(ret)) { + Py_DECREF(ret); + PyErr_SetString(PyExc_Exception, "tell() method did not return a number"); + throw pyerr(); + } + ans = PyInt_AsUnsignedLongMask(ret); + Py_DECREF(ret); + if (PyErr_Occurred() != NULL) throw pyerr(); + + return static_cast(ans); + } + + void Write(const char* pBuffer, size_t lLen) { + PyObject *ret; + + ret = PyObject_CallMethod(file, (char*)"write", (char*)"s#", pBuffer, (int)lLen); + if (ret == NULL) { + if (PyErr_Occurred() == NULL) + PyErr_SetString(PyExc_Exception, "Failed to call write() on python file object"); + throw pyerr(); + } + Py_DECREF(ret); + update_written(); + } + + void Flush() { + Py_XDECREF(PyObject_CallMethod(file, (char*)"flush", NULL)); + } + +}; + + +PyObject* pdf::write_doc(PdfMemDocument *doc, PyObject *f) { + OutputDevice d(f); + + try { + doc->Write(&d); + } catch(const PdfError & err) { + podofo_set_exception(err); return NULL; + } catch (...) { + if (PyErr_Occurred() == NULL) + PyErr_SetString(PyExc_Exception, "An unknown error occurred while trying to write the pdf to the file object"); + return NULL; + } + + Py_RETURN_NONE; +} +