diff --git a/src/calibre/ebooks/conversion/plugins/pdf_output.py b/src/calibre/ebooks/conversion/plugins/pdf_output.py index e835adbe21..84a7519800 100644 --- a/src/calibre/ebooks/conversion/plugins/pdf_output.py +++ b/src/calibre/ebooks/conversion/plugins/pdf_output.py @@ -261,4 +261,4 @@ class PDFOutput(OutputFormatPlugin): oeb_output = plugin_for_output_format('oeb') oeb_output.convert(oeb_book, oeb_dir, self.input_plugin, self.opts, self.log) opfpath = glob.glob(os.path.join(oeb_dir, '*.opf'))[0] - convert(opfpath, self.log, self.opts) + convert(opfpath, self.opts, self.output_path, self.log) diff --git a/src/calibre/ebooks/oeb/polish/split.py b/src/calibre/ebooks/oeb/polish/split.py index 0fbb7065e7..1ccf5d288b 100644 --- a/src/calibre/ebooks/oeb/polish/split.py +++ b/src/calibre/ebooks/oeb/polish/split.py @@ -357,7 +357,7 @@ def remove_name_attributes(root): elem.set('id', elem.attrib.pop('name')) -def merge_html(container, names, master): +def merge_html(container, names, master, insert_page_breaks=False): p = container.parsed root = p(master) @@ -428,6 +428,9 @@ def merge_html(container, names, master): if q in amap: a.set('href', '#' + amap[q]) + if insert_page_breaks: + master_body.append(master_body.makeelement(XHTML('div'), style='page-break-after:always')) + for child in children: if isinstance(child, string_or_bytes): add_text(master_body, child) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index e89b37d43b..3e2a55554e 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -6,11 +6,20 @@ from __future__ import absolute_import, division, print_function, unicode_litera import os +from PyQt5.Qt import QApplication, QUrl, QTimer from PyQt5.QtWebEngineWidgets import QWebEnginePage +from calibre.constants import iswindows from calibre.ebooks.oeb.polish.container import Container as ContainerBase from calibre.ebooks.oeb.polish.split import merge_html +from calibre.gui2 import setup_unix_signals from calibre.gui2.webengine import secure_webengine +from calibre.ebooks.pdf.image_writer import get_page_layout +from calibre.utils.logging import default_log + +from polyglot.builtins import range + +OK, LOAD_FAILED, KILL_SIGNAL = range(0, 3) class Container(ContainerBase): @@ -27,15 +36,18 @@ class Renderer(QWebEnginePage): def __init__(self, opts): QWebEnginePage.__init__(self) secure_webengine(self) + self.settle_time = 0 s = self.settings() s.setAttribute(s.JavascriptEnabled, True) s.setFontSize(s.DefaultFontSize, opts.pdf_default_font_size) s.setFontSize(s.DefaultFixedFontSize, opts.pdf_mono_font_size) s.setFontSize(s.MinimumLogicalFontSize, 8) s.setFontSize(s.MinimumFontSize, 8) - std = {'serif':opts.pdf_serif_family, 'sans':opts.pdf_sans_family, - 'mono':opts.pdf_mono_family}.get(opts.pdf_standard_font, - opts.pdf_serif_family) + std = { + 'serif': opts.pdf_serif_family, + 'sans' : opts.pdf_sans_family, + 'mono' : opts.pdf_mono_family + }.get(opts.pdf_standard_font, opts.pdf_serif_family) if std: s.setFontFamily(s.StandardFont, std) if opts.pdf_serif_family: @@ -45,14 +57,60 @@ class Renderer(QWebEnginePage): if opts.pdf_mono_family: s.setFontFamily(s.FixedFont, opts.pdf_mono_family) + self.loadFinished.connect(self.load_finished) + if not iswindows: + setup_unix_signals(self) -def convert(opf_path, log, opts): + def load_finished(self, ok): + if not ok: + QApplication.instance().exit(LOAD_FAILED) + return + QTimer.singleShot(int(1000 * self.settle_time), self.print_to_pdf) + + def signal_received(self, read_fd): + try: + os.read(read_fd, 1024) + except EnvironmentError: + return + QApplication.instance().exit(KILL_SIGNAL) + + def print_to_pdf(self): + self.printToPdf(self.printing_done, self.page_layout) + + def printing_done(self, pdf_data): + self.pdf_data = pdf_data + QApplication.instance().exit(OK) + + def convert_html_file(self, path, page_layout, settle_time=0): + self.settle_time = settle_time + self.page_layout = page_layout + self.pdf_data = None + self.setUrl(QUrl.fromLocalFile(path)) + ret = QApplication.exec_() + if ret == LOAD_FAILED: + raise SystemExit('Failed to load {}'.format(path)) + if ret == KILL_SIGNAL: + raise SystemExit('Kill signal received') + if ret != OK: + raise SystemExit('Unknown error occurred') + return self.pdf_data + + +def convert(opf_path, opts, output_path=None, log=default_log): container = Container(opf_path, log) - spine_names = [name for name in container.spine_names] + spine_names = [name for name, is_linear in container.spine_names] master = spine_names[0] if len(spine_names) > 1: - merge_html(container, spine_names, master) + merge_html(container, spine_names, master, insert_page_breaks=True) container.commit() index_file = container.name_to_abspath(master) - index_file + + renderer = Renderer(opts) + page_layout = get_page_layout(opts) + pdf_data = renderer.convert_html_file(index_file, page_layout, settle_time=1) + + if output_path is None: + return pdf_data + with open(output_path, 'wb') as f: + f.write(pdf_data) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index d79d789ac6..f62b989200 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -813,6 +813,26 @@ def setup_hidpi(): prints('Not controlling automatic hidpi scaling') +def setup_unix_signals(self): + if hasattr(os, 'pipe2'): + read_fd, write_fd = os.pipe2(os.O_CLOEXEC | os.O_NONBLOCK) + else: + import fcntl + read_fd, write_fd = os.pipe() + cloexec_flag = getattr(fcntl, 'FD_CLOEXEC', 1) + for fd in (read_fd, write_fd): + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, flags | cloexec_flag | os.O_NONBLOCK) + + for sig in (signal.SIGINT, signal.SIGTERM): + signal.signal(sig, lambda x, y: None) + signal.siginterrupt(sig, False) + signal.set_wakeup_fd(write_fd) + self.signal_notifier = QSocketNotifier(read_fd, QSocketNotifier.Read, self) + self.signal_notifier.setEnabled(True) + self.signal_notifier.activated.connect(self.signal_received, type=Qt.QueuedConnection) + + class Application(QApplication): shutdown_signal_received = pyqtSignal() @@ -1066,23 +1086,7 @@ class Application(QApplication): self.setQuitOnLastWindowClosed(True) def setup_unix_signals(self): - import fcntl - if hasattr(os, 'pipe2'): - read_fd, write_fd = os.pipe2(os.O_CLOEXEC | os.O_NONBLOCK) - else: - read_fd, write_fd = os.pipe() - cloexec_flag = getattr(fcntl, 'FD_CLOEXEC', 1) - for fd in (read_fd, write_fd): - flags = fcntl.fcntl(fd, fcntl.F_GETFD) - fcntl.fcntl(fd, fcntl.F_SETFD, flags | cloexec_flag | os.O_NONBLOCK) - - for sig in (signal.SIGINT, signal.SIGTERM): - signal.signal(sig, lambda x, y: None) - signal.siginterrupt(sig, False) - signal.set_wakeup_fd(write_fd) - self.signal_notifier = QSocketNotifier(read_fd, QSocketNotifier.Read, self) - self.signal_notifier.setEnabled(True) - self.signal_notifier.activated.connect(self.signal_received, type=Qt.QueuedConnection) + setup_unix_signals(self) def signal_received(self, read_fd): try: