mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Fix a regression in 6.0 that broke rendering of first page of EPUB as cover when the EPUB has no actual cover
This commit is contained in:
parent
a41c3b775c
commit
ba8f4fb7ae
@ -89,7 +89,7 @@ def extract_calibre_cover(raw, base, log):
|
||||
return return_raster_image(img)
|
||||
|
||||
|
||||
def render_html_svg_workaround(path_to_html, log, width=590, height=750):
|
||||
def render_html_svg_workaround(path_to_html, log, width=590, height=750, root=''):
|
||||
from calibre.ebooks.oeb.base import SVG_NS
|
||||
with open(path_to_html, 'rb') as f:
|
||||
raw = f.read()
|
||||
@ -108,11 +108,11 @@ def render_html_svg_workaround(path_to_html, log, width=590, height=750):
|
||||
pass
|
||||
|
||||
if data is None:
|
||||
data = render_html_data(path_to_html, width, height)
|
||||
data = render_html_data(path_to_html, width, height, root=root)
|
||||
return data
|
||||
|
||||
|
||||
def render_html_data(path_to_html, width, height):
|
||||
def render_html_data(path_to_html, width, height, root=''):
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.utils.ipc.simple_worker import fork_job, WorkerError
|
||||
result = {}
|
||||
@ -127,7 +127,7 @@ def render_html_data(path_to_html, width, height):
|
||||
|
||||
with TemporaryDirectory('-render-html') as tdir:
|
||||
try:
|
||||
result = fork_job('calibre.ebooks.render_html', 'main', args=(path_to_html, tdir, 'jpeg'))
|
||||
result = fork_job('calibre.ebooks.render_html', 'main', args=(path_to_html, tdir, 'jpeg', root))
|
||||
except WorkerError as e:
|
||||
report_error(e.orig_tb)
|
||||
else:
|
||||
|
@ -218,7 +218,7 @@ class EPUBInput(InputFormatPlugin):
|
||||
elem[0].getparent(), OPF('item'), href=guide_elem.get('href'), id='calibre_raster_cover')
|
||||
t.set('media-type', 'image/jpeg')
|
||||
if os.path.exists(guide_cover):
|
||||
renderer = render_html_svg_workaround(guide_cover, log)
|
||||
renderer = render_html_svg_workaround(guide_cover, log, root=os.getcwd())
|
||||
if renderer is not None:
|
||||
with lopen('calibre_raster_cover.jpg', 'wb') as f:
|
||||
f.write(renderer)
|
||||
|
@ -146,16 +146,11 @@ class PDFOutput(OutputFormatPlugin):
|
||||
# Ensure Qt is setup to be used with WebEngine
|
||||
# specialize_options is called early enough in the pipeline
|
||||
# that hopefully no Qt application has been constructed as yet
|
||||
from qt.webengine import QWebEngineUrlScheme
|
||||
from qt.webengine import QWebEnginePage # noqa
|
||||
from calibre.gui2 import must_use_qt
|
||||
from calibre.constants import FAKE_PROTOCOL
|
||||
scheme = QWebEngineUrlScheme(FAKE_PROTOCOL.encode('ascii'))
|
||||
scheme.setSyntax(QWebEngineUrlScheme.Syntax.Host)
|
||||
scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme)
|
||||
QWebEngineUrlScheme.registerScheme(scheme)
|
||||
from calibre.utils.webengine import setup_fake_protocol, setup_default_profile
|
||||
setup_fake_protocol()
|
||||
must_use_qt()
|
||||
from calibre.utils.webengine import setup_default_profile
|
||||
setup_default_profile()
|
||||
self.input_fmt = input_fmt
|
||||
|
||||
|
@ -193,7 +193,7 @@ def render_cover(cpage, zf, reader=None):
|
||||
cpage = os.path.join(tdir, cpage)
|
||||
if not os.path.exists(cpage):
|
||||
return
|
||||
return render_html_svg_workaround(cpage, default_log)
|
||||
return render_html_svg_workaround(cpage, default_log, root=tdir)
|
||||
|
||||
|
||||
def get_cover(raster_cover, first_spine_item, reader):
|
||||
|
@ -625,7 +625,7 @@ class OEBReader:
|
||||
writer = OEBWriter()
|
||||
writer(self.oeb, tdir)
|
||||
path = os.path.join(tdir, unquote(hcover.href))
|
||||
data = render_html_svg_workaround(path, self.logger)
|
||||
data = render_html_svg_workaround(path, self.logger, root=tdir)
|
||||
if not data:
|
||||
data = b''
|
||||
id, href = self.oeb.manifest.generate('cover', 'cover.jpg')
|
||||
|
@ -4,26 +4,88 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from qt.core import (
|
||||
QApplication, QMarginsF, QPageLayout, QPageSize, Qt, QTimer, QUrl
|
||||
QApplication, QByteArray, QMarginsF, QPageLayout, QPageSize, Qt, QTimer, QUrl
|
||||
)
|
||||
from qt.webengine import (
|
||||
QWebEnginePage, QWebEngineProfile, QWebEngineScript,
|
||||
QWebEngineUrlRequestInterceptor, QWebEngineUrlRequestJob,
|
||||
QWebEngineUrlSchemeHandler
|
||||
)
|
||||
from qt.webengine import QWebEnginePage, QWebEngineScript
|
||||
|
||||
from calibre.constants import FAKE_HOST, FAKE_PROTOCOL
|
||||
from calibre.ebooks.metadata.pdf import page_images
|
||||
from calibre.ebooks.oeb.polish.utils import guess_type
|
||||
from calibre.gui2 import must_use_qt
|
||||
from calibre.utils.webengine import secure_webengine
|
||||
from calibre.gui_launch import setup_qt_logging
|
||||
from calibre.utils.filenames import atomic_rename
|
||||
from calibre.utils.logging import default_log
|
||||
from calibre.utils.monotonic import monotonic
|
||||
from calibre.utils.webengine import (
|
||||
secure_webengine, send_reply, setup_fake_protocol, setup_profile
|
||||
)
|
||||
|
||||
LOAD_TIMEOUT = 20
|
||||
PRINT_TIMEOUT = 10
|
||||
|
||||
|
||||
class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
||||
|
||||
def interceptRequest(self, request_info):
|
||||
method = bytes(request_info.requestMethod())
|
||||
if method not in (b'GET', b'HEAD'):
|
||||
default_log.warn(f'Blocking URL request with method: {method}')
|
||||
request_info.block(True)
|
||||
return
|
||||
qurl = request_info.requestUrl()
|
||||
if qurl.scheme() not in (FAKE_PROTOCOL,):
|
||||
default_log.warn(f'Blocking URL request {qurl.toString()} as it is not for a resource related to the HTML file being rendered')
|
||||
request_info.block(True)
|
||||
return
|
||||
|
||||
|
||||
class UrlSchemeHandler(QWebEngineUrlSchemeHandler):
|
||||
|
||||
def __init__(self, root, parent=None):
|
||||
self.root = root
|
||||
super().__init__(parent)
|
||||
self.allowed_hosts = (FAKE_HOST,)
|
||||
|
||||
def requestStarted(self, rq):
|
||||
if bytes(rq.requestMethod()) != b'GET':
|
||||
return self.fail_request(rq, QWebEngineUrlRequestJob.Error.RequestDenied)
|
||||
url = rq.requestUrl()
|
||||
host = url.host()
|
||||
if host not in self.allowed_hosts or url.scheme() != FAKE_PROTOCOL:
|
||||
return self.fail_request(rq)
|
||||
path = url.path()
|
||||
rp = path[1:]
|
||||
if not rp:
|
||||
return self.fail_request(rq, QWebEngineUrlRequestJob.Error.UrlNotFound)
|
||||
resolved_path = os.path.abspath(os.path.join(self.root, rp.replace('/', os.sep)))
|
||||
if not resolved_path.startswith(self.root):
|
||||
return self.fail_request(rq, QWebEngineUrlRequestJob.Error.UrlNotFound)
|
||||
|
||||
try:
|
||||
with open(resolved_path, 'rb') as f:
|
||||
data = f.read()
|
||||
except OSError as err:
|
||||
default_log(f'Failed to read file: {rp} with error: {err}')
|
||||
return self.fail_request(rq, QWebEngineUrlRequestJob.Error.RequestFailed)
|
||||
|
||||
send_reply(rq, guess_type(os.path.basename(resolved_path)), data)
|
||||
|
||||
def fail_request(self, rq, fail_code=None):
|
||||
if fail_code is None:
|
||||
fail_code = QWebEngineUrlRequestJob.Error.UrlNotFound
|
||||
rq.fail(fail_code)
|
||||
print(f"Blocking FAKE_PROTOCOL request: {rq.requestUrl().toString()} with code: {fail_code}", file=sys.stderr)
|
||||
|
||||
|
||||
class Render(QWebEnginePage):
|
||||
|
||||
def __init__(self):
|
||||
QWebEnginePage.__init__(self)
|
||||
def __init__(self, profile):
|
||||
QWebEnginePage.__init__(self, profile, QApplication.instance())
|
||||
secure_webengine(self)
|
||||
self.printing_started = False
|
||||
self.loadFinished.connect(self.load_finished, type=Qt.ConnectionType.QueuedConnection)
|
||||
@ -32,6 +94,11 @@ class Render(QWebEnginePage):
|
||||
t.setInterval(500)
|
||||
t.timeout.connect(self.hang_check)
|
||||
|
||||
def break_cycles(self):
|
||||
self.hang_timer.timeout.disconnect()
|
||||
self.pdfPrintingFinished.disconnect()
|
||||
self.setParent(None)
|
||||
|
||||
def load_finished(self, ok):
|
||||
if ok:
|
||||
self.runJavaScript('''
|
||||
@ -52,8 +119,10 @@ class Render(QWebEnginePage):
|
||||
def javaScriptConsoleMessage(self, level, msg, linenumber, source_id):
|
||||
pass
|
||||
|
||||
def start_load(self, path_to_html):
|
||||
self.load(QUrl.fromLocalFile(path_to_html))
|
||||
def start_load(self, path_to_html, root):
|
||||
url = QUrl(f'{FAKE_PROTOCOL}://{FAKE_HOST}')
|
||||
url.setPath('/' + os.path.relpath(path_to_html, root).replace(os.sep, '/'))
|
||||
self.setUrl(url)
|
||||
self.start_time = monotonic()
|
||||
self.hang_timer.start()
|
||||
|
||||
@ -79,7 +148,9 @@ class Render(QWebEnginePage):
|
||||
if type(getattr(QPageSize, sz, None)) is type(QPageSize.PageSizeId.A4): # noqa
|
||||
page_size = QPageSize(getattr(QPageSize, sz))
|
||||
else:
|
||||
from calibre.ebooks.pdf.image_writer import parse_pdf_page_size
|
||||
from calibre.ebooks.pdf.image_writer import (
|
||||
parse_pdf_page_size
|
||||
)
|
||||
ps = parse_pdf_page_size(sz, data.get('unit', 'inch'))
|
||||
if ps is not None:
|
||||
page_size = ps
|
||||
@ -95,17 +166,25 @@ class Render(QWebEnginePage):
|
||||
self.hang_timer.stop()
|
||||
|
||||
|
||||
def main(path_to_html, tdir, image_format='jpeg'):
|
||||
def main(path_to_html, tdir, image_format='jpeg', root=''):
|
||||
if image_format not in ('jpeg', 'png'):
|
||||
raise ValueError('Image format must be either jpeg or png')
|
||||
must_use_qt()
|
||||
from calibre.utils.webengine import setup_default_profile
|
||||
setup_default_profile()
|
||||
setup_qt_logging()
|
||||
setup_fake_protocol()
|
||||
profile = setup_profile(QWebEngineProfile(QApplication.instance()))
|
||||
path_to_html = os.path.abspath(path_to_html)
|
||||
url_handler = UrlSchemeHandler(root or os.path.dirname(path_to_html), parent=profile)
|
||||
interceptor = RequestInterceptor(profile)
|
||||
profile.installUrlSchemeHandler(QByteArray(FAKE_PROTOCOL.encode('ascii')), url_handler)
|
||||
profile.setUrlRequestInterceptor(interceptor)
|
||||
|
||||
os.chdir(tdir)
|
||||
renderer = Render()
|
||||
renderer.start_load(path_to_html)
|
||||
renderer = Render(profile)
|
||||
renderer.start_load(path_to_html, url_handler.root)
|
||||
ret = QApplication.instance().exec()
|
||||
renderer.break_cycles()
|
||||
del renderer
|
||||
if ret == 0:
|
||||
page_images('rendered.pdf', image_format=image_format)
|
||||
ext = {'jpeg': 'jpg'}.get(image_format, image_format)
|
||||
|
@ -1188,8 +1188,9 @@ def main(shm_name=None):
|
||||
|
||||
override = 'calibre-gui' if islinux else None
|
||||
app = Application([], override_program_name=override)
|
||||
from calibre.utils.webengine import setup_default_profile
|
||||
from calibre.utils.webengine import setup_default_profile, setup_fake_protocol
|
||||
setup_default_profile()
|
||||
setup_fake_protocol()
|
||||
d = TOCEditor(path, title=title, write_result_to=path + '.result')
|
||||
d.start()
|
||||
ok = 0
|
||||
|
@ -8,7 +8,7 @@ import time
|
||||
|
||||
from qt.core import QIcon
|
||||
|
||||
from calibre.constants import EDITOR_APP_UID, FAKE_PROTOCOL, islinux
|
||||
from calibre.constants import EDITOR_APP_UID, islinux
|
||||
from calibre.ebooks.oeb.polish.check.css import shutdown as shutdown_css_check_pool
|
||||
from calibre.gui2 import (
|
||||
Application, decouple, set_gui_prefs, setup_gui_option_parser
|
||||
@ -50,14 +50,11 @@ def gui_main(path=None, notify=None):
|
||||
|
||||
|
||||
def _run(args, notify=None):
|
||||
from qt.webengine import QWebEngineUrlScheme
|
||||
from calibre.utils.webengine import setup_fake_protocol
|
||||
# Ensure we can continue to function if GUI is closed
|
||||
os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None)
|
||||
reset_base_dir()
|
||||
scheme = QWebEngineUrlScheme(FAKE_PROTOCOL.encode('ascii'))
|
||||
scheme.setSyntax(QWebEngineUrlScheme.Syntax.Host)
|
||||
scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme)
|
||||
QWebEngineUrlScheme.registerScheme(scheme)
|
||||
setup_fake_protocol()
|
||||
|
||||
# The following two lines are needed to prevent circular imports causing
|
||||
# errors during initialization of plugins that use the polish container
|
||||
|
@ -8,7 +8,7 @@ import sys
|
||||
from contextlib import closing
|
||||
from qt.core import QIcon, QObject, Qt, QTimer, pyqtSignal
|
||||
|
||||
from calibre.constants import FAKE_PROTOCOL, VIEWER_APP_UID, islinux
|
||||
from calibre.constants import VIEWER_APP_UID, islinux
|
||||
from calibre.gui2 import Application, error_dialog, setup_gui_option_parser
|
||||
from calibre.gui2.listener import send_message_in_process
|
||||
from calibre.gui2.viewer.config import get_session_pref, vprefs
|
||||
@ -167,14 +167,11 @@ def run_gui(app, opts, args, internal_book_data, listener=None):
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
from qt.webengine import QWebEngineUrlScheme
|
||||
from calibre.utils.webengine import setup_fake_protocol
|
||||
# Ensure viewer can continue to function if GUI is closed
|
||||
os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None)
|
||||
reset_base_dir()
|
||||
scheme = QWebEngineUrlScheme(FAKE_PROTOCOL.encode('ascii'))
|
||||
scheme.setSyntax(QWebEngineUrlScheme.Syntax.Host)
|
||||
scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme)
|
||||
QWebEngineUrlScheme.registerScheme(scheme)
|
||||
setup_fake_protocol()
|
||||
override = 'calibre-ebook-viewer' if islinux else None
|
||||
processed_args = []
|
||||
internal_book_data = internal_book_data_path = None
|
||||
|
@ -16,11 +16,11 @@ from calibre.utils.ipc.simple_worker import start_pipe_worker
|
||||
|
||||
|
||||
def worker_main(source):
|
||||
from qt.core import QLoggingCategory, QUrl
|
||||
QLoggingCategory.setFilterRules('''\
|
||||
qt.webenginecontext.info=false
|
||||
''')
|
||||
from qt.core import QUrl
|
||||
|
||||
from calibre.gui2 import must_use_qt
|
||||
from calibre.gui_launch import setup_qt_logging
|
||||
setup_qt_logging()
|
||||
|
||||
from .simple_backend import SimpleScraper
|
||||
must_use_qt()
|
||||
|
@ -59,7 +59,9 @@ def compiler():
|
||||
|
||||
from calibre import walk
|
||||
from calibre.gui2 import must_use_qt
|
||||
from calibre.utils.webengine import secure_webengine, setup_default_profile, setup_profile
|
||||
from calibre.utils.webengine import (
|
||||
secure_webengine, setup_default_profile, setup_profile
|
||||
)
|
||||
must_use_qt()
|
||||
setup_default_profile()
|
||||
|
||||
@ -335,7 +337,7 @@ def run_rapydscript_tests():
|
||||
from qt.core import QApplication, QByteArray, QEventLoop, QUrl
|
||||
from qt.webengine import (
|
||||
QWebEnginePage, QWebEngineProfile, QWebEngineScript, QWebEngineUrlRequestJob,
|
||||
QWebEngineUrlScheme, QWebEngineUrlSchemeHandler
|
||||
QWebEngineUrlSchemeHandler
|
||||
)
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
@ -343,14 +345,12 @@ def run_rapydscript_tests():
|
||||
from calibre.gui2 import must_use_qt
|
||||
from calibre.gui2.viewer.web_view import send_reply
|
||||
from calibre.utils.webengine import (
|
||||
create_script, insert_scripts, secure_webengine, setup_default_profile, setup_profile
|
||||
create_script, insert_scripts, secure_webengine, setup_default_profile,
|
||||
setup_fake_protocol, setup_profile
|
||||
)
|
||||
must_use_qt()
|
||||
setup_default_profile()
|
||||
scheme = QWebEngineUrlScheme(FAKE_PROTOCOL.encode('ascii'))
|
||||
scheme.setSyntax(QWebEngineUrlScheme.Syntax.Host)
|
||||
scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme)
|
||||
QWebEngineUrlScheme.registerScheme(scheme)
|
||||
setup_fake_protocol()
|
||||
|
||||
base = base_dir()
|
||||
rapydscript_dir = os.path.join(base, 'src', 'pyj')
|
||||
|
@ -3,11 +3,25 @@
|
||||
# License: GPL v3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
import json, os
|
||||
import json
|
||||
import os
|
||||
from qt.core import QBuffer, QIODevice, QObject, pyqtSignal, sip
|
||||
from qt.webengine import QWebEngineScript, QWebEngineSettings, QWebEngineProfile
|
||||
from qt.webengine import (
|
||||
QWebEngineProfile, QWebEngineScript, QWebEngineSettings, QWebEngineUrlScheme
|
||||
)
|
||||
|
||||
from calibre.constants import cache_dir, SPECIAL_TITLE_FOR_WEBENGINE_COMMS
|
||||
from calibre.constants import (
|
||||
FAKE_PROTOCOL, SPECIAL_TITLE_FOR_WEBENGINE_COMMS, cache_dir
|
||||
)
|
||||
|
||||
|
||||
def setup_fake_protocol():
|
||||
p = FAKE_PROTOCOL.encode('ascii')
|
||||
if not QWebEngineUrlScheme.schemeByName(p).name():
|
||||
scheme = QWebEngineUrlScheme(p)
|
||||
scheme.setSyntax(QWebEngineUrlScheme.Syntax.Host)
|
||||
scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme)
|
||||
QWebEngineUrlScheme.registerScheme(scheme)
|
||||
|
||||
|
||||
def setup_profile(profile):
|
||||
|
Loading…
x
Reference in New Issue
Block a user