Basic PDF output works

This commit is contained in:
Kovid Goyal 2019-07-08 16:47:25 +05:30
parent 4abfcc46ab
commit b25766f6aa
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 91 additions and 26 deletions

View File

@ -261,4 +261,4 @@ class PDFOutput(OutputFormatPlugin):
oeb_output = plugin_for_output_format('oeb') oeb_output = plugin_for_output_format('oeb')
oeb_output.convert(oeb_book, oeb_dir, self.input_plugin, self.opts, self.log) oeb_output.convert(oeb_book, oeb_dir, self.input_plugin, self.opts, self.log)
opfpath = glob.glob(os.path.join(oeb_dir, '*.opf'))[0] 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)

View File

@ -357,7 +357,7 @@ def remove_name_attributes(root):
elem.set('id', elem.attrib.pop('name')) 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 p = container.parsed
root = p(master) root = p(master)
@ -428,6 +428,9 @@ def merge_html(container, names, master):
if q in amap: if q in amap:
a.set('href', '#' + amap[q]) 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: for child in children:
if isinstance(child, string_or_bytes): if isinstance(child, string_or_bytes):
add_text(master_body, child) add_text(master_body, child)

View File

@ -6,11 +6,20 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import os import os
from PyQt5.Qt import QApplication, QUrl, QTimer
from PyQt5.QtWebEngineWidgets import QWebEnginePage 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.container import Container as ContainerBase
from calibre.ebooks.oeb.polish.split import merge_html 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.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): class Container(ContainerBase):
@ -27,15 +36,18 @@ class Renderer(QWebEnginePage):
def __init__(self, opts): def __init__(self, opts):
QWebEnginePage.__init__(self) QWebEnginePage.__init__(self)
secure_webengine(self) secure_webengine(self)
self.settle_time = 0
s = self.settings() s = self.settings()
s.setAttribute(s.JavascriptEnabled, True) s.setAttribute(s.JavascriptEnabled, True)
s.setFontSize(s.DefaultFontSize, opts.pdf_default_font_size) s.setFontSize(s.DefaultFontSize, opts.pdf_default_font_size)
s.setFontSize(s.DefaultFixedFontSize, opts.pdf_mono_font_size) s.setFontSize(s.DefaultFixedFontSize, opts.pdf_mono_font_size)
s.setFontSize(s.MinimumLogicalFontSize, 8) s.setFontSize(s.MinimumLogicalFontSize, 8)
s.setFontSize(s.MinimumFontSize, 8) s.setFontSize(s.MinimumFontSize, 8)
std = {'serif':opts.pdf_serif_family, 'sans':opts.pdf_sans_family, std = {
'mono':opts.pdf_mono_family}.get(opts.pdf_standard_font, 'serif': opts.pdf_serif_family,
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: if std:
s.setFontFamily(s.StandardFont, std) s.setFontFamily(s.StandardFont, std)
if opts.pdf_serif_family: if opts.pdf_serif_family:
@ -45,14 +57,60 @@ class Renderer(QWebEnginePage):
if opts.pdf_mono_family: if opts.pdf_mono_family:
s.setFontFamily(s.FixedFont, 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) 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] master = spine_names[0]
if len(spine_names) > 1: if len(spine_names) > 1:
merge_html(container, spine_names, master) merge_html(container, spine_names, master, insert_page_breaks=True)
container.commit() container.commit()
index_file = container.name_to_abspath(master) 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)

View File

@ -813,6 +813,26 @@ def setup_hidpi():
prints('Not controlling automatic hidpi scaling') 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): class Application(QApplication):
shutdown_signal_received = pyqtSignal() shutdown_signal_received = pyqtSignal()
@ -1066,23 +1086,7 @@ class Application(QApplication):
self.setQuitOnLastWindowClosed(True) self.setQuitOnLastWindowClosed(True)
def setup_unix_signals(self): def setup_unix_signals(self):
import fcntl setup_unix_signals(self)
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)
def signal_received(self, read_fd): def signal_received(self, read_fd):
try: try: