def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import must_use_qt from calibre.utils.podofo import get_podofo must_use_qt() QObject.__init__(self) self.logger = self.log = log self.podofo = get_podofo() self.doc = self.podofo.PDFDoc() self.loop = QEventLoop() self.view = QWebView() self.page = Page(opts, self.log) self.view.setPage(self.page) self.view.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform) self.view.loadFinished.connect(self._render_html, type=Qt.QueuedConnection) for x in (Qt.Horizontal, Qt.Vertical): self.view.page().mainFrame().setScrollBarPolicy(x, Qt.ScrollBarAlwaysOff) self.render_queue = [] self.combine_queue = [] self.tmp_path = PersistentTemporaryDirectory(u'_pdf_output_parts') self.opts = opts self.cover_data = cover_data self.paged_js = None self.toc = toc
def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import must_use_qt must_use_qt() QObject.__init__(self) self.logger = self.log = log self.opts = opts self.cover_data = cover_data self.paged_js = None self.toc = toc self.loop = QEventLoop() self.view = QWebView() self.page = Page(opts, self.log) self.view.setPage(self.page) self.view.setRenderHints(QPainter.Antialiasing| QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform) self.view.loadFinished.connect(self.render_html, type=Qt.QueuedConnection) for x in (Qt.Horizontal, Qt.Vertical): self.view.page().mainFrame().setScrollBarPolicy(x, Qt.ScrollBarAlwaysOff) self.report_progress = lambda x, y: x self.current_section = '' self.current_tl_section = ''
def convert(self, oeb_book, output_path, input_plugin, opts, log): from calibre.gui2 import must_use_qt, load_builtin_fonts # Turn off hinting in WebKit (requires a patched build of QtWebKit) os.environ['CALIBRE_WEBKIT_NO_HINTING'] = '1' try: must_use_qt() load_builtin_fonts() self.oeb = oeb_book self.input_plugin, self.opts, self.log = input_plugin, opts, log self.output_path = output_path from calibre.ebooks.oeb.base import OPF, OPF2_NS from lxml import etree from io import BytesIO package = etree.Element(OPF('package'), attrib={'version': '2.0', 'unique-identifier': 'dummy'}, nsmap={None: OPF2_NS}) from calibre.ebooks.metadata.opf2 import OPF self.oeb.metadata.to_opf2(package) self.metadata = OPF(BytesIO(etree.tostring(package))).to_book_metadata() self.cover_data = None if input_plugin.is_image_collection: log.debug('Converting input as an image collection...') self.convert_images(input_plugin.get_images()) else: log.debug('Converting input as a text based book...') self.convert_text(oeb_book) finally: os.environ.pop('CALIBRE_WEBKIT_NO_HINTING', None)
def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import must_use_qt from calibre.utils.podofo import get_podofo must_use_qt() QObject.__init__(self) self.logger = self.log = log self.podofo = get_podofo() self.doc = self.podofo.PDFDoc() self.loop = QEventLoop() self.view = QWebView() self.page = Page(opts, self.log) self.view.setPage(self.page) self.view.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform) self.view.loadFinished.connect(self._render_html, type=Qt.QueuedConnection) for x in (Qt.Horizontal, Qt.Vertical): self.view.page().mainFrame().setScrollBarPolicy( x, Qt.ScrollBarAlwaysOff) self.render_queue = [] self.combine_queue = [] self.tmp_path = PersistentTemporaryDirectory(u'_pdf_output_parts') self.opts = opts self.cover_data = cover_data self.paged_js = None self.toc = toc
def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import must_use_qt must_use_qt() QObject.__init__(self) self.logger = self.log = log self.mathjax_dir = P('mathjax', allow_user_override=False) current_log(log) self.opts = opts self.cover_data = cover_data self.paged_js = None self.toc = toc self.loop = QEventLoop() self.view = QWebView() self.page = Page(opts, self.log) self.view.setPage(self.page) self.view.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform) self.view.loadFinished.connect(self.render_html, type=Qt.QueuedConnection) self.view.loadProgress.connect(self.load_progress) self.ignore_failure = None self.hang_check_timer = t = QTimer(self) t.timeout.connect(self.hang_check) t.setInterval(1000) for x in (Qt.Horizontal, Qt.Vertical): self.view.page().mainFrame().setScrollBarPolicy( x, Qt.ScrollBarAlwaysOff) self.report_progress = lambda x, y: x self.current_section = '' self.current_tl_section = ''
def convert(self, oeb_book, output_path, input_plugin, opts, log): from calibre.gui2 import must_use_qt, load_builtin_fonts from calibre.ebooks.oeb.transforms.split import Split # Turn off hinting in WebKit (requires a patched build of QtWebKit) os.environ['CALIBRE_WEBKIT_NO_HINTING'] = '1' self.filtered_font_warnings = set() try: # split on page breaks, as the JS code to convert page breaks to # column breaks will not work because of QWebSettings.LocalContentCanAccessFileUrls Split()(oeb_book, opts) must_use_qt() load_builtin_fonts() self.oeb = oeb_book self.input_plugin, self.opts, self.log = input_plugin, opts, log self.output_path = output_path from calibre.ebooks.oeb.base import OPF, OPF2_NS from lxml import etree from io import BytesIO package = etree.Element(OPF('package'), attrib={'version': '2.0', 'unique-identifier': 'dummy'}, nsmap={None: OPF2_NS}) from calibre.ebooks.metadata.opf2 import OPF self.oeb.metadata.to_opf2(package) self.metadata = OPF(BytesIO(etree.tostring(package))).to_book_metadata() self.cover_data = None if input_plugin.is_image_collection: log.debug('Converting input as an image collection...') self.convert_images(input_plugin.get_images()) else: log.debug('Converting input as a text based book...') self.convert_text(oeb_book) finally: os.environ.pop('CALIBRE_WEBKIT_NO_HINTING', None)
def convert(self, oeb_book, output_path, input_plugin, opts, log): from calibre.gui2 import must_use_qt, load_builtin_fonts # Turn off hinting in WebKit (requires a patched build of QtWebKit) os.environ['CALIBRE_WEBKIT_NO_HINTING'] = '1' try: must_use_qt() load_builtin_fonts() self.oeb = oeb_book self.input_plugin, self.opts, self.log = input_plugin, opts, log self.output_path = output_path from calibre.ebooks.oeb.base import OPF, OPF2_NS from lxml import etree from io import BytesIO package = etree.Element(OPF('package'), attrib={ 'version': '2.0', 'unique-identifier': 'dummy' }, nsmap={None: OPF2_NS}) from calibre.ebooks.metadata.opf2 import OPF self.oeb.metadata.to_opf2(package) self.metadata = OPF(BytesIO( etree.tostring(package))).to_book_metadata() self.cover_data = None if input_plugin.is_image_collection: log.debug('Converting input as an image collection...') self.convert_images(input_plugin.get_images()) else: log.debug('Converting input as a text based book...') self.convert_text(oeb_book) finally: os.environ.pop('CALIBRE_WEBKIT_NO_HINTING', None)
def __init__(self, container, do_embed=False): self.container = container self.log = self.logger = container.log self.do_embed = do_embed must_use_qt() self.parser = CSSParser(loglevel=logging.CRITICAL, log=logging.getLogger('calibre.css')) self.first_letter_pat = regex.compile(r'^[\p{Ps}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]+', regex.VERSION1 | regex.UNICODE) self.capitalize_pat = regex.compile(r'[\p{L}\p{N}]', regex.VERSION1 | regex.UNICODE) self.loop = QEventLoop() self.view = QWebView() self.page = Page(self.log) self.view.setPage(self.page) self.page.setViewportSize(QSize(1200, 1600)) self.view.loadFinished.connect(self.collect, type=Qt.QueuedConnection) self.render_queue = list(container.spine_items) self.font_stats = {} self.font_usage_map = {} self.font_spec_map = {} self.font_rule_map = {} self.all_font_rules = {} QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('Failed to gather statistics from book, see log for details')
def convert(self, oeb_book, output_path, input_plugin, opts, log): from calibre.gui2 import must_use_qt, load_builtin_fonts must_use_qt() load_builtin_fonts() self.oeb = oeb_book self.input_plugin, self.opts, self.log = input_plugin, opts, log self.output_path = output_path from calibre.ebooks.oeb.base import OPF, OPF2_NS from lxml import etree from io import BytesIO package = etree.Element(OPF('package'), attrib={ 'version': '2.0', 'unique-identifier': 'dummy' }, nsmap={None: OPF2_NS}) from calibre.ebooks.metadata.opf2 import OPF self.oeb.metadata.to_opf2(package) self.metadata = OPF(BytesIO( etree.tostring(package))).to_book_metadata() self.cover_data = None if input_plugin.is_image_collection: log.debug('Converting input as an image collection...') self.convert_images(input_plugin.get_images()) else: log.debug('Converting input as a text based book...') self.convert_text(oeb_book)
def convert(self, oeb_book, output_path, input_plugin, opts, log): from calibre.gui2 import must_use_qt, load_builtin_fonts must_use_qt() load_builtin_fonts() self.oeb = oeb_book self.input_plugin, self.opts, self.log = input_plugin, opts, log self.output_path = output_path from calibre.ebooks.oeb.base import OPF, OPF2_NS from lxml import etree from io import BytesIO package = etree.Element( OPF("package"), attrib={"version": "2.0", "unique-identifier": "dummy"}, nsmap={None: OPF2_NS} ) from calibre.ebooks.metadata.opf2 import OPF self.oeb.metadata.to_opf2(package) self.metadata = OPF(BytesIO(etree.tostring(package))).to_book_metadata() self.cover_data = None if input_plugin.is_image_collection: log.debug("Converting input as an image collection...") self.convert_images(input_plugin.get_images()) else: log.debug("Converting input as a text based book...") self.convert_text(oeb_book)
def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import must_use_qt must_use_qt() QObject.__init__(self) self.logger = self.log = log self.mathjax_dir = P('mathjax', allow_user_override=False) current_log(log) self.opts = opts self.cover_data = cover_data self.paged_js = None self.toc = toc self.loop = QEventLoop() self.view = QWebView() self.page = Page(opts, self.log) self.view.setPage(self.page) self.view.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform) self.view.loadFinished.connect(self.render_html, type=Qt.QueuedConnection) self.view.loadProgress.connect(self.load_progress) self.ignore_failure = None self.hang_check_timer = t = QTimer(self) t.timeout.connect(self.hang_check) t.setInterval(1000) for x in (Qt.Horizontal, Qt.Vertical): self.view.page().mainFrame().setScrollBarPolicy(x, Qt.ScrollBarAlwaysOff) self.report_progress = lambda x, y: x self.current_section = '' self.current_tl_section = ''
def __init__(self, container, do_embed=False): self.container = container self.log = self.logger = container.log self.do_embed = do_embed must_use_qt() self.parser = CSSParser(loglevel=logging.CRITICAL, log=logging.getLogger('calibre.css')) self.first_letter_pat = regex.compile(r'^[\p{Ps}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]+', regex.VERSION1 | regex.UNICODE) self.loop = QEventLoop() self.view = QWebView() self.page = Page(self.log) self.view.setPage(self.page) self.page.setViewportSize(QSize(1200, 1600)) self.view.loadFinished.connect(self.collect, type=Qt.QueuedConnection) self.render_queue = list(container.spine_items) self.font_stats = {} self.font_usage_map = {} self.font_spec_map = {} self.font_rule_map = {} self.all_font_rules = {} QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('Failed to gather statistics from book, see log for details')
def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import must_use_qt must_use_qt() QObject.__init__(self) self.logger = self.log = log current_log(log) self.opts = opts self.cover_data = cover_data self.paged_js = None self.toc = toc self.loop = QEventLoop() self.view = QWebView() self.page = Page(opts, self.log) self.view.setPage(self.page) self.view.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform) self.view.loadFinished.connect(self.render_html, type=Qt.QueuedConnection) for x in (Qt.Horizontal, Qt.Vertical): self.view.page().mainFrame().setScrollBarPolicy( x, Qt.ScrollBarAlwaysOff) self.report_progress = lambda x, y: x self.current_section = '' self.current_tl_section = ''
def main(): must_use_qt() load_builtin_fonts() renderer = Renderer() renderer.setUrl(QUrl.fromLocalFile(sys.argv[-1])) renderer.loadFinished.connect(renderer.do_print) QApplication.instance().exec_() print('Output written to:', OUTPUT)
def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import must_use_qt must_use_qt() self.logger = self.log = log self.opts = opts self.cover_data = cover_data self.toc = toc
def verify_theme(report): must_use_qt() report.bad = bad = {} for name, path in iteritems(report.name_map): reader = QImageReader(os.path.join(report.path, path)) img = reader.read() if img.isNull(): bad[name] = reader.errorString() return bool(bad)
def verify_theme(report): must_use_qt() report.bad = bad = {} for name, path in report.name_map.iteritems(): reader = QImageReader(os.path.join(report.path, path)) img = reader.read() if img.isNull(): bad[name] = reader.errorString() return bool(bad)
def __init__(self): must_use_qt() QWebEnginePage.__init__(self, create_profile(), QApplication.instance()) self.titleChanged.connect(self.title_changed) secure_webengine(self.settings()) self.console_messages = [] self.ready = False self.working = False self.pending = None self.setHtml('')
def __init__( self, # Logging. If None, uses a default log, which does not output # debugging info log=None, # Receives a string and returns True/False. By default, returns # True for all strings confirm_callback=None, # Prompt callback. Receives a msg string and a default value # string. Should return the user input value or None if the user # canceled the prompt. By default returns None. prompt_callback=None, # User agent to be used user_agent=USER_AGENT, # The size (in MB) of the on disk cache. Note that because the disk # cache cannot be shared between different instances, we currently # use a temporary dir for the cache, which is deleted on # program exit. Set to zero to disable cache. disk_cache_size=50, # Enable Inspect element functionality enable_developer_tools=False, # Verbosity verbosity=0, # The default timeout (in seconds) default_timeout=30, # If True, do not connect to the X server on linux headless=True): must_use_qt(headless=headless) QObject.__init__(self) FormsMixin.__init__(self) if log is None: log = ThreadSafeLog() if verbosity: log.filter_level = log.DEBUG self.log = log self.default_timeout = default_timeout self.page = WebPage(log, confirm_callback=confirm_callback, prompt_callback=prompt_callback, user_agent=user_agent, enable_developer_tools=enable_developer_tools, parent=self) self.nam = NetworkAccessManager(log, disk_cache_size=disk_cache_size, parent=self) self.page.setNetworkAccessManager(self.nam)
def run_rapydscript_tests(): from PyQt5.Qt import QApplication, QEventLoop from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript from calibre.gui2 import must_use_qt from calibre.gui2.webengine import secure_webengine must_use_qt() base = base_dir() rapydscript_dir = os.path.join(base, 'src', 'pyj') fname = os.path.join(rapydscript_dir, 'test.pyj') with lopen(fname, 'rb') as f: js = compile_fast(f.read(), fname) def create_script(src, name): s = QWebEngineScript() s.setName(name) s.setInjectionPoint(QWebEngineScript.DocumentReady) s.setWorldId(QWebEngineScript.ApplicationWorld) s.setRunsOnSubFrames(False) s.setSourceCode(src) return s class Tester(QWebEnginePage): def __init__(self): QWebEnginePage.__init__(self) self.titleChanged.connect(self.title_changed) secure_webengine(self) self.scripts().insert(create_script(js, 'test-rapydscript.js')) self.setHtml('<p>initialize') self.working = True def title_changed(self, title): if title == 'initialized': self.titleChanged.disconnect() self.runJavaScript('window.main()', QWebEngineScript.ApplicationWorld, self.callback) def spin_loop(self): while self.working: QApplication.instance().processEvents( QEventLoop.ExcludeUserInputEvents) return self.result def callback(self, result): self.result = result self.working = False def javaScriptConsoleMessage(self, level, msg, line_num, source_id): print(msg, file=sys.stderr if level > 0 else sys.stdout) tester = Tester() result = tester.spin_loop() raise SystemExit(int(result))
def load_icon_for_file(path: str, as_data=False, size=ICON_SIZE): try: hicon = winutil.get_icon_for_file(path) except Exception: return must_use_qt() pmap = hicon_to_pixmap(hicon) if not pmap.isNull(): if pmap.width() != size: pmap = pmap.scaled(size, size, aspectRatioMode=Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation) return pixmap_to_data(pmap) if as_data else pmap
def __init__(self, # Logging. If None, uses a default log, which does not output # debugging info log=None, # Receives a string and returns True/False. By default, returns # True for all strings confirm_callback=None, # Prompt callback. Receives a msg string and a default value # string. Should return the user input value or None if the user # canceled the prompt. By default returns None. prompt_callback=None, # User agent to be used user_agent=USER_AGENT, # The size (in MB) of the on disk cache. Note that because the disk # cache cannot be shared between different instances, we currently # use a temporary dir for the cache, which is deleted on # program exit. Set to zero to disable cache. disk_cache_size=50, # Enable Inspect element functionality enable_developer_tools=False, # Verbosity verbosity=0, # The default timeout (in seconds) default_timeout=30, # If True, do not connect to the X server on linux headless=True ): must_use_qt(headless=headless) QObject.__init__(self) FormsMixin.__init__(self) if log is None: log = ThreadSafeLog() if verbosity: log.filter_level = log.DEBUG self.log = log self.default_timeout = default_timeout self.page = WebPage(log, confirm_callback=confirm_callback, prompt_callback=prompt_callback, user_agent=user_agent, enable_developer_tools=enable_developer_tools, parent=self) self.nam = NetworkAccessManager(log, disk_cache_size=disk_cache_size, parent=self) self.page.setNetworkAccessManager(self.nam)
def main(path_to_html, tdir, image_format='jpeg'): if image_format not in ('jpeg', 'png'): raise ValueError('Image format must be either jpeg or png') must_use_qt() path_to_html = os.path.abspath(path_to_html) os.chdir(tdir) renderer = Render() renderer.start_load(path_to_html) ret = QApplication.instance().exec_() if ret == 0: page_images('rendered.pdf', image_format=image_format) ext = {'jpeg': 'jpg'}.get(image_format, image_format) atomic_rename('page-images-1.' + ext, 'rendered.' + image_format) return ret == 0
def __init__( self, # Logging. If None, uses a default log, which does not output # debugging info log=None, # Receives a string and returns True/False. By default, returns # True for all strings confirm_callback=None, # Prompt callback. Receives a msg string and a default value # string. Should return the user input value or None if the user # canceled the prompt. By default returns None. prompt_callback=None, # User agent to be used user_agent=USER_AGENT, # If True a disk cache is used use_disk_cache=True, # Enable Inspect element functionality enable_developer_tools=False, # Verbosity verbosity=0, # The default timeout (in seconds) default_timeout=30): must_use_qt() QObject.__init__(self) FormsMixin.__init__(self) if log is None: log = ThreadSafeLog() if verbosity: log.filter_level = log.DEBUG self.log = log self.default_timeout = default_timeout self.page = WebPage(log, confirm_callback=confirm_callback, prompt_callback=prompt_callback, user_agent=user_agent, enable_developer_tools=enable_developer_tools, parent=self) self.nam = NetworkAccessManager(log, use_disk_cache=use_disk_cache, parent=self) self.page.setNetworkAccessManager(self.nam)
def read_icon(handle, icon): must_use_qt() resource = win32api.LoadResource(handle, win32con.RT_ICON, icon.id) pixmap = QPixmap() pixmap.loadFromData(resource) hicon = None if pixmap.isNull(): if icon.width > 0 and icon.height > 0: hicon = ctypes.windll.user32.CreateIconFromResourceEx( resource, len(resource), True, 0x00030000, icon.width, icon.height, win32con.LR_DEFAULTCOLOR) else: hicon = win32gui.CreateIconFromResource(resource, True) pixmap = hicon_to_pixmap(hicon).copy() win32gui.DestroyIcon(hicon) return pixmap
def get_pdf_printer(opts, for_comic=False, output_file_name=None): # {{{ from calibre.gui2 import must_use_qt must_use_qt() printer = QPrinter(QPrinter.HighResolution) custom_size = get_custom_size(opts) if isosx and not for_comic: # On OSX, the native engine can only produce a single page size # (usually A4). The Qt engine on the other hand produces image based # PDFs. If we set a custom page size using QSizeF the native engine # produces unreadable output, so we just ignore the custom size # settings. printer.setPaperSize(paper_size(opts.paper_size)) else: if opts.output_profile.short_name == 'default' or \ opts.output_profile.width > 9999 or opts.override_profile_size: if custom_size is None: printer.setPaperSize(paper_size(opts.paper_size)) else: printer.setPaperSize(QSizeF(custom_size[0], custom_size[1]), unit(opts.unit)) else: w = opts.output_profile.comic_screen_size[0] if for_comic else \ opts.output_profile.width h = opts.output_profile.comic_screen_size[1] if for_comic else \ opts.output_profile.height dpi = opts.output_profile.dpi printer.setPaperSize(QSizeF(float(w) / dpi, float(h) / dpi), QPrinter.Inch) if for_comic: # Comic pages typically have their own margins, or their background # color is not white, in which case the margin looks bad printer.setPageMargins(0, 0, 0, 0, QPrinter.Point) else: printer.setPageMargins(opts.margin_left, opts.margin_top, opts.margin_right, opts.margin_bottom, QPrinter.Point) printer.setOutputFormat(QPrinter.PdfFormat) printer.setFullPage(for_comic) if output_file_name: printer.setOutputFileName(output_file_name) if isosx and not for_comic: # Ensure we are not generating enormous image based PDFs printer.setOutputFormat(QPrinter.NativeFormat) return printer
def convert(self, oeb_book, output_path, input_plugin, opts, log): from calibre.gui2 import must_use_qt, load_builtin_fonts must_use_qt() load_builtin_fonts() self.oeb = oeb_book self.input_plugin, self.opts, self.log = input_plugin, opts, log self.output_path = output_path self.metadata = oeb_book.metadata self.cover_data = None if input_plugin.is_image_collection: log.debug('Converting input as an image collection...') self.convert_images(input_plugin.get_images()) else: log.debug('Converting input as a text based book...') self.convert_text(oeb_book)
def load_icon_resource_as_pixmap(icon_resource, size=ICON_SIZE): if not icon_resource: return parts = tuple(filter(None, re.split(r',([-0-9]+$)', icon_resource))) if len(parts) != 2: return module, index = parts index = int(index) if module.startswith('"') and module.endswith('"'): module = split_commandline(module)[0] hmodule = winutil.load_library( module, winutil.LOAD_LIBRARY_AS_DATAFILE | winutil.LOAD_LIBRARY_AS_IMAGE_RESOURCE) icons = winutil.load_icons(hmodule, index) pixmaps = [] must_use_qt() for icon_data, icon_handle in icons: pixmap = QPixmap() pixmap.loadFromData(icon_data) if pixmap.isNull() and bool(icon_handle): pixmap = hicon_to_pixmap(icon_handle) if pixmap.isNull(): continue pixmaps.append(pixmap) if not pixmaps: return def area(p): return p.width() * p.height() pixmaps.sort(key=area) q = size * size for pmap in pixmaps: if area(pmap) >= q: if area(pmap) == q: return pmap return pmap.scaled( size, size, aspectRatioMode=Qt.AspectRatioMode.KeepAspectRatio, transformMode=Qt.TransformationMode.SmoothTransformation) return pixmaps[-1].scaled( size, size, aspectRatioMode=Qt.AspectRatioMode.KeepAspectRatio, transformMode=Qt.TransformationMode.SmoothTransformation)
def __init__(self, # Logging. If None, uses a default log, which does not output # debugging info log=None, # Receives a string and returns True/False. By default, returns # True for all strings confirm_callback=None, # Prompt callback. Receives a msg string and a default value # string. Should return the user input value or None if the user # canceled the prompt. By default returns None. prompt_callback=None, # User agent to be used user_agent=USER_AGENT, # If True a disk cache is used use_disk_cache=True, # Enable Inspect element functionality enable_developer_tools=False, # Verbosity verbosity=0, # The default timeout (in seconds) default_timeout=30 ): must_use_qt() QObject.__init__(self) FormsMixin.__init__(self) if log is None: log = ThreadSafeLog() if verbosity: log.filter_level = log.DEBUG self.log = log self.default_timeout = default_timeout self.page = WebPage(log, confirm_callback=confirm_callback, prompt_callback=prompt_callback, user_agent=user_agent, enable_developer_tools=enable_developer_tools, parent=self) self.nam = NetworkAccessManager(log, use_disk_cache=use_disk_cache, parent=self) self.page.setNetworkAccessManager(self.nam)
def specialize_options(self, log, opts, input_fmt): # 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) must_use_qt() self.input_fmt = input_fmt if opts.pdf_use_document_margins: # Prevent the conversion pipeline from overwriting document margins opts.margin_left = opts.margin_right = opts.margin_top = opts.margin_bottom = -1
def simple_load_icon(module, index, as_data=False, size=ICON_SIZE): ' Use the win32 API ExtractIcon to load the icon. This restricts icon size to 32x32, but has less chance of failing ' try: large_icons, small_icons = win32gui.ExtractIconEx(module, index, 10) except pywintypes.error as err: if err.winerror != winerror.ERROR_FILE_NOT_FOUND: raise prints('File %r does not exist, cannot load icon' % module) return icons = large_icons + small_icons try: if icons: must_use_qt() pixmap = copy_to_size(QtWin.fromHICON(icons[0]), size=size) if as_data: return pixmap_to_data(pixmap) return QIcon(pixmap) finally: tuple(map(win32gui.DestroyIcon, icons))
def __init__(self, container): self.container = container self.log = self.logger = container.log must_use_qt() self.loop = QEventLoop() self.view = QWebView() self.page = Page(self.log) self.view.setPage(self.page) self.page.setViewportSize(QSize(1200, 1600)) self.view.loadFinished.connect(self.collect, type=Qt.QueuedConnection) self.render_queue = list(container.spine_items) self.font_stats = {} QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception( 'Failed to gather statistics from book, see log for details')
def __init__(self, container): self.container = container self.log = self.logger = container.log must_use_qt() self.loop = QEventLoop() self.view = QWebView() self.page = Page(self.log) self.view.setPage(self.page) self.page.setViewportSize(QSize(1200, 1600)) self.view.loadFinished.connect(self.collect, type=Qt.QueuedConnection) self.render_queue = list(container.spine_items) self.font_stats = {} QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('Failed to gather statistics from book, see log for details')
def worker_main(source): QLoggingCategory.setFilterRules('''\ qt.webenginecontext.info=false ''') from calibre.gui2 import must_use_qt from .simple_backend import SimpleScraper must_use_qt() s = SimpleScraper(source) for line in sys.stdin.buffer: line = line.strip() if source == 'test': print(line.decode('utf-8'), file=sys.stderr) try: cmd, rest = line.split(b':', 1) except Exception: continue if cmd == b'EXIT': raise SystemExit(int(rest)) if cmd == b'FETCH': try: d = json.loads(rest) html = s.fetch(QUrl.fromEncoded(d['url'].encode('utf-8')), timeout=float(d['timeout'])) except Exception as e: import traceback result = { 'ok': False, 'tb': traceback.format_exc(), 'err': str(e) } else: with PersistentTemporaryFile( suffix='-scraper-result.html') as t: t.write(html.encode('utf-8')) result = {'ok': True, 'html_file': t.name} print(json.dumps(result), flush=True)
def run_rapydscript_tests(): from urllib.parse import parse_qs from qt.core import QApplication, QByteArray, QEventLoop, QUrl from qt.webengine import ( QWebEnginePage, QWebEngineProfile, QWebEngineScript, QWebEngineUrlRequestJob, QWebEngineUrlScheme, QWebEngineUrlSchemeHandler ) from calibre.constants import FAKE_HOST, FAKE_PROTOCOL from calibre.gui2 import must_use_qt from calibre.gui2.viewer.web_view import send_reply from calibre.gui2.webengine import secure_webengine, insert_scripts, create_script must_use_qt() scheme = QWebEngineUrlScheme(FAKE_PROTOCOL.encode('ascii')) scheme.setSyntax(QWebEngineUrlScheme.Syntax.Host) scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme) QWebEngineUrlScheme.registerScheme(scheme) base = base_dir() rapydscript_dir = os.path.join(base, 'src', 'pyj') fname = os.path.join(rapydscript_dir, 'test.pyj') with lopen(fname, 'rb') as f: js = compile_fast(f.read(), fname) class UrlSchemeHandler(QWebEngineUrlSchemeHandler): def __init__(self, parent=None): QWebEngineUrlSchemeHandler.__init__(self, parent) self.allowed_hosts = (FAKE_HOST,) self.registered_data = {} 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: return self.fail_request(rq) q = parse_qs(url.query()) if not q: return self.fail_request(rq) mt = q.get('mime-type', ('text/plain',))[0] data = q.get('data', ('',))[0].encode('utf-8') send_reply(rq, mt, 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()}", file=sys.stderr) class Tester(QWebEnginePage): def __init__(self): profile = QWebEngineProfile(QApplication.instance()) profile.setHttpUserAgent('calibre-tester') insert_scripts(profile, create_script('test-rapydscript.js', js, on_subframes=False)) url_handler = UrlSchemeHandler(profile) profile.installUrlSchemeHandler(QByteArray(FAKE_PROTOCOL.encode('ascii')), url_handler) QWebEnginePage.__init__(self, profile, None) self.titleChanged.connect(self.title_changed) secure_webengine(self) self.setHtml('<p>initialize', QUrl(f'{FAKE_PROTOCOL}://{FAKE_HOST}/index.html')) self.working = True def title_changed(self, title): if title == 'initialized': self.titleChanged.disconnect() self.runJavaScript('window.main()', QWebEngineScript.ScriptWorldId.ApplicationWorld, self.callback) def spin_loop(self): while self.working: QApplication.instance().processEvents(QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents) return self.result def callback(self, result): self.result = result self.working = False def javaScriptConsoleMessage(self, level, msg, line_num, source_id): print(msg, file=sys.stderr if level > 0 else sys.stdout) tester = Tester() result = tester.spin_loop() raise SystemExit(int(result))
def __init__(self): from calibre.gui2 import must_use_qt must_use_qt()
def handle_embedded_fonts(self): ''' Because of QtWebKit's inability to handle embedded fonts correctly, we remove the embedded fonts and make them available system wide instead. If you ever move to Qt WebKit 2.3+ then this will be unnecessary. ''' from calibre.ebooks.oeb.base import urlnormalize from calibre.gui2 import must_use_qt from calibre.utils.fonts.utils import get_font_names, remove_embed_restriction from PyQt4.Qt import QFontDatabase, QByteArray # First find all @font-face rules and remove them, adding the embedded # fonts to Qt family_map = {} for item in list(self.oeb.manifest): if not hasattr(item.data, 'cssRules'): continue remove = set() for i, rule in enumerate(item.data.cssRules): if rule.type == rule.FONT_FACE_RULE: remove.add(i) try: s = rule.style src = s.getProperty('src').propertyValue[0].uri font_family = s.getProperty('font-family').propertyValue[0].value except: continue path = item.abshref(src) ff = self.oeb.manifest.hrefs.get(urlnormalize(path), None) if ff is None: continue raw = ff.data self.oeb.manifest.remove(ff) try: raw = remove_embed_restriction(raw) except: continue must_use_qt() QFontDatabase.addApplicationFontFromData(QByteArray(raw)) try: family_name = get_font_names(raw)[0] except: family_name = None if family_name: family_map[icu_lower(font_family)] = family_name for i in sorted(remove, reverse=True): item.data.cssRules.pop(i) # Now map the font family name specified in the css to the actual # family name of the embedded font (they may be different in general). for item in self.oeb.manifest: if not hasattr(item.data, 'cssRules'): continue for i, rule in enumerate(item.data.cssRules): if rule.type != rule.STYLE_RULE: continue ff = rule.style.getProperty('font-family') if ff is None: continue val = ff.propertyValue for i in xrange(val.length): k = icu_lower(val[i].value) if k in family_map: val[i].value = family_map[k]
def render_svg(filepath): must_use_qt(headless=False) pngpath = filepath[:-4] + '.png' i = QImage(filepath) i.save(pngpath)
def __init__(self, base_css=''): self.base_css = base_css from calibre.gui2 import must_use_qt must_use_qt()
def compiler(): from calibre_lzma.xz import decompress ans = getattr(compiler, 'ans', None) if ans is not None: return ans from calibre import walk from calibre.gui2 import must_use_qt from calibre.gui2.webengine import secure_webengine from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript from PyQt5.Qt import QApplication, QEventLoop must_use_qt() buf = BytesIO() decompress(P(COMPILER_PATH, data=True, allow_user_override=False), buf) base = base_dir() rapydscript_dir = os.path.join(base, 'src', 'pyj') cache_path = os.path.join(module_cache_dir(), 'embedded-compiler-write-cache.json') def create_vfs(): ans = {} for x in walk(rapydscript_dir): if x.endswith('.pyj'): r = os.path.relpath(x, rapydscript_dir).replace('\\', '/') with open(x, 'rb') as f: ans['__stdlib__/' + r] = f.read().decode('utf-8') return ans def vfs_script(): try: with open(cache_path, 'rb') as f: write_cache = f.read().decode('utf-8') except Exception: write_cache = '{}' return ''' (function() { "use strict"; var vfs = VFS; function read_file_sync(name) { var ans = vfs[name]; if (typeof ans === "string") return ans; ans = write_cache[name]; if (typeof ans === "string") return ans; return null; } function write_file_sync(name, data) { write_cache[name] = data; } RapydScript.virtual_file_system = { 'read_file_sync': read_file_sync, 'write_file_sync': write_file_sync }; window.compiler = RapydScript.create_embedded_compiler(); document.title = 'compiler initialized'; })(); '''.replace( 'VFS', json.dumps(create_vfs()) + ';\n' + 'window.write_cache = ' + write_cache, 1) def create_script(src, name): s = QWebEngineScript() s.setName(name) s.setInjectionPoint(QWebEngineScript.DocumentReady) s.setWorldId(QWebEngineScript.ApplicationWorld) s.setRunsOnSubFrames(True) s.setSourceCode(src) return s class Compiler(QWebEnginePage): def __init__(self): QWebEnginePage.__init__(self) self.errors = [] secure_webengine(self) script = buf.getvalue().decode('utf-8') script += '\n\n;;\n\n' + vfs_script() self.scripts().insert(create_script(script, 'rapydscript.js')) self.setHtml('<p>initialize') while self.title() != 'compiler initialized': self.spin_loop() def spin_loop(self): QApplication.instance().processEvents( QEventLoop.ExcludeUserInputEvents) def javaScriptConsoleMessage(self, level, msg, line_num, source_id): if level: self.errors.append(msg) else: print('{}:{}:{}'.format(source_id, line_num, msg)) def __call__(self, src, options): self.compiler_result = null = object() self.errors = [] self.working = True options['basedir'] = '__stdlib__' options['write_name'] = True options['keep_docstrings'] = False src = 'var js = window.compiler.compile({}, {}); [js, window.write_cache]'.format( *map(json.dumps, (src, options))) self.runJavaScript(src, QWebEngineScript.ApplicationWorld, self.compilation_done) while self.working: self.spin_loop() if self.compiler_result is null or self.compiler_result is None: raise CompileFailure( 'Failed to compile rapydscript code with error: ' + '\n'.join(self.errors)) write_cache = self.compiler_result[1] with open(cache_path, 'wb') as f: f.write(as_bytes(json.dumps(write_cache))) return self.compiler_result[0] def eval(self, js): self.compiler_result = null = object() self.errors = [] self.working = True self.runJavaScript(js, QWebEngineScript.ApplicationWorld, self.compilation_done) while self.working: self.spin_loop() if self.compiler_result is null: raise CompileFailure('Failed to eval JS with error: ' + '\n'.join(self.errors)) return self.compiler_result def compilation_done(self, js): self.working = False self.compiler_result = js compiler.ans = Compiler() return compiler.ans