From 3a5a616226e5fd568dfeeac2754695e68d4d994b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 24 Oct 2012 18:27:09 +0530 Subject: [PATCH] E-book viewer: Add workaround for Qt embedded font bug (at least for ebooks that put all their css in .css files, such as ebooks generated by calibre) --- src/calibre/ebooks/oeb/iterator/book.py | 14 ++- .../ebooks/oeb/iterator/extract_fonts.py | 100 ++++++++++++++++++ src/calibre/gui2/viewer/main.py | 3 +- 3 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 src/calibre/ebooks/oeb/iterator/extract_fonts.py diff --git a/src/calibre/ebooks/oeb/iterator/book.py b/src/calibre/ebooks/oeb/iterator/book.py index e5955dffb5..992d5304c6 100644 --- a/src/calibre/ebooks/oeb/iterator/book.py +++ b/src/calibre/ebooks/oeb/iterator/book.py @@ -22,7 +22,6 @@ from calibre.utils.logging import default_log from calibre import (guess_type, prepare_string_for_xml, xml_replace_entities) from calibre.ebooks.oeb.transforms.cover import CoverManager - from calibre.ebooks.oeb.iterator.spine import (SpineItem, create_indexing_data) from calibre.ebooks.oeb.iterator.bookmarks import BookmarksMixin @@ -76,7 +75,8 @@ class EbookIterator(BookmarksMixin): return i def __enter__(self, processed=False, only_input_plugin=False, - run_char_count=True, read_anchor_map=True): + run_char_count=True, read_anchor_map=True, + extract_embedded_fonts_for_qt=False): ''' Convert an ebook file into an exploded OEB book suitable for display in viewers/preprocessing etc. ''' @@ -174,6 +174,16 @@ class EbookIterator(BookmarksMixin): self.read_bookmarks() + if extract_embedded_fonts_for_qt: + from calibre.ebooks.oeb.iterator.extract_fonts import extract_fonts + try: + extract_fonts(self.opf, self.log) + except: + ol = self.log.filter_level + self.log.filter_level = self.log.DEBUG + self.log.exception('Failed to extract fonts') + self.log.filter_level = ol + return self def __exit__(self, *args): diff --git a/src/calibre/ebooks/oeb/iterator/extract_fonts.py b/src/calibre/ebooks/oeb/iterator/extract_fonts.py new file mode 100644 index 0000000000..7b2ec5312f --- /dev/null +++ b/src/calibre/ebooks/oeb/iterator/extract_fonts.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import re, os, logging +from functools import partial +from future_builtins import map + +class FamilyMap(dict): + + def __init__(self, log): + dict.__init__(self) + self.replace_map = {} + self.added_fonts = set() + self.log = log + + def __call__(self, basedir, match): + self.read_font_fule(basedir, match.group()) + return b'' + + def finalize(self): + if self.replace_map: + self.pat = re.compile(br'(font-family.*?)(' + + b'|'.join(self.replace_map.iterkeys())+b')', re.I) + + def replace_font_families(self, raw): + if self.replace_map: + def sub(m): + k = m.group(2).lower() + for q, val in self.replace_map.iteritems(): + if q.lower() == k.lower(): + return m.group().replace(m.group(2), val) + return m.group() + + return self.pat.sub(sub, raw) + + def read_font_fule(self, basedir, css): + from PyQt4.Qt import QFontDatabase + import cssutils + cssutils.log.setLevel(logging.ERROR) + try: + sheet = cssutils.parseString(css) + except: + return + for rule in sheet.cssRules: + try: + s = rule.style + src = s.getProperty('src').propertyValue[0].uri + font_family = s.getProperty('font-family').propertyValue[0].value + except: + continue + if not src or not font_family: + continue + font_file = os.path.normcase(os.path.abspath(os.path.join(basedir, + src))) + if font_file not in self.added_fonts: + self.added_fonts.add(font_file) + if os.path.exists(font_file): + with open(font_file, 'rb') as f: + idx = QFontDatabase.addApplicationFontFromData(f.read()) + if idx > -1: + family = map(unicode, + QFontDatabase.applicationFontFamilies(idx)).next() + self.log('Extracted embedded font:', family, 'from', + os.path.basename(font_file)) + if (family and family != font_family and + family not in self.replace_map): + self.log('Replacing font family value:', + font_family, 'with', family) + self.replace_map[font_family.encode('utf-8')] = \ + family.encode('utf-8') + +def extract_fonts(opf, log): + css_files = {} + font_family_map = FamilyMap(log) + pat = re.compile(br'^\s*@font-face\s*{[^}]+}', re.M) + + for item in opf.manifest: + if item.mime_type and item.mime_type.lower() in { + 'text/css', 'text/x-oeb1-css', 'text/x-oeb-css'}: + try: + with open(item.path, 'rb') as f: + raw = f.read() + except EnvironmentError: + continue + css_files[item.path] = pat.sub(partial(font_family_map, + os.path.dirname(item.path)), raw) + + font_family_map.finalize() + + for path, raw in css_files.iteritems(): + with open(path, 'wb') as f: + nraw = font_family_map.replace_font_families(raw) + f.write(nraw) + diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index b47773465f..be332ec207 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -963,7 +963,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.iterator.__exit__() self.iterator = EbookIterator(pathtoebook) self.open_progress_indicator(_('Loading ebook...')) - worker = Worker(target=self.iterator.__enter__) + worker = Worker(target=partial(self.iterator.__enter__, + extract_embedded_fonts_for_qt=True)) worker.start() while worker.isAlive(): worker.join(0.1)