def dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) ml, mr = opts.margin_left, opts.margin_right self.doc = PdfDevice( out_stream, page_size=page_size, left_margin=ml, top_margin=opts.margin_top, right_margin=mr, bottom_margin=opts.margin_bottom, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, opts=opts, mark_links=opts.pdf_mark_links) self.painter = QPainter(self.doc) self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags, mi=pdf_metadata.mi) self.doc_title = pdf_metadata.title self.doc_author = pdf_metadata.author for imgpath in items: self.log.debug('Processing %s...' % imgpath) self.doc.init_page() p = QPixmap() with lopen(imgpath, 'rb') as f: if not p.loadFromData(f.read()): raise ValueError('Could not read image from: {}'.format(imgpath)) draw_image_page(QRect(*self.doc.full_page_rect), self.painter, p, preserve_aspect_ratio=True) self.doc.end_page() if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details')
def main(): app = QApplication([]) app tdir = os.path.abspath('.') pdf = os.path.join(tdir, 'painter.pdf') func = full dpi = 100 with open(pdf, 'wb') as f: dev = PdfDevice(f, xdpi=dpi, ydpi=dpi, compress=False) img = QImage(dev.width(), dev.height(), QImage.Format_ARGB32_Premultiplied) img.setDotsPerMeterX(dpi*39.37) img.setDotsPerMeterY(dpi*39.37) img.fill(Qt.white) run(dev, func) run(img, func) path = os.path.join(tdir, 'painter.png') img.save(path) print ('PDF written to:', pdf) print ('Image written to:', path)
def main(): app = QApplication([]) app tdir = os.path.abspath('.') pdf = os.path.join(tdir, 'painter.pdf') func = full dpi = 100 with open(pdf, 'wb') as f: dev = PdfDevice(f, xdpi=dpi, ydpi=dpi, compress=False) img = QImage(dev.width(), dev.height(), QImage.Format_ARGB32_Premultiplied) img.setDotsPerMeterX(dpi * 39.37) img.setDotsPerMeterY(dpi * 39.37) img.fill(Qt.white) run(dev, func) run(img, func) path = os.path.join(tdir, 'painter.png') img.save(path) print('PDF written to:', pdf) print('Image written to:', path)
def dump(self, items, out_stream, pdf_metadata): opts = self.opts self.outline = Outline(self.toc, items) page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() ml, mr = opts.margin_left, opts.margin_right margin_side = min(ml, mr) ml, mr = ml - margin_side, mr - margin_side self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf) self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) # TODO: Test margins mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom)) ms = self.doc.to_px(margin_side, vertical=False) self.margin_top, self.margin_size, self.margin_bottom = map( lambda x:int(floor(x)), (mt, ms, mb)) self.painter = QPainter(self.doc) self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags) self.painter.save() try: if self.cover_data is not None: p = QPixmap() p.loadFromData(self.cover_data) if not p.isNull(): draw_image_page(QRect(0, 0, self.doc.width(), self.doc.height()), self.painter, p, preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) self.loop.exec_() # TODO: Outline and links self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details')
def dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() def margin(which): val = getattr(opts, 'pdf_page_margin_' + which) if val == 0.0: val = getattr(opts, 'margin_' + which) return val ml, mr, mt, mb = map(margin, 'left right top bottom'.split()) # We cannot set the side margins in the webview as there is no right # margin for the last page (the margins are implemented with # -webkit-column-gap) self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, opts=opts, mark_links=opts.pdf_mark_links, page_margins=(ml, mr, mt, mb)) self.footer = opts.pdf_footer_template if self.footer: self.footer = self.footer.strip() if not self.footer and opts.pdf_page_numbers: self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>' self.header = opts.pdf_header_template if self.header: self.header = self.header.strip() min_margin = 1.5 * opts._final_base_font_size if self.footer and mb < min_margin: self.log.warn( 'Bottom margin is too small for footer, increasing it to %.1fpts' % min_margin) mb = min_margin if self.header and mt < min_margin: self.log.warn( 'Top margin is too small for header, increasing it to %.1fpts' % min_margin) mt = min_margin self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) mt, mb = map(self.doc.to_px, (mt, mb)) self.margin_top, self.margin_bottom = map(lambda x: int(floor(x)), (mt, mb)) self.painter = QPainter(self.doc) try: self.book_language = pdf_metadata.mi.languages[0] except Exception: self.book_language = 'eng' self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags, mi=pdf_metadata.mi) self.doc_title = pdf_metadata.title self.doc_author = pdf_metadata.author self.painter.save() try: if self.cover_data is not None: p = QPixmap() try: p.loadFromData(self.cover_data) except TypeError: self.log.warn( 'This ebook does not have a raster cover, cannot generate cover for PDF' '. Cover type: %s' % type(self.cover_data)) if not p.isNull(): self.doc.init_page() draw_image_page(QRect(*self.doc.full_page_rect), self.painter, p, preserve_aspect_ratio=self.opts. preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('PDF Output failed, see log for details') if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details')
class PDFWriter(QObject): @pyqtSlot(result=unicode_type) def title(self): return self.doc_title @pyqtSlot(result=unicode_type) def author(self): return self.doc_author @pyqtSlot(result=unicode_type) def section(self): return self.current_section @pyqtSlot(result=unicode_type) def tl_section(self): return self.current_tl_section 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 dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() def margin(which): val = getattr(opts, 'pdf_page_margin_' + which) if val == 0.0: val = getattr(opts, 'margin_' + which) return val ml, mr, mt, mb = map(margin, 'left right top bottom'.split()) # We cannot set the side margins in the webview as there is no right # margin for the last page (the margins are implemented with # -webkit-column-gap) self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, opts=opts, mark_links=opts.pdf_mark_links, page_margins=(ml, mr, mt, mb)) self.footer = opts.pdf_footer_template if self.footer: self.footer = self.footer.strip() if not self.footer and opts.pdf_page_numbers: self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>' self.header = opts.pdf_header_template if self.header: self.header = self.header.strip() min_margin = 1.5 * opts._final_base_font_size if self.footer and mb < min_margin: self.log.warn( 'Bottom margin is too small for footer, increasing it to %.1fpts' % min_margin) mb = min_margin if self.header and mt < min_margin: self.log.warn( 'Top margin is too small for header, increasing it to %.1fpts' % min_margin) mt = min_margin self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) mt, mb = map(self.doc.to_px, (mt, mb)) self.margin_top, self.margin_bottom = map(lambda x: int(floor(x)), (mt, mb)) self.painter = QPainter(self.doc) try: self.book_language = pdf_metadata.mi.languages[0] except Exception: self.book_language = 'eng' self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags, mi=pdf_metadata.mi) self.doc_title = pdf_metadata.title self.doc_author = pdf_metadata.author self.painter.save() try: if self.cover_data is not None: p = QPixmap() try: p.loadFromData(self.cover_data) except TypeError: self.log.warn( 'This ebook does not have a raster cover, cannot generate cover for PDF' '. Cover type: %s' % type(self.cover_data)) if not p.isNull(): self.doc.init_page() draw_image_page(QRect(*self.doc.full_page_rect), self.painter, p, preserve_aspect_ratio=self.opts. preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('PDF Output failed, see log for details') if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details') def render_inline_toc(self): evaljs = self.view.page().mainFrame().evaluateJavaScript self.rendered_inline_toc = True from calibre.ebooks.pdf.render.toc import toc_as_html raw = toc_as_html(self.toc, self.doc, self.opts, evaljs) pt = PersistentTemporaryFile('_pdf_itoc.htm') pt.write(raw) pt.close() self.render_queue.append(pt.name) self.render_next() def render_book(self): if self.doc.errors_occurred: return self.loop.exit(1) try: if not self.render_queue: if self.opts.pdf_add_toc and self.toc is not None and len( self.toc) > 0 and not hasattr(self, 'rendered_inline_toc'): return self.render_inline_toc() self.loop.exit() else: self.render_next() except: self.logger.exception('Rendering failed') self.loop.exit(1) def render_next(self): item = unicode_type(self.render_queue.pop(0)) self.logger.debug('Processing %s...' % item) self.current_item = item load_html(item, self.view) self.last_load_progress_at = monotonic() self.hang_check_timer.start() def load_progress(self, progress): self.last_load_progress_at = monotonic() def hang_check(self): if monotonic() - self.last_load_progress_at > 60: self.log.warn('Timed out waiting for %s to render' % self.current_item) self.ignore_failure = self.current_item self.view.stop() def render_html(self, ok): self.hang_check_timer.stop() if self.ignore_failure == self.current_item: ok = True self.ignore_failure = None if ok: try: self.do_paged_render() except: self.log.exception('Rendering failed') self.loop.exit(1) return else: # The document is so corrupt that we can't render the page. self.logger.error('Document %s cannot be rendered.' % self.current_item) self.loop.exit(1) return done = self.total_items - len(self.render_queue) self.report_progress( done / self.total_items, _('Rendered %s' % os.path.basename(self.current_item))) self.render_book() @property def current_page_num(self): return self.doc.current_page_num def load_mathjax(self): evaljs = self.view.page().mainFrame().evaluateJavaScript mjpath = self.mathjax_dir.replace(os.sep, '/') if iswindows: mjpath = u'/' + mjpath if bool( evaljs(''' window.mathjax.base = %s; mathjax.check_for_math(); mathjax.math_present ''' % (json.dumps(mjpath, ensure_ascii=False)))): self.log.debug('Math present, loading MathJax') while not bool(evaljs('mathjax.math_loaded')): self.loop.processEvents(self.loop.ExcludeUserInputEvents) # give the MathJax fonts time to load for i in range(5): self.loop.processEvents(self.loop.ExcludeUserInputEvents) evaljs( 'document.getElementById("MathJax_Message").style.display="none";' ) def load_header_footer_images(self): from calibre.utils.monotonic import monotonic evaljs = self.view.page().mainFrame().evaluateJavaScript st = monotonic() while not evaljs('paged_display.header_footer_images_loaded()'): self.loop.processEvents(self.loop.ExcludeUserInputEvents) if monotonic() - st > 5: self.log.warn( 'Header and footer images have not loaded in 5 seconds, ignoring' ) break def get_sections(self, anchor_map, only_top_level=False): sections = defaultdict(list) ci = os.path.abspath(os.path.normcase(self.current_item)) if self.toc is not None: tocentries = self.toc.top_level_items( ) if only_top_level else self.toc.flat() for toc in tocentries: path = toc.abspath or None frag = toc.fragment or None if path is None: continue path = os.path.abspath(os.path.normcase(path)) if path == ci: col = 0 if frag and frag in anchor_map: col = anchor_map[frag]['column'] sections[col].append(toc.text or _('Untitled')) return sections def hyphenate(self, evaljs): evaljs(u'''\ Hyphenator.config( { 'minwordlength' : 6, // 'hyphenchar' : '|', 'displaytogglebox' : false, 'remoteloading' : false, 'doframes' : true, 'defaultlanguage' : 'en', 'storagetype' : 'session', 'onerrorhandler' : function (e) { console.log(e); } }); Hyphenator.hyphenate(document.body, "%s"); ''' % self.hyphenate_lang) def convert_page_margins(self, doc_margins): ans = [0, 0, 0, 0] def convert(name, idx, vertical=True): m = doc_margins.get(name) if m is None: ans[idx] = getattr(self.doc.engine, '{}_margin'.format(name)) else: ans[idx] = m convert('left', 0, False), convert('top', 1), convert('right', 2, False), convert('bottom', 3) return ans def do_paged_render(self): if self.paged_js is None: import uuid from calibre.utils.resources import compiled_coffeescript as cc self.paged_js = cc('ebooks.oeb.display.utils').decode('utf-8') self.paged_js += cc('ebooks.oeb.display.indexing').decode('utf-8') self.paged_js += cc('ebooks.oeb.display.paged').decode('utf-8') self.paged_js += cc('ebooks.oeb.display.mathjax').decode('utf-8') if self.opts.pdf_hyphenate: self.paged_js += P('viewer/hyphenate/Hyphenator.js', data=True).decode('utf-8') hjs, self.hyphenate_lang = load_hyphenator_dicts( {}, self.book_language) self.paged_js += hjs self.hf_uuid = str(uuid.uuid4()).replace('-', '') self.view.page().mainFrame().addToJavaScriptWindowObject( "py_bridge", self) self.view.page().longjs_counter = 0 evaljs = self.view.page().mainFrame().evaluateJavaScript evaljs(self.paged_js) self.load_mathjax() if self.opts.pdf_hyphenate: self.hyphenate(evaljs) margin_top, margin_bottom = self.margin_top, self.margin_bottom page_margins = None if self.opts.pdf_use_document_margins: doc_margins = evaljs( 'document.documentElement.getAttribute("data-calibre-pdf-output-page-margins")' ) try: doc_margins = json.loads(doc_margins) except Exception: doc_margins = None if doc_margins and isinstance(doc_margins, dict): doc_margins = { k: float(v) for k, v in iteritems(doc_margins) if isinstance(v, numbers.Number) and k in {'right', 'top', 'left', 'bottom'} } if doc_margins: margin_top = margin_bottom = 0 page_margins = self.convert_page_margins(doc_margins) amap = json.loads( evaljs(''' document.body.style.backgroundColor = "white"; // Qt WebKit cannot handle opacity with the Pdf backend s = document.createElement('style'); s.textContent = '* {opacity: 1 !important}'; document.documentElement.appendChild(s); paged_display.set_geometry(1, %d, %d, %d); paged_display.layout(); paged_display.fit_images(); ret = book_indexing.all_links_and_anchors(); window.scrollTo(0, 0); // This is needed as getting anchor positions could have caused the viewport to scroll JSON.stringify(ret); ''' % (margin_top, 0, margin_bottom))) if not isinstance(amap, dict): amap = { 'links': [], 'anchors': {} } # Some javascript error occurred for val in itervalues(amap['anchors']): if isinstance(val, dict) and 'column' in val: val['column'] = int(val['column']) for href, val in amap['links']: if isinstance(val, dict) and 'column' in val: val['column'] = int(val['column']) sections = self.get_sections(amap['anchors']) tl_sections = self.get_sections(amap['anchors'], True) col = 0 if self.header: evaljs('paged_display.header_template = ' + json.dumps(self.header)) if self.footer: evaljs('paged_display.footer_template = ' + json.dumps(self.footer)) if self.header or self.footer: evaljs('paged_display.create_header_footer("%s");' % self.hf_uuid) start_page = self.current_page_num mf = self.view.page().mainFrame() def set_section(col, sections, attr): # If this page has no section, use the section from the previous page idx = col if col in sections else col - 1 if col - 1 in sections else None if idx is not None: setattr(self, attr, sections[idx][0]) from calibre.ebooks.pdf.render.toc import calculate_page_number while True: set_section(col, sections, 'current_section') set_section(col, tl_sections, 'current_tl_section') self.doc.init_page(page_margins) num = calculate_page_number(self.current_page_num, self.opts.pdf_page_number_map, evaljs) if self.header or self.footer: if evaljs('paged_display.update_header_footer(%d)' % num) is True: self.load_header_footer_images() self.painter.save() mf.render(self.painter, mf.ContentsLayer) self.painter.restore() try: nsl = int(evaljs('paged_display.next_screen_location()')) except (TypeError, ValueError): break self.doc.end_page(nsl <= 0) if nsl <= 0: break evaljs( 'window.scrollTo(%d, 0); paged_display.position_header_footer();' % nsl) if self.doc.errors_occurred: break col += 1 if not self.doc.errors_occurred and self.doc.current_page_num > 1: self.doc.add_links(self.current_item, start_page, amap['links'], amap['anchors'])
def dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() # We cannot set the side margins in the webview as there is no right # margin for the last page (the margins are implemented with # -webkit-column-gap) ml, mr = opts.margin_left, opts.margin_right self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, mark_links=opts.pdf_mark_links) self.footer = opts.pdf_footer_template if self.footer: self.footer = self.footer.strip() if not self.footer and opts.pdf_page_numbers: self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>' self.header = opts.pdf_header_template if self.header: self.header = self.header.strip() min_margin = 36 if self.footer and opts.margin_bottom < min_margin: self.log.warn( 'Bottom margin is too small for footer, increasing it.') opts.margin_bottom = min_margin if self.header and opts.margin_top < min_margin: self.log.warn('Top margin is too small for header, increasing it.') opts.margin_top = min_margin self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom)) self.margin_top, self.margin_bottom = map(lambda x: int(floor(x)), (mt, mb)) self.painter = QPainter(self.doc) self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags) self.doc_title = pdf_metadata.title self.doc_author = pdf_metadata.author self.painter.save() try: if self.cover_data is not None: p = QPixmap() p.loadFromData(self.cover_data) if not p.isNull(): self.doc.init_page() draw_image_page(QRect(*self.doc.full_page_rect), self.painter, p, preserve_aspect_ratio=self.opts. preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('PDF Output failed, see log for details') if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details')
class PDFWriter(QObject): def _pass_json_value_getter(self): val = json.dumps(self.bridge_value) return QString(val) def _pass_json_value_setter(self, value): self.bridge_value = json.loads(unicode(value)) _pass_json_value = pyqtProperty(QString, fget=_pass_json_value_getter, fset=_pass_json_value_setter) @pyqtSlot(result=unicode) def title(self): return self.doc_title @pyqtSlot(result=unicode) def author(self): return self.doc_author @pyqtSlot(result=unicode) def section(self): return self.current_section def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import is_ok_to_use_qt if not is_ok_to_use_qt(): raise Exception('Not OK to 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 = '' def dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() # We cannot set the side margins in the webview as there is no right # margin for the last page (the margins are implemented with # -webkit-column-gap) ml, mr = opts.margin_left, opts.margin_right self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, mark_links=opts.pdf_mark_links) self.footer = opts.pdf_footer_template if self.footer: self.footer = self.footer.strip() if not self.footer and opts.pdf_page_numbers: self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>' self.header = opts.pdf_header_template if self.header: self.header = self.header.strip() min_margin = 36 if self.footer and opts.margin_bottom < min_margin: self.log.warn( 'Bottom margin is too small for footer, increasing it.') opts.margin_bottom = min_margin if self.header and opts.margin_top < min_margin: self.log.warn('Top margin is too small for header, increasing it.') opts.margin_top = min_margin self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom)) self.margin_top, self.margin_bottom = map(lambda x: int(floor(x)), (mt, mb)) self.painter = QPainter(self.doc) self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags) self.doc_title = pdf_metadata.title self.doc_author = pdf_metadata.author self.painter.save() try: if self.cover_data is not None: p = QPixmap() p.loadFromData(self.cover_data) if not p.isNull(): self.doc.init_page() draw_image_page(QRect(*self.doc.full_page_rect), self.painter, p, preserve_aspect_ratio=self.opts. preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('PDF Output failed, see log for details') if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details') def render_book(self): if self.doc.errors_occurred: return self.loop.exit(1) try: if not self.render_queue: self.loop.exit() else: self.render_next() except: self.logger.exception('Rendering failed') self.loop.exit(1) def render_next(self): item = unicode(self.render_queue.pop(0)) self.logger.debug('Processing %s...' % item) self.current_item = item load_html(item, self.view) def render_html(self, ok): if ok: try: self.do_paged_render() except: self.log.exception('Rendering failed') self.loop.exit(1) return else: # The document is so corrupt that we can't render the page. self.logger.error('Document cannot be rendered.') self.loop.exit(1) return done = self.total_items - len(self.render_queue) self.report_progress( done / self.total_items, _('Rendered %s' % os.path.basename(self.current_item))) self.render_book() @property def current_page_num(self): return self.doc.current_page_num def load_mathjax(self): evaljs = self.view.page().mainFrame().evaluateJavaScript mjpath = P(u'viewer/mathjax').replace(os.sep, '/') if iswindows: mjpath = u'/' + mjpath if evaljs(''' window.mathjax.base = %s; mathjax.check_for_math(); mathjax.math_present ''' % (json.dumps(mjpath, ensure_ascii=False))).toBool(): self.log.debug('Math present, loading MathJax') while not evaljs('mathjax.math_loaded').toBool(): self.loop.processEvents(self.loop.ExcludeUserInputEvents) evaljs( 'document.getElementById("MathJax_Message").style.display="none";' ) def get_sections(self, anchor_map): sections = {} ci = os.path.abspath(os.path.normcase(self.current_item)) if self.toc is not None: for toc in self.toc.flat(): path = toc.abspath or None frag = toc.fragment or None if path is None: continue path = os.path.abspath(os.path.normcase(path)) if path == ci: col = 0 if frag and frag in anchor_map: col = anchor_map[frag]['column'] if col not in sections: sections[col] = toc.text or _('Untitled') return sections def do_paged_render(self): if self.paged_js is None: import uuid from calibre.utils.resources import compiled_coffeescript as cc self.paged_js = cc('ebooks.oeb.display.utils') self.paged_js += cc('ebooks.oeb.display.indexing') self.paged_js += cc('ebooks.oeb.display.paged') self.paged_js += cc('ebooks.oeb.display.mathjax') self.hf_uuid = str(uuid.uuid4()).replace('-', '') self.view.page().mainFrame().addToJavaScriptWindowObject( "py_bridge", self) self.view.page().longjs_counter = 0 evaljs = self.view.page().mainFrame().evaluateJavaScript evaljs(self.paged_js) self.load_mathjax() evaljs(''' py_bridge.__defineGetter__('value', function() { return JSON.parse(this._pass_json_value); }); py_bridge.__defineSetter__('value', function(val) { this._pass_json_value = JSON.stringify(val); }); document.body.style.backgroundColor = "white"; paged_display.set_geometry(1, %d, %d, %d); paged_display.layout(); paged_display.fit_images(); py_bridge.value = book_indexing.all_links_and_anchors(); ''' % (self.margin_top, 0, self.margin_bottom)) amap = self.bridge_value if not isinstance(amap, dict): amap = { 'links': [], 'anchors': {} } # Some javascript error occurred sections = self.get_sections(amap['anchors']) col = 0 if self.header: self.bridge_value = self.header evaljs('paged_display.header_template = py_bridge.value') if self.footer: self.bridge_value = self.footer evaljs('paged_display.footer_template = py_bridge.value') if self.header or self.footer: evaljs('paged_display.create_header_footer("%s");' % self.hf_uuid) start_page = self.current_page_num mf = self.view.page().mainFrame() while True: if col in sections: self.current_section = sections[col] self.doc.init_page() if self.header or self.footer: evaljs('paged_display.update_header_footer(%d)' % self.current_page_num) self.painter.save() mf.render(self.painter) self.painter.restore() nsl = evaljs('paged_display.next_screen_location()').toInt() self.doc.end_page() if not nsl[1] or nsl[0] <= 0: break evaljs( 'window.scrollTo(%d, 0); paged_display.position_header_footer();' % nsl[0]) if self.doc.errors_occurred: break col += 1 if not self.doc.errors_occurred: self.doc.add_links(self.current_item, start_page, amap['links'], amap['anchors'])
def dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() # We cannot set the side margins in the webview as there is no right # margin for the last page (the margins are implemented with # -webkit-column-gap) ml, mr = opts.margin_left, opts.margin_right self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, opts=opts, mark_links=opts.pdf_mark_links) self.footer = opts.pdf_footer_template if self.footer: self.footer = self.footer.strip() if not self.footer and opts.pdf_page_numbers: self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>' self.header = opts.pdf_header_template if self.header: self.header = self.header.strip() min_margin = 1.5 * opts._final_base_font_size if self.footer and opts.margin_bottom < min_margin: self.log.warn('Bottom margin is too small for footer, increasing it to %.1fpts' % min_margin) opts.margin_bottom = min_margin if self.header and opts.margin_top < min_margin: self.log.warn('Top margin is too small for header, increasing it to %.1fpts' % min_margin) opts.margin_top = min_margin self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom)) self.margin_top, self.margin_bottom = map(lambda x:int(floor(x)), (mt, mb)) self.painter = QPainter(self.doc) self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags, mi=pdf_metadata.mi) self.doc_title = pdf_metadata.title self.doc_author = pdf_metadata.author self.painter.save() try: if self.cover_data is not None: p = QPixmap() try: p.loadFromData(self.cover_data) except TypeError: self.log.warn('This ebook does not have a raster cover, cannot generate cover for PDF' '. Cover type: %s' % type(self.cover_data)) if not p.isNull(): self.doc.init_page() draw_image_page(QRect(*self.doc.full_page_rect), self.painter, p, preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('PDF Output failed, see log for details') if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details')
class PDFWriter(QObject): def _pass_json_value_getter(self): val = json.dumps(self.bridge_value) return val def _pass_json_value_setter(self, value): self.bridge_value = json.loads(unicode(value)) _pass_json_value = pyqtProperty(str, fget=_pass_json_value_getter, fset=_pass_json_value_setter) @pyqtSlot(result=unicode) def title(self): return self.doc_title @pyqtSlot(result=unicode) def author(self): return self.doc_author @pyqtSlot(result=unicode) def section(self): return self.current_section @pyqtSlot(result=unicode) def tl_section(self): return self.current_tl_section 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 dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() # We cannot set the side margins in the webview as there is no right # margin for the last page (the margins are implemented with # -webkit-column-gap) ml, mr = opts.margin_left, opts.margin_right self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, opts=opts, mark_links=opts.pdf_mark_links) self.footer = opts.pdf_footer_template if self.footer: self.footer = self.footer.strip() if not self.footer and opts.pdf_page_numbers: self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>' self.header = opts.pdf_header_template if self.header: self.header = self.header.strip() min_margin = 1.5 * opts._final_base_font_size if self.footer and opts.margin_bottom < min_margin: self.log.warn('Bottom margin is too small for footer, increasing it to %.1fpts' % min_margin) opts.margin_bottom = min_margin if self.header and opts.margin_top < min_margin: self.log.warn('Top margin is too small for header, increasing it to %.1fpts' % min_margin) opts.margin_top = min_margin self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom)) self.margin_top, self.margin_bottom = map(lambda x:int(floor(x)), (mt, mb)) self.painter = QPainter(self.doc) self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags, mi=pdf_metadata.mi) self.doc_title = pdf_metadata.title self.doc_author = pdf_metadata.author self.painter.save() try: if self.cover_data is not None: p = QPixmap() try: p.loadFromData(self.cover_data) except TypeError: self.log.warn('This ebook does not have a raster cover, cannot generate cover for PDF' '. Cover type: %s' % type(self.cover_data)) if not p.isNull(): self.doc.init_page() draw_image_page(QRect(*self.doc.full_page_rect), self.painter, p, preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('PDF Output failed, see log for details') if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details') def render_inline_toc(self): self.rendered_inline_toc = True from calibre.ebooks.pdf.render.toc import toc_as_html raw = toc_as_html(self.toc, self.doc, self.opts) pt = PersistentTemporaryFile('_pdf_itoc.htm') pt.write(raw) pt.close() self.render_queue.append(pt.name) self.render_next() def render_book(self): if self.doc.errors_occurred: return self.loop.exit(1) try: if not self.render_queue: if self.opts.pdf_add_toc and self.toc is not None and len(self.toc) > 0 and not hasattr(self, 'rendered_inline_toc'): return self.render_inline_toc() self.loop.exit() else: self.render_next() except: self.logger.exception('Rendering failed') self.loop.exit(1) def render_next(self): item = unicode(self.render_queue.pop(0)) self.logger.debug('Processing %s...' % item) self.current_item = item load_html(item, self.view) def render_html(self, ok): if ok: try: self.do_paged_render() except: self.log.exception('Rendering failed') self.loop.exit(1) return else: # The document is so corrupt that we can't render the page. self.logger.error('Document cannot be rendered.') self.loop.exit(1) return done = self.total_items - len(self.render_queue) self.report_progress(done/self.total_items, _('Rendered %s'%os.path.basename(self.current_item))) self.render_book() @property def current_page_num(self): return self.doc.current_page_num def load_mathjax(self): evaljs = self.view.page().mainFrame().evaluateJavaScript mjpath = P(u'viewer/mathjax').replace(os.sep, '/') if iswindows: mjpath = u'/' + mjpath if bool(evaljs(''' window.mathjax.base = %s; mathjax.check_for_math(); mathjax.math_present '''%(json.dumps(mjpath, ensure_ascii=False)))): self.log.debug('Math present, loading MathJax') while not bool(evaljs('mathjax.math_loaded')): self.loop.processEvents(self.loop.ExcludeUserInputEvents) evaljs('document.getElementById("MathJax_Message").style.display="none";') def get_sections(self, anchor_map, only_top_level=False): sections = defaultdict(list) ci = os.path.abspath(os.path.normcase(self.current_item)) if self.toc is not None: tocentries = self.toc.top_level_items() if only_top_level else self.toc.flat() for toc in tocentries: path = toc.abspath or None frag = toc.fragment or None if path is None: continue path = os.path.abspath(os.path.normcase(path)) if path == ci: col = 0 if frag and frag in anchor_map: col = anchor_map[frag]['column'] sections[col].append(toc.text or _('Untitled')) return sections def do_paged_render(self): if self.paged_js is None: import uuid from calibre.utils.resources import compiled_coffeescript as cc self.paged_js = cc('ebooks.oeb.display.utils') self.paged_js += cc('ebooks.oeb.display.indexing') self.paged_js += cc('ebooks.oeb.display.paged') self.paged_js += cc('ebooks.oeb.display.mathjax') self.hf_uuid = str(uuid.uuid4()).replace('-', '') self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self) self.view.page().longjs_counter = 0 evaljs = self.view.page().mainFrame().evaluateJavaScript evaljs(self.paged_js) self.load_mathjax() evaljs(''' Object.defineProperty(py_bridge, 'value', { get : function() { return JSON.parse(this._pass_json_value); }, set : function(val) { this._pass_json_value = JSON.stringify(val); } }); document.body.style.backgroundColor = "white"; paged_display.set_geometry(1, %d, %d, %d); paged_display.layout(); paged_display.fit_images(); py_bridge.value = book_indexing.all_links_and_anchors(); window.scrollTo(0, 0); // This is needed as getting anchor positions could have caused the viewport to scroll '''%(self.margin_top, 0, self.margin_bottom)) amap = self.bridge_value if not isinstance(amap, dict): amap = {'links':[], 'anchors':{}} # Some javascript error occurred sections = self.get_sections(amap['anchors']) tl_sections = self.get_sections(amap['anchors'], True) col = 0 if self.header: self.bridge_value = self.header evaljs('paged_display.header_template = py_bridge.value') if self.footer: self.bridge_value = self.footer evaljs('paged_display.footer_template = py_bridge.value') if self.header or self.footer: evaljs('paged_display.create_header_footer("%s");'%self.hf_uuid) start_page = self.current_page_num mf = self.view.page().mainFrame() def set_section(col, sections, attr): # If this page has no section, use the section from the previous page idx = col if col in sections else col - 1 if col - 1 in sections else None if idx is not None: setattr(self, attr, sections[idx][0]) while True: set_section(col, sections, 'current_section') set_section(col, tl_sections, 'current_tl_section') self.doc.init_page() if self.header or self.footer: evaljs('paged_display.update_header_footer(%d)'%self.current_page_num) self.painter.save() mf.render(self.painter) self.painter.restore() try: nsl = int(evaljs('paged_display.next_screen_location()')) except (TypeError, ValueError): break self.doc.end_page(nsl <= 0) if nsl <= 0: break evaljs('window.scrollTo(%d, 0); paged_display.position_header_footer();'%nsl) if self.doc.errors_occurred: break col += 1 if not self.doc.errors_occurred: self.doc.add_links(self.current_item, start_page, amap['links'], amap['anchors'])
class PDFWriter(QObject): def _pass_json_value_getter(self): val = json.dumps(self.bridge_value) return val def _pass_json_value_setter(self, value): self.bridge_value = json.loads(unicode(value)) _pass_json_value = pyqtProperty(str, fget=_pass_json_value_getter, fset=_pass_json_value_setter) @pyqtSlot(result=unicode) def title(self): return self.doc_title @pyqtSlot(result=unicode) def author(self): return self.doc_author @pyqtSlot(result=unicode) def section(self): return self.current_section @pyqtSlot(result=unicode) def tl_section(self): return self.current_tl_section 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 dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() def margin(which): val = getattr(opts, 'pdf_page_margin_' + which) if val == 0.0: val = getattr(opts, 'margin_' + which) return val ml, mr, mt, mb = map(margin, 'left right top bottom'.split()) # We cannot set the side margins in the webview as there is no right # margin for the last page (the margins are implemented with # -webkit-column-gap) self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, opts=opts, mark_links=opts.pdf_mark_links) self.footer = opts.pdf_footer_template if self.footer: self.footer = self.footer.strip() if not self.footer and opts.pdf_page_numbers: self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>' self.header = opts.pdf_header_template if self.header: self.header = self.header.strip() min_margin = 1.5 * opts._final_base_font_size if self.footer and mb < min_margin: self.log.warn( 'Bottom margin is too small for footer, increasing it to %.1fpts' % min_margin) mb = min_margin if self.header and mt < min_margin: self.log.warn( 'Top margin is too small for header, increasing it to %.1fpts' % min_margin) mt = min_margin self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) mt, mb = map(self.doc.to_px, (mt, mb)) self.margin_top, self.margin_bottom = map(lambda x: int(floor(x)), (mt, mb)) self.painter = QPainter(self.doc) self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags, mi=pdf_metadata.mi) self.doc_title = pdf_metadata.title self.doc_author = pdf_metadata.author self.painter.save() try: if self.cover_data is not None: p = QPixmap() try: p.loadFromData(self.cover_data) except TypeError: self.log.warn( 'This ebook does not have a raster cover, cannot generate cover for PDF' '. Cover type: %s' % type(self.cover_data)) if not p.isNull(): self.doc.init_page() draw_image_page(QRect(*self.doc.full_page_rect), self.painter, p, preserve_aspect_ratio=self.opts. preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('PDF Output failed, see log for details') if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details') def render_inline_toc(self): self.rendered_inline_toc = True from calibre.ebooks.pdf.render.toc import toc_as_html raw = toc_as_html(self.toc, self.doc, self.opts) pt = PersistentTemporaryFile('_pdf_itoc.htm') pt.write(raw) pt.close() self.render_queue.append(pt.name) self.render_next() def render_book(self): if self.doc.errors_occurred: return self.loop.exit(1) try: if not self.render_queue: if self.opts.pdf_add_toc and self.toc is not None and len( self.toc) > 0 and not hasattr(self, 'rendered_inline_toc'): return self.render_inline_toc() self.loop.exit() else: self.render_next() except: self.logger.exception('Rendering failed') self.loop.exit(1) def render_next(self): item = unicode(self.render_queue.pop(0)) self.logger.debug('Processing %s...' % item) self.current_item = item load_html(item, self.view) def render_html(self, ok): if ok: try: self.do_paged_render() except: self.log.exception('Rendering failed') self.loop.exit(1) return else: # The document is so corrupt that we can't render the page. self.logger.error('Document cannot be rendered.') self.loop.exit(1) return done = self.total_items - len(self.render_queue) self.report_progress( done / self.total_items, _('Rendered %s' % os.path.basename(self.current_item))) self.render_book() @property def current_page_num(self): return self.doc.current_page_num def load_mathjax(self): evaljs = self.view.page().mainFrame().evaluateJavaScript mjpath = P(u'viewer/mathjax').replace(os.sep, '/') if iswindows: mjpath = u'/' + mjpath if bool( evaljs(''' window.mathjax.base = %s; mathjax.check_for_math(); mathjax.math_present ''' % (json.dumps(mjpath, ensure_ascii=False)))): self.log.debug('Math present, loading MathJax') while not bool(evaljs('mathjax.math_loaded')): self.loop.processEvents(self.loop.ExcludeUserInputEvents) evaljs( 'document.getElementById("MathJax_Message").style.display="none";' ) def get_sections(self, anchor_map, only_top_level=False): sections = defaultdict(list) ci = os.path.abspath(os.path.normcase(self.current_item)) if self.toc is not None: tocentries = self.toc.top_level_items( ) if only_top_level else self.toc.flat() for toc in tocentries: path = toc.abspath or None frag = toc.fragment or None if path is None: continue path = os.path.abspath(os.path.normcase(path)) if path == ci: col = 0 if frag and frag in anchor_map: col = anchor_map[frag]['column'] sections[col].append(toc.text or _('Untitled')) return sections def do_paged_render(self): if self.paged_js is None: import uuid from calibre.utils.resources import compiled_coffeescript as cc self.paged_js = cc('ebooks.oeb.display.utils') self.paged_js += cc('ebooks.oeb.display.indexing') self.paged_js += cc('ebooks.oeb.display.paged') self.paged_js += cc('ebooks.oeb.display.mathjax') self.hf_uuid = str(uuid.uuid4()).replace('-', '') self.view.page().mainFrame().addToJavaScriptWindowObject( "py_bridge", self) self.view.page().longjs_counter = 0 evaljs = self.view.page().mainFrame().evaluateJavaScript evaljs(self.paged_js) self.load_mathjax() evaljs(''' Object.defineProperty(py_bridge, 'value', { get : function() { return JSON.parse(this._pass_json_value); }, set : function(val) { this._pass_json_value = JSON.stringify(val); } }); document.body.style.backgroundColor = "white"; paged_display.set_geometry(1, %d, %d, %d); paged_display.layout(); paged_display.fit_images(); py_bridge.value = book_indexing.all_links_and_anchors(); window.scrollTo(0, 0); // This is needed as getting anchor positions could have caused the viewport to scroll ''' % (self.margin_top, 0, self.margin_bottom)) amap = self.bridge_value if not isinstance(amap, dict): amap = { 'links': [], 'anchors': {} } # Some javascript error occurred sections = self.get_sections(amap['anchors']) tl_sections = self.get_sections(amap['anchors'], True) col = 0 if self.header: self.bridge_value = self.header evaljs('paged_display.header_template = py_bridge.value') if self.footer: self.bridge_value = self.footer evaljs('paged_display.footer_template = py_bridge.value') if self.header or self.footer: evaljs('paged_display.create_header_footer("%s");' % self.hf_uuid) start_page = self.current_page_num mf = self.view.page().mainFrame() def set_section(col, sections, attr): # If this page has no section, use the section from the previous page idx = col if col in sections else col - 1 if col - 1 in sections else None if idx is not None: setattr(self, attr, sections[idx][0]) while True: set_section(col, sections, 'current_section') set_section(col, tl_sections, 'current_tl_section') self.doc.init_page() if self.header or self.footer: evaljs('paged_display.update_header_footer(%d)' % self.current_page_num) self.painter.save() mf.render(self.painter) self.painter.restore() try: nsl = int(evaljs('paged_display.next_screen_location()')) except (TypeError, ValueError): break self.doc.end_page(nsl <= 0) if nsl <= 0: break evaljs( 'window.scrollTo(%d, 0); paged_display.position_header_footer();' % nsl) if self.doc.errors_occurred: break col += 1 if not self.doc.errors_occurred and self.doc.current_page_num > 1: self.doc.add_links(self.current_item, start_page, amap['links'], amap['anchors'])
class PDFWriter(QObject): @pyqtSlot(result=unicode) def title(self): return self.doc_title @pyqtSlot(result=unicode) def author(self): return self.doc_author @pyqtSlot(result=unicode) def section(self): return self.current_section @pyqtSlot(result=unicode) def tl_section(self): return self.current_tl_section 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 dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() def margin(which): val = getattr(opts, 'pdf_page_margin_' + which) if val == 0.0: val = getattr(opts, 'margin_' + which) return val ml, mr, mt, mb = map(margin, 'left right top bottom'.split()) # We cannot set the side margins in the webview as there is no right # margin for the last page (the margins are implemented with # -webkit-column-gap) self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, opts=opts, mark_links=opts.pdf_mark_links, page_margins=(ml, mr, mt, mb)) self.footer = opts.pdf_footer_template if self.footer: self.footer = self.footer.strip() if not self.footer and opts.pdf_page_numbers: self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>' self.header = opts.pdf_header_template if self.header: self.header = self.header.strip() min_margin = 1.5 * opts._final_base_font_size if self.footer and mb < min_margin: self.log.warn('Bottom margin is too small for footer, increasing it to %.1fpts' % min_margin) mb = min_margin if self.header and mt < min_margin: self.log.warn('Top margin is too small for header, increasing it to %.1fpts' % min_margin) mt = min_margin self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) mt, mb = map(self.doc.to_px, (mt, mb)) self.margin_top, self.margin_bottom = map(lambda x:int(floor(x)), (mt, mb)) self.painter = QPainter(self.doc) try: self.book_language = pdf_metadata.mi.languages[0] except Exception: self.book_language = 'eng' self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags, mi=pdf_metadata.mi) self.doc_title = pdf_metadata.title self.doc_author = pdf_metadata.author self.painter.save() try: if self.cover_data is not None: p = QPixmap() try: p.loadFromData(self.cover_data) except TypeError: self.log.warn('This ebook does not have a raster cover, cannot generate cover for PDF' '. Cover type: %s' % type(self.cover_data)) if not p.isNull(): self.doc.init_page() draw_image_page(QRect(*self.doc.full_page_rect), self.painter, p, preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('PDF Output failed, see log for details') if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details') def render_inline_toc(self): evaljs = self.view.page().mainFrame().evaluateJavaScript self.rendered_inline_toc = True from calibre.ebooks.pdf.render.toc import toc_as_html raw = toc_as_html(self.toc, self.doc, self.opts, evaljs) pt = PersistentTemporaryFile('_pdf_itoc.htm') pt.write(raw) pt.close() self.render_queue.append(pt.name) self.render_next() def render_book(self): if self.doc.errors_occurred: return self.loop.exit(1) try: if not self.render_queue: if self.opts.pdf_add_toc and self.toc is not None and len(self.toc) > 0 and not hasattr(self, 'rendered_inline_toc'): return self.render_inline_toc() self.loop.exit() else: self.render_next() except: self.logger.exception('Rendering failed') self.loop.exit(1) def render_next(self): item = unicode(self.render_queue.pop(0)) self.logger.debug('Processing %s...' % item) self.current_item = item load_html(item, self.view) def render_html(self, ok): if ok: try: self.do_paged_render() except: self.log.exception('Rendering failed') self.loop.exit(1) return else: # The document is so corrupt that we can't render the page. self.logger.error('Document cannot be rendered.') self.loop.exit(1) return done = self.total_items - len(self.render_queue) self.report_progress(done/self.total_items, _('Rendered %s'%os.path.basename(self.current_item))) self.render_book() @property def current_page_num(self): return self.doc.current_page_num def load_mathjax(self): evaljs = self.view.page().mainFrame().evaluateJavaScript mjpath = P(u'viewer/mathjax').replace(os.sep, '/') if iswindows: mjpath = u'/' + mjpath if bool(evaljs(''' window.mathjax.base = %s; mathjax.check_for_math(); mathjax.math_present '''%(json.dumps(mjpath, ensure_ascii=False)))): self.log.debug('Math present, loading MathJax') while not bool(evaljs('mathjax.math_loaded')): self.loop.processEvents(self.loop.ExcludeUserInputEvents) evaljs('document.getElementById("MathJax_Message").style.display="none";') def load_header_footer_images(self): from calibre.utils.monotonic import monotonic evaljs = self.view.page().mainFrame().evaluateJavaScript st = monotonic() while not evaljs('paged_display.header_footer_images_loaded()'): self.loop.processEvents(self.loop.ExcludeUserInputEvents) if monotonic() - st > 5: self.log.warn('Header and footer images have not loaded in 5 seconds, ignoring') break def get_sections(self, anchor_map, only_top_level=False): sections = defaultdict(list) ci = os.path.abspath(os.path.normcase(self.current_item)) if self.toc is not None: tocentries = self.toc.top_level_items() if only_top_level else self.toc.flat() for toc in tocentries: path = toc.abspath or None frag = toc.fragment or None if path is None: continue path = os.path.abspath(os.path.normcase(path)) if path == ci: col = 0 if frag and frag in anchor_map: col = anchor_map[frag]['column'] sections[col].append(toc.text or _('Untitled')) return sections def hyphenate(self, evaljs): evaljs(u'''\ Hyphenator.config( { 'minwordlength' : 6, // 'hyphenchar' : '|', 'displaytogglebox' : false, 'remoteloading' : false, 'doframes' : true, 'defaultlanguage' : 'en', 'storagetype' : 'session', 'onerrorhandler' : function (e) { console.log(e); } }); Hyphenator.hyphenate(document.body, "%s"); ''' % self.hyphenate_lang ) def convert_page_margins(self, doc_margins): ans = [0, 0, 0, 0] def convert(name, idx, vertical=True): m = doc_margins.get(name) if m is None: ans[idx] = getattr(self.doc.engine, '{}_margin'.format(name)) else: ans[idx] = m convert('left', 0, False), convert('top', 1), convert('right', 2, False), convert('bottom', 3) return ans def do_paged_render(self): if self.paged_js is None: import uuid from calibre.utils.resources import compiled_coffeescript as cc self.paged_js = cc('ebooks.oeb.display.utils') self.paged_js += cc('ebooks.oeb.display.indexing') self.paged_js += cc('ebooks.oeb.display.paged') self.paged_js += cc('ebooks.oeb.display.mathjax') if self.opts.pdf_hyphenate: self.paged_js += P('viewer/hyphenate/Hyphenator.js', data=True).decode('utf-8') hjs, self.hyphenate_lang = load_hyphenator_dicts({}, self.book_language) self.paged_js += hjs self.hf_uuid = str(uuid.uuid4()).replace('-', '') self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self) self.view.page().longjs_counter = 0 evaljs = self.view.page().mainFrame().evaluateJavaScript evaljs(self.paged_js) self.load_mathjax() if self.opts.pdf_hyphenate: self.hyphenate(evaljs) margin_top, margin_bottom = self.margin_top, self.margin_bottom page_margins = None if self.opts.pdf_use_document_margins: doc_margins = evaljs('document.documentElement.getAttribute("data-calibre-pdf-output-page-margins")') try: doc_margins = json.loads(doc_margins) except Exception: doc_margins = None if doc_margins and isinstance(doc_margins, dict): doc_margins = {k:float(v) for k, v in doc_margins.iteritems() if isinstance(v, (float, int)) and k in {'right', 'top', 'left', 'bottom'}} if doc_margins: margin_top = margin_bottom = 0 page_margins = self.convert_page_margins(doc_margins) amap = json.loads(evaljs(''' document.body.style.backgroundColor = "white"; // Qt WebKit cannot handle opacity with the Pdf backend s = document.createElement('style'); s.textContent = '* {opacity: 1 !important}'; document.documentElement.appendChild(s); paged_display.set_geometry(1, %d, %d, %d); paged_display.layout(); paged_display.fit_images(); ret = book_indexing.all_links_and_anchors(); window.scrollTo(0, 0); // This is needed as getting anchor positions could have caused the viewport to scroll JSON.stringify(ret); '''%(margin_top, 0, margin_bottom))) if not isinstance(amap, dict): amap = {'links':[], 'anchors':{}} # Some javascript error occurred for val in amap['anchors'].itervalues(): if isinstance(val, dict) and 'column' in val: val['column'] = int(val['column']) for href, val in amap['links']: if isinstance(val, dict) and 'column' in val: val['column'] = int(val['column']) sections = self.get_sections(amap['anchors']) tl_sections = self.get_sections(amap['anchors'], True) col = 0 if self.header: evaljs('paged_display.header_template = ' + json.dumps(self.header)) if self.footer: evaljs('paged_display.footer_template = ' + json.dumps(self.footer)) if self.header or self.footer: evaljs('paged_display.create_header_footer("%s");'%self.hf_uuid) start_page = self.current_page_num mf = self.view.page().mainFrame() def set_section(col, sections, attr): # If this page has no section, use the section from the previous page idx = col if col in sections else col - 1 if col - 1 in sections else None if idx is not None: setattr(self, attr, sections[idx][0]) from calibre.ebooks.pdf.render.toc import calculate_page_number while True: set_section(col, sections, 'current_section') set_section(col, tl_sections, 'current_tl_section') self.doc.init_page(page_margins) num = calculate_page_number(self.current_page_num, self.opts.pdf_page_number_map, evaljs) if self.header or self.footer: if evaljs('paged_display.update_header_footer(%d)'%num) is True: self.load_header_footer_images() self.painter.save() mf.render(self.painter, mf.ContentsLayer) self.painter.restore() try: nsl = int(evaljs('paged_display.next_screen_location()')) except (TypeError, ValueError): break self.doc.end_page(nsl <= 0) if nsl <= 0: break evaljs('window.scrollTo(%d, 0); paged_display.position_header_footer();'%nsl) if self.doc.errors_occurred: break col += 1 if not self.doc.errors_occurred and self.doc.current_page_num > 1: self.doc.add_links(self.current_item, start_page, amap['links'], amap['anchors'])
class PDFWriter(QObject): def _pass_json_value_getter(self): val = json.dumps(self.bridge_value) return QString(val) def _pass_json_value_setter(self, value): self.bridge_value = json.loads(unicode(value)) _pass_json_value = pyqtProperty(QString, fget=_pass_json_value_getter, fset=_pass_json_value_setter) def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import is_ok_to_use_qt if not is_ok_to_use_qt(): raise Exception('Not OK to 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 def dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() # We cannot set the side margins in the webview as there is no right # margin for the last page (the margins are implemented with # -webkit-column-gap) ml, mr = opts.margin_left, opts.margin_right self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, mark_links=opts.pdf_mark_links) self.footer = opts.pdf_footer_template if self.footer is None and opts.pdf_page_numbers: self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>' self.header = opts.pdf_header_template min_margin = 36 if self.footer and opts.margin_bottom < min_margin: self.log.warn('Bottom margin is too small for footer, increasing it.') opts.margin_bottom = min_margin if self.header and opts.margin_top < min_margin: self.log.warn('Top margin is too small for header, increasing it.') opts.margin_top = min_margin self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom)) self.margin_top, self.margin_bottom = map(lambda x:int(floor(x)), (mt, mb)) self.painter = QPainter(self.doc) self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags) self.painter.save() try: if self.cover_data is not None: p = QPixmap() p.loadFromData(self.cover_data) if not p.isNull(): self.doc.init_page() draw_image_page(QRect(0, 0, self.doc.width(), self.doc.height()), self.painter, p, preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception('PDF Output failed, see log for details') if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details') def render_book(self): if self.doc.errors_occurred: return self.loop.exit(1) try: if not self.render_queue: self.loop.exit() else: self.render_next() except: self.logger.exception('Rendering failed') self.loop.exit(1) def render_next(self): item = unicode(self.render_queue.pop(0)) self.logger.debug('Processing %s...' % item) self.current_item = item load_html(item, self.view) def render_html(self, ok): if ok: try: self.do_paged_render() except: self.log.exception('Rendering failed') self.loop.exit(1) return else: # The document is so corrupt that we can't render the page. self.logger.error('Document cannot be rendered.') self.loop.exit(1) return done = self.total_items - len(self.render_queue) self.report_progress(done/self.total_items, _('Rendered %s'%os.path.basename(self.current_item))) self.render_book() @property def current_page_num(self): return self.doc.current_page_num def do_paged_render(self): if self.paged_js is None: from calibre.utils.resources import compiled_coffeescript self.paged_js = compiled_coffeescript('ebooks.oeb.display.utils') self.paged_js += compiled_coffeescript('ebooks.oeb.display.indexing') self.paged_js += compiled_coffeescript('ebooks.oeb.display.paged') self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self) evaljs = self.view.page().mainFrame().evaluateJavaScript evaljs(self.paged_js) evaljs(''' py_bridge.__defineGetter__('value', function() { return JSON.parse(this._pass_json_value); }); py_bridge.__defineSetter__('value', function(val) { this._pass_json_value = JSON.stringify(val); }); document.body.style.backgroundColor = "white"; paged_display.set_geometry(1, %d, %d, %d); paged_display.layout(); paged_display.fit_images(); py_bridge.value = book_indexing.all_links_and_anchors(); '''%(self.margin_top, 0, self.margin_bottom)) if self.header: self.bridge_value = self.header evaljs('paged_display.header_template = py_bridge.value') if self.footer: self.bridge_value = self.footer evaljs('paged_display.footer_template = py_bridge.value') if self.header or self.footer: evaljs('paged_display.create_header_footer();') amap = self.bridge_value if not isinstance(amap, dict): amap = {'links':[], 'anchors':{}} # Some javascript error occurred start_page = self.current_page_num mf = self.view.page().mainFrame() while True: self.doc.init_page() if self.header or self.footer: evaljs('paged_display.update_header_footer(%d)'%self.current_page_num) self.painter.save() mf.render(self.painter) self.painter.restore() nsl = evaljs('paged_display.next_screen_location()').toInt() self.doc.end_page() if not nsl[1] or nsl[0] <= 0: break evaljs('window.scrollTo(%d, 0); paged_display.position_header_footer();'%nsl[0]) if self.doc.errors_occurred: break if not self.doc.errors_occurred: self.doc.add_links(self.current_item, start_page, amap['links'], amap['anchors'])
class PDFWriter(QObject): def _pass_json_value_getter(self): val = json.dumps(self.bridge_value) return QString(val) def _pass_json_value_setter(self, value): self.bridge_value = json.loads(unicode(value)) _pass_json_value = pyqtProperty(QString, fget=_pass_json_value_getter, fset=_pass_json_value_setter) @pyqtSlot(result=unicode) def title(self): return self.doc_title @pyqtSlot(result=unicode) def author(self): return self.doc_author @pyqtSlot(result=unicode) def section(self): return self.current_section def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import is_ok_to_use_qt if not is_ok_to_use_qt(): raise Exception("Not OK to 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 = "" def dump(self, items, out_stream, pdf_metadata): opts = self.opts page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() # We cannot set the side margins in the webview as there is no right # margin for the last page (the margins are implemented with # -webkit-column-gap) ml, mr = opts.margin_left, opts.margin_right self.doc = PdfDevice( out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf, mark_links=opts.pdf_mark_links, ) self.footer = opts.pdf_footer_template if self.footer: self.footer = self.footer.strip() if not self.footer and opts.pdf_page_numbers: self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>' self.header = opts.pdf_header_template if self.header: self.header = self.header.strip() min_margin = 36 if self.footer and opts.margin_bottom < min_margin: self.log.warn("Bottom margin is too small for footer, increasing it.") opts.margin_bottom = min_margin if self.header and opts.margin_top < min_margin: self.log.warn("Top margin is too small for header, increasing it.") opts.margin_top = min_margin self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom)) self.margin_top, self.margin_bottom = map(lambda x: int(floor(x)), (mt, mb)) self.painter = QPainter(self.doc) self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags) self.doc_title = pdf_metadata.title self.doc_author = pdf_metadata.author self.painter.save() try: if self.cover_data is not None: p = QPixmap() p.loadFromData(self.cover_data) if not p.isNull(): self.doc.init_page() draw_image_page( QRect(*self.doc.full_page_rect), self.painter, p, preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio, ) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) if self.loop.exec_() == 1: raise Exception("PDF Output failed, see log for details") if self.toc is not None and len(self.toc) > 0: self.doc.add_outline(self.toc) self.painter.end() if self.doc.errors_occurred: raise Exception("PDF Output failed, see log for details") def render_inline_toc(self): self.rendered_inline_toc = True from calibre.ebooks.pdf.render.toc import toc_as_html raw = toc_as_html(self.toc, self.doc, self.opts) pt = PersistentTemporaryFile("_pdf_itoc.htm") pt.write(raw) pt.close() self.render_queue.append(pt.name) self.render_next() def render_book(self): if self.doc.errors_occurred: return self.loop.exit(1) try: if not self.render_queue: if ( self.opts.pdf_add_toc and self.toc is not None and len(self.toc) > 0 and not hasattr(self, "rendered_inline_toc") ): return self.render_inline_toc() self.loop.exit() else: self.render_next() except: self.logger.exception("Rendering failed") self.loop.exit(1) def render_next(self): item = unicode(self.render_queue.pop(0)) self.logger.debug("Processing %s..." % item) self.current_item = item load_html(item, self.view) def render_html(self, ok): if ok: try: self.do_paged_render() except: self.log.exception("Rendering failed") self.loop.exit(1) return else: # The document is so corrupt that we can't render the page. self.logger.error("Document cannot be rendered.") self.loop.exit(1) return done = self.total_items - len(self.render_queue) self.report_progress(done / self.total_items, _("Rendered %s" % os.path.basename(self.current_item))) self.render_book() @property def current_page_num(self): return self.doc.current_page_num def load_mathjax(self): evaljs = self.view.page().mainFrame().evaluateJavaScript mjpath = P("viewer/mathjax").replace(os.sep, "/") if iswindows: mjpath = "/" + mjpath if evaljs( """ window.mathjax.base = %s; mathjax.check_for_math(); mathjax.math_present """ % (json.dumps(mjpath, ensure_ascii=False)) ).toBool(): self.log.debug("Math present, loading MathJax") while not evaljs("mathjax.math_loaded").toBool(): self.loop.processEvents(self.loop.ExcludeUserInputEvents) evaljs('document.getElementById("MathJax_Message").style.display="none";') def get_sections(self, anchor_map): sections = {} ci = os.path.abspath(os.path.normcase(self.current_item)) if self.toc is not None: for toc in self.toc.flat(): path = toc.abspath or None frag = toc.fragment or None if path is None: continue path = os.path.abspath(os.path.normcase(path)) if path == ci: col = 0 if frag and frag in anchor_map: col = anchor_map[frag]["column"] if col not in sections: sections[col] = toc.text or _("Untitled") return sections def do_paged_render(self): if self.paged_js is None: import uuid from calibre.utils.resources import compiled_coffeescript as cc self.paged_js = cc("ebooks.oeb.display.utils") self.paged_js += cc("ebooks.oeb.display.indexing") self.paged_js += cc("ebooks.oeb.display.paged") self.paged_js += cc("ebooks.oeb.display.mathjax") self.hf_uuid = str(uuid.uuid4()).replace("-", "") self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self) self.view.page().longjs_counter = 0 evaljs = self.view.page().mainFrame().evaluateJavaScript evaljs(self.paged_js) self.load_mathjax() evaljs( """ py_bridge.__defineGetter__('value', function() { return JSON.parse(this._pass_json_value); }); py_bridge.__defineSetter__('value', function(val) { this._pass_json_value = JSON.stringify(val); }); document.body.style.backgroundColor = "white"; paged_display.set_geometry(1, %d, %d, %d); paged_display.layout(); paged_display.fit_images(); py_bridge.value = book_indexing.all_links_and_anchors(); window.scrollTo(0, 0); // This is needed as getting anchor positions could have caused the viewport to scroll """ % (self.margin_top, 0, self.margin_bottom) ) amap = self.bridge_value if not isinstance(amap, dict): amap = {"links": [], "anchors": {}} # Some javascript error occurred sections = self.get_sections(amap["anchors"]) col = 0 if self.header: self.bridge_value = self.header evaljs("paged_display.header_template = py_bridge.value") if self.footer: self.bridge_value = self.footer evaljs("paged_display.footer_template = py_bridge.value") if self.header or self.footer: evaljs('paged_display.create_header_footer("%s");' % self.hf_uuid) start_page = self.current_page_num mf = self.view.page().mainFrame() while True: if col in sections: self.current_section = sections[col] self.doc.init_page() if self.header or self.footer: evaljs("paged_display.update_header_footer(%d)" % self.current_page_num) self.painter.save() mf.render(self.painter) self.painter.restore() nsl = evaljs("paged_display.next_screen_location()").toInt() self.doc.end_page() if not nsl[1] or nsl[0] <= 0: break evaljs("window.scrollTo(%d, 0); paged_display.position_header_footer();" % nsl[0]) if self.doc.errors_occurred: break col += 1 if not self.doc.errors_occurred: self.doc.add_links(self.current_item, start_page, amap["links"], amap["anchors"])
class PDFWriter(QObject): def _pass_json_value_getter(self): val = json.dumps(self.bridge_value) return QString(val) def _pass_json_value_setter(self, value): self.bridge_value = json.loads(unicode(value)) _pass_json_value = pyqtProperty(QString, fget=_pass_json_value_getter, fset=_pass_json_value_setter) def __init__(self, opts, log, cover_data=None, toc=None): from calibre.gui2 import is_ok_to_use_qt if not is_ok_to_use_qt(): raise Exception('Not OK to 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 def dump(self, items, out_stream, pdf_metadata): opts = self.opts self.outline = Outline(self.toc, items) page_size = get_page_size(self.opts) xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() ml, mr = opts.margin_left, opts.margin_right margin_side = min(ml, mr) ml, mr = ml - margin_side, mr - margin_side self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, top_margin=0, right_margin=mr, bottom_margin=0, xdpi=xdpi, ydpi=ydpi, errors=self.log.error, debug=self.log.debug, compress=not opts.uncompressed_pdf) self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) self.render_queue = items self.total_items = len(items) # TODO: Test margins mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom)) ms = self.doc.to_px(margin_side, vertical=False) self.margin_top, self.margin_size, self.margin_bottom = map( lambda x:int(floor(x)), (mt, ms, mb)) self.painter = QPainter(self.doc) self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags) self.painter.save() try: if self.cover_data is not None: p = QPixmap() p.loadFromData(self.cover_data) if not p.isNull(): draw_image_page(QRect(0, 0, self.doc.width(), self.doc.height()), self.painter, p, preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio) self.doc.end_page() finally: self.painter.restore() QTimer.singleShot(0, self.render_book) self.loop.exec_() # TODO: Outline and links self.painter.end() if self.doc.errors_occurred: raise Exception('PDF Output failed, see log for details') def render_book(self): if self.doc.errors_occurred: return self.loop.exit(1) try: if not self.render_queue: self.loop.exit() else: self.render_next() except: self.logger.exception('Rendering failed') self.loop.exit(1) def render_next(self): item = unicode(self.render_queue.pop(0)) self.logger.debug('Processing %s...' % item) self.current_item = item load_html(item, self.view) def render_html(self, ok): if ok: try: self.do_paged_render() except: self.log.exception('Rendering failed') self.loop.exit(1) else: # The document is so corrupt that we can't render the page. self.logger.error('Document cannot be rendered.') self.loop.exit(1) return done = self.total_items - len(self.render_queue) self.report_progress(done/self.total_items, _('Rendered %s'%os.path.basename(self.current_item))) self.render_book() @property def current_page_num(self): return self.doc.current_page_num def do_paged_render(self): if self.paged_js is None: from calibre.utils.resources import compiled_coffeescript self.paged_js = compiled_coffeescript('ebooks.oeb.display.utils') self.paged_js += compiled_coffeescript('ebooks.oeb.display.indexing') self.paged_js += compiled_coffeescript('ebooks.oeb.display.paged') self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self) evaljs = self.view.page().mainFrame().evaluateJavaScript evaljs(self.paged_js) evaljs(''' py_bridge.__defineGetter__('value', function() { return JSON.parse(this._pass_json_value); }); py_bridge.__defineSetter__('value', function(val) { this._pass_json_value = JSON.stringify(val); }); document.body.style.backgroundColor = "white"; paged_display.set_geometry(1, %d, %d, %d); paged_display.layout(); paged_display.fit_images(); '''%(self.margin_top, self.margin_size, self.margin_bottom)) mf = self.view.page().mainFrame() start_page = self.current_page_num dx = 0 while True: self.doc.init_page() self.painter.save() mf.render(self.painter) self.painter.restore() nsl = evaljs('paged_display.next_screen_location()').toInt() self.doc.end_page() if not nsl[1] or nsl[0] <= 0: break dx = nsl[0] evaljs('window.scrollTo(%d, 0)'%dx) if self.doc.errors_occurred: break self.bridge_value = tuple(self.outline.anchor_map[self.current_item]) evaljs('py_bridge.value = book_indexing.anchor_positions(py_bridge.value)') amap = self.bridge_value if not isinstance(amap, dict): amap = {} # Some javascript error occurred self.outline.set_pos(self.current_item, None, start_page, 0) for anchor, x in amap.iteritems(): pagenum, ypos = x self.outline.set_pos(self.current_item, anchor, start_page + pagenum, ypos)