def searchable_names(self): ans = { 'text': OrderedDict(), 'styles': OrderedDict(), 'selected': OrderedDict(), 'open': OrderedDict() } for item in self.all_files: category = unicode(item.data(0, CATEGORY_ROLE) or '') mime = unicode(item.data(0, MIME_ROLE) or '') name = unicode(item.data(0, NAME_ROLE) or '') ok = category in {'text', 'styles'} if ok: ans[category][name] = syntax_from_mime(name, mime) if not ok: if category == 'misc': ok = mime in { guess_type('a.' + x) for x in ('opf', 'ncx', 'txt', 'xml') } elif category == 'images': ok = mime == guess_type('a.svg') if ok: cats = [] if item.isSelected(): cats.append('selected') if name in editors: cats.append('open') for cat in cats: ans[cat][name] = syntax_from_mime(name, mime) return ans
def rename_requested(self, oldname, newname): self.commit_all_editors_to_container() if guess_type(oldname) != guess_type(newname): args = os.path.splitext(oldname) + os.path.splitext(newname) if not confirm(_( 'You are changing the file type of {0}<b>{1}</b> to {2}<b>{3}</b>.' ' Doing so can cause problems, are you sure?').format( *args), 'confirm-filetype-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs): return if urlnormalize(newname) != newname: if not confirm(_( 'The name you have chosen {0} contains special characters, internally' ' it will look like: {1}Try to use only the English alphabet [a-z], numbers [0-9],' ' hyphens and underscores for file names. Other characters can cause problems for ' ' different ebook viewers. Are you sure you want to proceed?' ).format('<pre>%s</pre>' % newname, '<pre>%s</pre>' % urlnormalize(newname)), 'confirm-urlunsafe-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs): return self.add_savepoint(_('Rename %s') % oldname) name_map = {oldname: newname} self.gui.blocking_job('rename_file', _('Renaming and updating links...'), partial(self.rename_done, name_map), rename_files, current_container(), name_map)
def check_links(container): links_map = defaultdict(set) xml_types = {guess_type('a.opf'), guess_type('a.ncx')} errors = [] a = errors.append def fl(x): x = repr(x) if x.startswith('u'): x = x[1:] return x for name, mt in container.mime_map.iteritems(): if mt in OEB_DOCS or mt in OEB_STYLES or mt in xml_types: for href, lnum, col in container.iterlinks(name): tname = container.href_to_name(href, name) if tname is not None: if container.exists(tname): links_map[name].add(tname) else: a(BadLink(_('The linked resource %s does not exist') % fl(href), name, lnum, col)) else: purl = urlparse(href) if purl.scheme == 'file': a(FileLink(_('The link %s is a file:// URL') % fl(href), name, lnum, col)) elif purl.path and purl.path.startswith('/') and purl.scheme in {'', 'file'}: a(LocalLink(_('The link %s points to a file outside the book') % fl(href), name, lnum, col)) spine_docs = {name for name, linear in container.spine_names} spine_styles = {tname for name in spine_docs for tname in links_map[name] if container.mime_map[tname] in OEB_STYLES} num = -1 while len(spine_styles) > num: # Handle import rules in stylesheets num = len(spine_styles) spine_styles |= {tname for name in spine_styles for tname in links_map[name] if container.mime_map[tname] in OEB_STYLES} seen = set(OEB_DOCS) | set(OEB_STYLES) spine_resources = {tname for name in spine_docs | spine_styles for tname in links_map[name] if container.mime_map[tname] not in seen} unreferenced = set() cover_name = container.guide_type_map.get('cover', None) for name, mt in container.mime_map.iteritems(): if mt in OEB_STYLES and name not in spine_styles: a(UnreferencedResource(name)) elif mt in OEB_DOCS and name not in spine_docs: a(UnreferencedDoc(name)) elif (mt in OEB_FONTS or mt.partition('/')[0] in {'image', 'audio', 'video'}) and name not in spine_resources and name != cover_name: a(UnreferencedResource(name)) else: continue unreferenced.add(name) manifest_names = set(container.manifest_id_map.itervalues()) for name in container.mime_map: if name not in container.names_that_need_not_be_manifested and name not in manifest_names: a(Unmanifested(name)) return errors
def check_links(container): links_map = defaultdict(set) xml_types = {guess_type('a.opf'), guess_type('a.ncx')} errors = [] a = errors.append def fl(x): x = repr(x) if x.startswith('u'): x = x[1:] return x for name, mt in container.mime_map.iteritems(): if mt in OEB_DOCS or mt in OEB_STYLES or mt in xml_types: for href, lnum, col in container.iterlinks(name): tname = container.href_to_name(href, name) if tname is not None: if container.exists(tname): links_map[name].add(tname) else: a(BadLink(_('The linked resource %s does not exist') % fl(href), name, lnum, col)) else: purl = urlparse(href) if purl.scheme == 'file': a(FileLink(_('The link %s is a file:// URL') % fl(href), name, lnum, col)) elif purl.path and purl.path.startswith('/') and purl.scheme in {'', 'file'}: a(LocalLink(_('The link %s points to a file outside the book') % fl(href), name, lnum, col)) spine_docs = {name for name, linear in container.spine_names} spine_styles = {tname for name in spine_docs for tname in links_map[name] if container.mime_map[tname] in OEB_STYLES} num = -1 while len(spine_styles) > num: # Handle import rules in stylesheets num = len(spine_styles) spine_styles |= {tname for name in spine_styles for tname in links_map[name] if container.mime_map[tname] in OEB_STYLES} seen = set(OEB_DOCS) | set(OEB_STYLES) spine_resources = {tname for name in spine_docs | spine_styles for tname in links_map[name] if container.mime_map[tname] not in seen} unreferenced = set() cover_name = container.guide_type_map.get('cover', None) for name, mt in container.mime_map.iteritems(): if mt in OEB_STYLES and name not in spine_styles: a(UnreferencedResource(name)) elif mt in OEB_DOCS and name not in spine_docs: a(UnreferencedDoc(name)) elif (mt in OEB_FONTS or mt.partition('/')[0] in {'image', 'audio', 'video'}) and name not in spine_resources and name != cover_name: a(UnreferencedResource(name)) else: continue unreferenced.add(name) manifest_names = set(container.manifest_id_map.itervalues()) for name in container.mime_map: if name not in container.names_that_need_not_be_manifested and name not in manifest_names: a(Unmanifested(name)) return errors
def syntax_from_mime(name, mime): if mime in OEB_DOCS: return 'html' if mime in OEB_STYLES: return 'css' if mime in {guess_type('a.opf'), guess_type('a.ncx'), guess_type('a.xml')}: return 'xml' if mime.startswith('text/'): return 'text'
def syntax_from_mime(name, mime): if mime in OEB_DOCS: return 'html' if mime in OEB_STYLES: return 'css' if mime in {guess_type('a.opf'), guess_type('a.ncx'), guess_type('a.xml')}: return 'xml' if mime.startswith('text/'): return 'text'
def syntax_from_mime(name, mime): if mime in OEB_DOCS: return "html" if mime in OEB_STYLES: return "css" if mime in {guess_type("a.opf"), guess_type("a.ncx"), guess_type("a.xml"), "application/oebps-page-map+xml"}: return "xml" if mime.startswith("text/"): return "text" if mime.startswith("image/") and mime.partition("/")[-1].lower() in {"jpeg", "jpg", "gif", "png"}: return "raster_image"
def syntax_from_mime(name, mime): if mime in OEB_DOCS: return 'html' if mime in OEB_STYLES: return 'css' if mime in {guess_type('a.svg'), guess_type('a.opf'), guess_type('a.ncx'), guess_type('a.xml'), 'application/oebps-page-map+xml'}: return 'xml' if mime.startswith('text/'): return 'text' if mime.startswith('image/') and mime.partition('/')[-1].lower() in { 'jpeg', 'jpg', 'gif', 'png'}: return 'raster_image'
def syntax_from_mime(name, mime): if mime in OEB_DOCS: return 'html' if mime in OEB_STYLES: return 'css' if mime in {guess_type('a.svg'), guess_type('a.opf'), guess_type('a.ncx'), guess_type('a.xml'), 'application/oebps-page-map+xml'}: return 'xml' if mime.startswith('text/'): return 'text' if mime.startswith('image/') and mime.partition('/')[-1].lower() in { 'jpeg', 'jpg', 'gif', 'png'}: return 'raster_image'
def manifest_key(x): mt = x.get('media-type', '') href = x.get('href', '') ext = href.rpartition('.')[-1].lower() cat = 1000 if mt in OEB_DOCS: cat = 0 elif mt == guess_type('a.ncx'): cat = 1 elif mt in OEB_STYLES: cat = 2 elif mt.startswith('image/'): cat = 3 elif ext in {'otf', 'ttf', 'woff'}: cat = 4 elif mt.startswith('audio/'): cat = 5 elif mt.startswith('video/'): cat = 6 if cat == 0: i = spine_ids.get(x.get('id', None), 1000000000) else: i = sort_key(href) return (cat, i)
def create_item(name, linear=None): imt = container.mime_map.get(name, guess_type(name)) icat = get_category(name, imt) category = "text" if linear is not None else ({"text": "misc"}.get(icat, icat)) item = QTreeWidgetItem(self.categories["text" if linear is not None else category], 1) flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if category == "text": flags |= Qt.ItemIsDragEnabled if name not in cannot_be_renamed: flags |= Qt.ItemIsEditable item.setFlags(flags) item.setStatusTip(0, _("Full path: ") + name) item.setData(0, NAME_ROLE, name) item.setData(0, CATEGORY_ROLE, category) item.setData(0, LINEAR_ROLE, bool(linear)) item.setData(0, MIME_ROLE, imt) set_display_name(name, item) # TODO: Add appropriate tooltips based on the emblems emblems = [] if name in {cover_page_name, cover_image_name}: emblems.append("default_cover.png") if name not in manifested_names and name not in ok_to_be_unmanifested: emblems.append("dialog_question.png") if linear is False: emblems.append("arrow-down.png") if linear is None and icat == "text": # Text item outside spine emblems.append("dialog_warning.png") if category == "text" and name in processed: # Duplicate entry in spine emblems.append("dialog_warning.png") render_emblems(item, emblems) return item
def accept(self): if not self.name_is_ok: return error_dialog( self, _('No name specified'), _('You must specify a name for the new file, with an extension, for example, chapter1.html' ), show=True) tprefs['auto_link_stylesheets'] = self.link_css.isChecked() name = unicode(self.name.text()) name, ext = name.rpartition('.')[0::2] name = (name + '.' + ext.lower()).replace('\\', '/') mt = guess_type(name) if not self.file_data: if mt in OEB_DOCS: self.file_data = template_for('html').encode('utf-8') if tprefs['auto_link_stylesheets']: data = add_stylesheet_links(current_container(), name, self.file_data) if data is not None: self.file_data = data self.using_template = True elif mt in OEB_STYLES: self.file_data = template_for('css').encode('utf-8') self.using_template = True self.file_name = name QDialog.accept(self)
def manifest_key(x): mt = x.get('media-type', '') href = x.get('href', '') ext = href.rpartition('.')[-1].lower() cat = 1000 if mt in OEB_DOCS: cat = 0 elif mt == guess_type('a.ncx'): cat = 1 elif mt in OEB_STYLES: cat = 2 elif mt.startswith('image/'): cat = 3 elif ext in {'otf', 'ttf', 'woff'}: cat = 4 elif mt.startswith('audio/'): cat = 5 elif mt.startswith('video/'): cat = 6 if cat == 0: i = spine_ids.get(x.get('id', None), 1000000000) else: i = sort_key(href) return (cat, i)
def find_existing_toc(container): toc = container.opf_xpath('//opf:spine/@toc') if toc: toc = container.manifest_id_map.get(toc[0], None) if not toc: ncx = guess_type('a.ncx') toc = container.manifest_type_map.get(ncx, [None])[0] if not toc: return None return toc
def find_existing_toc(container): toc = container.opf_xpath('//opf:spine/@toc') if toc: toc = container.manifest_id_map.get(toc[0], None) if not toc: ncx = guess_type('a.ncx') toc = container.manifest_type_map.get(ncx, [None])[0] if not toc: return None return toc
def pretty_all(container): for name, mt in container.mime_map.iteritems(): prettied = False if mt in OEB_DOCS: pretty_html_tree(container, container.parsed(name)) prettied = True elif mt in OEB_STYLES: container.parsed(name) prettied = True elif name == container.opf_name: root = container.parsed(name) pretty_opf(root) pretty_xml_tree(root) prettied = True elif mt in {guess_type('a.ncx'), guess_type('a.xml')}: pretty_xml_tree(container.parsed(name)) prettied = True if prettied: container.dirty(name)
def pretty_all(container): for name, mt in container.mime_map.iteritems(): prettied = False if mt in OEB_DOCS: pretty_html_tree(container, container.parsed(name)) prettied = True elif mt in OEB_STYLES: container.parsed(name) prettied = True elif name == container.opf_name: root = container.parsed(name) pretty_opf(root) pretty_xml_tree(root) prettied = True elif mt in {guess_type('a.ncx'), guess_type('a.xml')}: pretty_xml_tree(container.parsed(name)) prettied = True if prettied: container.dirty(name)
def create_item(name, linear=None): imt = container.mime_map.get(name, guess_type(name)) icat = get_category(name, imt) category = 'text' if linear is not None else ({'text':'misc'}.get(icat, icat)) item = QTreeWidgetItem(self.categories['text' if linear is not None else category], 1) flags = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable if category == 'text': flags |= Qt.ItemFlag.ItemIsDragEnabled | Qt.ItemFlag.ItemIsDropEnabled if name not in cannot_be_renamed: flags |= Qt.ItemFlag.ItemIsEditable item.setFlags(flags) item.setStatusTip(0, _('Full path: ') + name) item.setData(0, NAME_ROLE, name) item.setData(0, CATEGORY_ROLE, category) item.setData(0, LINEAR_ROLE, bool(linear)) item.setData(0, MIME_ROLE, imt) set_display_name(name, item) tooltips = [] emblems = [] if name in {cover_page_name, cover_image_name}: emblems.append('default_cover.png') tooltips.append(_('This file is the cover %s for this book') % (_('image') if name == cover_image_name else _('page'))) if name in container.opf_name: emblems.append('metadata.png') tooltips.append(_('This file contains all the metadata and book structure information')) if imt == ncx_mime or name in nav_items: emblems.append('toc.png') tooltips.append(_('This file contains the metadata table of contents')) if name not in manifested_names and not container.ok_to_be_unmanifested(name): emblems.append('dialog_question.png') tooltips.append(_('This file is not listed in the book manifest')) if linear is False: emblems.append('arrow-down.png') tooltips.append(_('This file is marked as non-linear in the spine\nDrag it to the top to make it linear')) if linear is None and icat == 'text': # Text item outside spine emblems.append('dialog_warning.png') tooltips.append(_('This file is a text file that is not referenced in the spine')) if category == 'text' and name in processed: # Duplicate entry in spine emblems.append('dialog_error.png') tooltips.append(_('This file occurs more than once in the spine')) if category == 'fonts' and name.rpartition('.')[-1].lower() in ('ttf', 'otf'): fname = self.get_font_family_name(name) if fname: tooltips.append(fname) else: emblems.append('dialog_error.png') tooltips.append(_('Not a valid font')) render_emblems(item, emblems) if tooltips: item.setData(0, Qt.ItemDataRole.ToolTipRole, '\n'.join(tooltips)) return item
def create_item(name, linear=None): imt = container.mime_map.get(name, guess_type(name)) icat = get_category(name, imt) category = "text" if linear is not None else ({"text": "misc"}.get(icat, icat)) item = QTreeWidgetItem(self.categories["text" if linear is not None else category], 1) flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if category == "text": flags |= Qt.ItemIsDragEnabled if name not in cannot_be_renamed: flags |= Qt.ItemIsEditable item.setFlags(flags) item.setStatusTip(0, _("Full path: ") + name) item.setData(0, NAME_ROLE, name) item.setData(0, CATEGORY_ROLE, category) item.setData(0, LINEAR_ROLE, bool(linear)) item.setData(0, MIME_ROLE, imt) set_display_name(name, item) tooltips = [] emblems = [] if name in {cover_page_name, cover_image_name}: emblems.append("default_cover.png") tooltips.append( _("This file is the cover %s for this book") % (_("image") if name == cover_image_name else _("page")) ) if name in container.opf_name: emblems.append("metadata.png") tooltips.append(_("This file contains all the metadata and book structure information")) if imt == ncx_mime or name in nav_items: emblems.append("toc.png") tooltips.append(_("This file contains the metadata table of contents")) if name not in manifested_names and not container.ok_to_be_unmanifested(name): emblems.append("dialog_question.png") tooltips.append(_("This file is not listed in the book manifest")) if linear is False: emblems.append("arrow-down.png") tooltips.append( _("This file is marked as non-linear in the spine\nDrag it to the top to make it linear") ) if linear is None and icat == "text": # Text item outside spine emblems.append("dialog_warning.png") tooltips.append(_("This file is a text file that is not referenced in the spine")) if category == "text" and name in processed: # Duplicate entry in spine emblems.append("dialog_error.png") tooltips.append(_("This file occurs more than once in the spine")) render_emblems(item, emblems) if tooltips: item.setData(0, Qt.ToolTipRole, "\n".join(tooltips)) return item
def rename_requested(self, oldname, newname): self.commit_all_editors_to_container() if guess_type(oldname) != guess_type(newname): args = os.path.splitext(oldname) + os.path.splitext(newname) if not confirm( _( "You are changing the file type of {0}<b>{1}</b> to {2}<b>{3}</b>." " Doing so can cause problems, are you sure?" ).format(*args), "confirm-filetype-change", parent=self.gui, title=_("Are you sure?"), config_set=tprefs, ): return if urlnormalize(newname) != newname: if not confirm( _( "The name you have chosen {0} contains special characters, internally" " it will look like: {1}Try to use only the English alphabet [a-z], numbers [0-9]," " hyphens and underscores for file names. Other characters can cause problems for " " different ebook viewers. Are you sure you want to proceed?" ).format("<pre>%s</pre>" % newname, "<pre>%s</pre>" % urlnormalize(newname)), "confirm-urlunsafe-change", parent=self.gui, title=_("Are you sure?"), config_set=tprefs, ): return self.add_savepoint(_("Rename %s") % oldname) name_map = {oldname: newname} self.gui.blocking_job( "rename_file", _("Renaming and updating links..."), partial(self.rename_done, name_map), rename_files, current_container(), name_map, )
def check_fonts(container): font_map = {} errors = [] for name, mt in container.mime_map.iteritems(): if mt in OEB_FONTS: raw = container.raw_data(name) if mt == guess_type('a.woff'): try: raw = woff.from_woff(raw) except Exception as e: errors.append( InvalidFont(_('Not a valid WOFF font: %s') % e, name)) continue try: name_map = get_all_font_names(raw) except Exception as e: errors.append(InvalidFont(_('Not a valid font: %s') % e, name)) continue font_map[name] = name_map.get('family_name', None) or name_map.get( 'preferred_family_name', None) or name_map.get( 'wws_family_name', None) sheets = [] for name, mt in container.mime_map.iteritems(): if mt in OEB_STYLES: sheets.append((name, container.parsed(name), None)) elif mt in OEB_DOCS: for style in container.parsed(name).xpath( '//*[local-name()="style"]'): if style.get('type', 'text/css') == 'text/css': sheets.append((name, container.parse_css(style.text), style.sourceline)) for name, sheet, line_offset in sheets: for rule in sheet.cssRules.rulesOfType(CSSRule.FONT_FACE_RULE): src = rule.style.getPropertyCSSValue('src') if src is not None and src.length > 0: href = getattr(src.item(0), 'uri', None) if href is not None: fname = container.href_to_name(href, name) font_name = font_map.get(fname, None) if font_name is None: continue ff = rule.style.getPropertyCSSValue('font-family') if ff is not None and ff.length > 0: ff = getattr(ff.item(0), 'value', None) if ff is not None and ff != font_name: errors.append( FontAliasing(font_name, ff, name, line_offset)) return errors
def searchable_names(self): ans = {'text':OrderedDict(), 'styles':OrderedDict(), 'selected':OrderedDict()} for item in self.all_files: category = unicode(item.data(0, CATEGORY_ROLE) or '') mime = unicode(item.data(0, MIME_ROLE) or '') name = unicode(item.data(0, NAME_ROLE) or '') ok = category in {'text', 'styles'} if ok: ans[category][name] = syntax_from_mime(name, mime) if not ok and category == 'misc': ok = mime in {guess_type('a.'+x) for x in ('opf', 'ncx', 'txt', 'xml')} if ok and item.isSelected(): ans['selected'][name] = syntax_from_mime(name, mime) return ans
def searchable_names(self): ans = {"text": OrderedDict(), "styles": OrderedDict(), "selected": OrderedDict()} for item in self.all_files: category = unicode(item.data(0, CATEGORY_ROLE) or "") mime = unicode(item.data(0, MIME_ROLE) or "") name = unicode(item.data(0, NAME_ROLE) or "") ok = category in {"text", "styles"} if ok: ans[category][name] = syntax_from_mime(name, mime) if not ok and category == "misc": ok = mime in {guess_type("a." + x) for x in ("opf", "ncx", "txt", "xml")} if ok and item.isSelected(): ans["selected"][name] = syntax_from_mime(name, mime) return ans
def searchable_names(self): ans = {'text':OrderedDict(), 'styles':OrderedDict(), 'selected':OrderedDict(), 'open':OrderedDict()} for item in self.all_files: category = unicode(item.data(0, CATEGORY_ROLE) or '') mime = unicode(item.data(0, MIME_ROLE) or '') name = unicode(item.data(0, NAME_ROLE) or '') ok = category in {'text', 'styles'} if ok: ans[category][name] = syntax_from_mime(name, mime) if not ok: if category == 'misc': ok = mime in {guess_type('a.'+x) for x in ('opf', 'ncx', 'txt', 'xml')} elif category == 'images': ok = mime == guess_type('a.svg') if ok: cats = [] if item.isSelected(): cats.append('selected') if name in editors: cats.append('open') for cat in cats: ans[cat][name] = syntax_from_mime(name, mime) return ans
def rename_requested(self, oldname, newname): self.commit_all_editors_to_container() if guess_type(oldname) != guess_type(newname): args = os.path.splitext(oldname) + os.path.splitext(newname) if not confirm( _('You are changing the file type of {0}<b>{1}</b> to {2}<b>{3}</b>.' ' Doing so can cause problems, are you sure?').format(*args), 'confirm-filetype-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs): return if urlnormalize(newname) != newname: if not confirm( _('The name you have chosen {0} contains special characters, internally' ' it will look like: {1}Try to use only the English alphabet [a-z], numbers [0-9],' ' hyphens and underscores for file names. Other characters can cause problems for ' ' different ebook viewers. Are you sure you want to proceed?').format( '<pre>%s</pre>'%newname, '<pre>%s</pre>' % urlnormalize(newname)), 'confirm-urlunsafe-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs): return self.add_savepoint(_('Rename %s') % oldname) self.gui.blocking_job( 'rename_file', _('Renaming and updating links...'), partial(self.rename_done, oldname, newname), rename_files, current_container(), {oldname: newname})
def accept(self): if not self.name_is_ok: return error_dialog(self, _('No name specified'), _( 'You must specify a name for the new file, with an extension, for example, chapter1.html'), show=True) name = unicode(self.name.text()) name, ext = name.rpartition('.')[0::2] name = (name + '.' + ext.lower()).replace('\\', '/') mt = guess_type(name) if mt in OEB_DOCS: self.file_data = template_for('html').encode('utf-8') self.using_template = True elif mt in OEB_STYLES: self.file_data = template_for('css').encode('utf-8') self.using_template = True self.file_name = name QDialog.accept(self)
def create_item(name, linear=None): imt = container.mime_map.get(name, guess_type(name)) icat = get_category(name, imt) category = 'text' if linear is not None else ({'text':'misc'}.get(icat, icat)) item = QTreeWidgetItem(self.categories['text' if linear is not None else category], 1) flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if category == 'text': flags |= Qt.ItemIsDragEnabled if name not in cannot_be_renamed: flags |= Qt.ItemIsEditable item.setFlags(flags) item.setStatusTip(0, _('Full path: ') + name) item.setData(0, NAME_ROLE, name) item.setData(0, CATEGORY_ROLE, category) item.setData(0, LINEAR_ROLE, bool(linear)) item.setData(0, MIME_ROLE, imt) set_display_name(name, item) tooltips = [] emblems = [] if name in {cover_page_name, cover_image_name}: emblems.append('default_cover.png') tooltips.append(_('This file is the cover %s for this book') % (_('image') if name == cover_image_name else _('page'))) if name in container.opf_name: emblems.append('metadata.png') tooltips.append(_('This file contains all the metadata and book structure information')) if imt == ncx_mime: emblems.append('toc.png') tooltips.append(_('This file contains the metadata table of contents')) if name not in manifested_names and name not in ok_to_be_unmanifested: emblems.append('dialog_question.png') tooltips.append(_('This file is not listed in the book manifest')) if linear is False: emblems.append('arrow-down.png') tooltips.append(_('This file is marked as non-linear in the spine')) if linear is None and icat == 'text': # Text item outside spine emblems.append('dialog_warning.png') tooltips.append(_('This file is a text file that is not referenced in the spine')) if category == 'text' and name in processed: # Duplicate entry in spine emblems.append('dialog_warning.png') tooltips.append(_('This file occurs more than once in the spine')) render_emblems(item, emblems) if tooltips: item.setData(0, Qt.ToolTipRole, '\n'.join(tooltips)) return item
def replace_links(container, link_map, frag_map=lambda name, frag:frag): ncx_type = guess_type('toc.ncx') for name, media_type in container.mime_map.iteritems(): repl = LinkReplacer(name, container, link_map, frag_map) if media_type.lower() in OEB_DOCS: rewrite_links(container.parsed(name), repl) elif media_type.lower() in OEB_STYLES: replaceUrls(container.parsed(name), repl) elif media_type.lower() == ncx_type: for elem in container.parsed(name).xpath('//*[@src]'): src = elem.get('src') nsrc = repl(src) if src != nsrc: elem.set('src', nsrc) if repl.replaced: container.dirty(name)
def accept(self): if not self.name_is_ok: return error_dialog(self, _('No name specified'), _( 'You must specify a name for the new file, with an extension, for example, chapter1.html'), show=True) name = unicode(self.name.text()) name, ext = name.rpartition('.')[0::2] name = (name + '.' + ext.lower()).replace('\\', '/') mt = guess_type(name) if not self.file_data: if mt in OEB_DOCS: self.file_data = template_for('html').encode('utf-8') self.using_template = True elif mt in OEB_STYLES: self.file_data = template_for('css').encode('utf-8') self.using_template = True self.file_name = name QDialog.accept(self)
def check_fonts(container): font_map = {} errors = [] for name, mt in container.mime_map.iteritems(): if mt in OEB_FONTS: raw = container.raw_data(name) if mt == guess_type('a.woff'): try: raw = woff.from_woff(raw) except Exception as e: errors.append(InvalidFont(_('Not a valid WOFF font: %s') % e, name)) continue try: name_map = get_all_font_names(raw) except Exception as e: errors.append(InvalidFont(_('Not a valid font: %s') % e, name)) continue font_map[name] = name_map.get('family_name', None) or name_map.get('preferred_family_name', None) or name_map.get('wws_family_name', None) sheets = [] for name, mt in container.mime_map.iteritems(): if mt in OEB_STYLES: sheets.append((name, container.parsed(name), None)) elif mt in OEB_DOCS: for style in container.parsed(name).xpath('//*[local-name()="style"]'): if style.get('type', 'text/css') == 'text/css': sheets.append((name, container.parse_css(style.text), style.sourceline)) for name, sheet, line_offset in sheets: for rule in sheet.cssRules.rulesOfType(CSSRule.FONT_FACE_RULE): src = rule.style.getPropertyCSSValue('src') if src is not None and src.length > 0: href = getattr(src.item(0), 'uri', None) if href is not None: fname = container.href_to_name(href, name) font_name = font_map.get(fname, None) if font_name is None: continue ff = rule.style.getPropertyCSSValue('font-family') if ff is not None and ff.length > 0: ff = getattr(ff.item(0), 'value', None) if ff is not None and ff != font_name: errors.append(FontAliasing(font_name, ff, name, line_offset)) return errors
def accept(self): if not self.name_is_ok: return error_dialog( self, _("No name specified"), _("You must specify a name for the new file, with an extension, for example, chapter1.html"), show=True, ) name = unicode(self.name.text()) name, ext = name.rpartition(".")[0::2] name = (name + "." + ext.lower()).replace("\\", "/") mt = guess_type(name) if mt in OEB_DOCS: self.file_data = template_for("html").encode("utf-8") self.using_template = True elif mt in OEB_STYLES: self.file_data = template_for("css").encode("utf-8") self.using_template = True self.file_name = name QDialog.accept(self)
def create_item(name, linear=None): imt = container.mime_map.get(name, guess_type(name)) icat = get_category(name, imt) category = 'text' if linear is not None else ({ 'text': 'misc' }.get(icat, icat)) item = QTreeWidgetItem( self.categories['text' if linear is not None else category], 1) flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if category == 'text': flags |= Qt.ItemIsDragEnabled if name not in cannot_be_renamed: flags |= Qt.ItemIsEditable item.setFlags(flags) item.setStatusTip(0, _('Full path: ') + name) item.setData(0, NAME_ROLE, name) item.setData(0, CATEGORY_ROLE, category) item.setData(0, LINEAR_ROLE, bool(linear)) item.setData(0, MIME_ROLE, imt) set_display_name(name, item) # TODO: Add appropriate tooltips based on the emblems emblems = [] if name in {cover_page_name, cover_image_name}: emblems.append('default_cover.png') if name in container.opf_name: emblems.append('metadata.png') if imt == ncx_mime: emblems.append('toc.png') if name not in manifested_names and name not in ok_to_be_unmanifested: emblems.append('dialog_question.png') if linear is False: emblems.append('arrow-down.png') if linear is None and icat == 'text': # Text item outside spine emblems.append('dialog_warning.png') if category == 'text' and name in processed: # Duplicate entry in spine emblems.append('dialog_warning.png') render_emblems(item, emblems) return item
def get_recommended_folders(container, names): ' Return the folders that are recommended for the given filenames ' from calibre.ebooks.oeb.polish.container import guess_type, OEB_FONTS from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES counts = defaultdict(Counter) def mt_to_category(mt): if mt in OEB_DOCS: category = 'text' elif mt in OEB_STYLES: category = 'style' elif mt in OEB_FONTS: category = 'font' else: category = mt.partition('/')[0] return category for name, mt in container.mime_map.iteritems(): folder = name.rpartition('/')[0] if '/' in name else '' counts[mt_to_category(mt)][folder] += 1 recommendations = {category:counter.most_common(1)[0][0] for category, counter in counts.iteritems()} return {n:recommendations.get(mt_to_category(guess_type(os.path.basename(n))), '') for n in names}
def accept(self): if not self.name_is_ok: return error_dialog(self, _('No name specified'), _( 'You must specify a name for the new file, with an extension, for example, chapter1.html'), show=True) tprefs['auto_link_stylesheets'] = self.link_css.isChecked() name = unicode(self.name.text()) name, ext = name.rpartition('.')[0::2] name = (name + '.' + ext.lower()).replace('\\', '/') mt = guess_type(name) if not self.file_data: if mt in OEB_DOCS: self.file_data = template_for('html').encode('utf-8') if tprefs['auto_link_stylesheets']: data = add_stylesheet_links(current_container(), name, self.file_data) if data is not None: self.file_data = data self.using_template = True elif mt in OEB_STYLES: self.file_data = template_for('css').encode('utf-8') self.using_template = True self.file_name = name QDialog.accept(self)
def build(self, container, preserve_state=True): if preserve_state: state = self.get_state() self.clear() self.root = self.invisibleRootItem() self.root.setFlags(Qt.ItemIsDragEnabled) self.categories = {} for category, text in ( ('text', _('Text')), ('styles', _('Styles')), ('images', _('Images')), ('fonts', _('Fonts')), ('misc', _('Miscellaneous')), ): self.categories[category] = i = QTreeWidgetItem(self.root, 0) i.setText(0, text) i.setData(0, Qt.DecorationRole, self.top_level_pixmap_cache[category]) f = i.font(0) f.setBold(True) i.setFont(0, f) i.setData(0, NAME_ROLE, category) flags = Qt.ItemIsEnabled if category == 'text': flags |= Qt.ItemIsDropEnabled i.setFlags(flags) processed, seen = {}, {} cover_page_name = get_cover_page_name(container) cover_image_name = get_raster_cover_name(container) manifested_names = set() for names in container.manifest_type_map.itervalues(): manifested_names |= set(names) font_types = {guess_type('a.'+x) for x in ('ttf', 'otf', 'woff')} def get_category(mt): category = 'misc' if mt.startswith('image/'): category = 'images' elif mt in font_types: category = 'fonts' elif mt in OEB_STYLES: category = 'styles' elif mt in OEB_DOCS: category = 'text' return category def set_display_name(name, item): if name in processed: # We have an exact duplicate (can happen if there are # duplicates in the spine) item.setText(0, processed[name].text(0)) return parts = name.split('/') text = parts[-1] while text in seen and parts: text = parts.pop() + '/' + text seen[text] = item item.setText(0, text) def render_emblems(item, emblems): emblems = tuple(emblems) if not emblems: return icon = self.rendered_emblem_cache.get(emblems, None) if icon is None: pixmaps = [] for emblem in emblems: pm = self.emblem_cache.get(emblem, None) if pm is None: pm = self.emblem_cache[emblem] = QPixmap( I(emblem)).scaled(self.iconSize(), transformMode=Qt.SmoothTransformation) pixmaps.append(pm) num = len(pixmaps) w, h = pixmaps[0].width(), pixmaps[0].height() if num == 1: icon = self.rendered_emblem_cache[emblems] = QIcon(pixmaps[0]) else: canvas = QPixmap((num * w) + ((num-1)*2), h) canvas.fill(Qt.transparent) painter = QPainter(canvas) for i, pm in enumerate(pixmaps): painter.drawPixmap(i * (w + 2), 0, pm) painter.end() icon = self.rendered_emblem_cache[emblems] = canvas item.setData(0, Qt.DecorationRole, icon) ok_to_be_unmanifested = container.names_that_need_not_be_manifested def create_item(name, linear=None): imt = container.mime_map.get(name, guess_type(name)) icat = get_category(imt) category = 'text' if linear is not None else ({'text':'misc'}.get(icat, icat)) item = QTreeWidgetItem(self.categories['text' if linear is not None else category], 1) flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if category == 'text': flags |= Qt.ItemIsDragEnabled item.setFlags(flags) item.setStatusTip(0, _('Full path: ') + name) item.setData(0, NAME_ROLE, name) item.setData(0, CATEGORY_ROLE, category) set_display_name(name, item) # TODO: Add appropriate tooltips based on the emblems emblems = [] if name in {cover_page_name, cover_image_name}: emblems.append('default_cover.png') if name not in manifested_names and name not in ok_to_be_unmanifested: emblems.append('dialog_question.png') if linear is False: emblems.append('arrow-down.png') if linear is None and icat == 'text': # Text item outside spine emblems.append('dialog_warning.png') if category == 'text' and name in processed: # Duplicate entry in spine emblems.append('dialog_warning.png') render_emblems(item, emblems) return item for name, linear in container.spine_names: processed[name] = create_item(name, linear=linear) all_files = list(container.manifest_type_map.iteritems()) all_files.append((guess_type('a.opf'), [container.opf_name])) for name in container.name_path_map: if name in processed: continue processed[name] = create_item(name) for c in self.categories.itervalues(): c.setExpanded(True) if preserve_state: self.set_state(state)
def build(self, container, preserve_state=True): if preserve_state: state = self.get_state() self.clear() self.root = self.invisibleRootItem() self.root.setFlags(Qt.ItemIsDragEnabled) self.categories = {} for category, text in ( ('text', _('Text')), ('styles', _('Styles')), ('images', _('Images')), ('fonts', _('Fonts')), ('misc', _('Miscellaneous')), ): self.categories[category] = i = QTreeWidgetItem(self.root, 0) i.setText(0, text) i.setData(0, Qt.DecorationRole, self.top_level_pixmap_cache[category]) f = i.font(0) f.setBold(True) i.setFont(0, f) i.setData(0, NAME_ROLE, category) flags = Qt.ItemIsEnabled if category == 'text': flags |= Qt.ItemIsDropEnabled i.setFlags(flags) processed, seen = {}, {} cover_page_name = get_cover_page_name(container) cover_image_name = get_raster_cover_name(container) manifested_names = set() for names in container.manifest_type_map.itervalues(): manifested_names |= set(names) def get_category(name, mt): category = 'misc' if mt.startswith('image/'): category = 'images' elif mt in OEB_FONTS: category = 'fonts' elif mt in OEB_STYLES: category = 'styles' elif mt in OEB_DOCS: category = 'text' ext = name.rpartition('.')[-1].lower() if ext in {'ttf', 'otf', 'woff'}: # Probably wrong mimetype in the OPF category = 'fonts' return category def set_display_name(name, item): if name in processed: # We have an exact duplicate (can happen if there are # duplicates in the spine) item.setText(0, processed[name].text(0)) item.setText(1, processed[name].text(1)) return parts = name.split('/') text = parts[-1] while text in seen and parts: text = parts.pop() + '/' + text seen[text] = item item.setText(0, text) item.setText(1, hexlify(sort_key(text))) def render_emblems(item, emblems): emblems = tuple(emblems) if not emblems: return icon = self.rendered_emblem_cache.get(emblems, None) if icon is None: pixmaps = [] for emblem in emblems: pm = self.emblem_cache.get(emblem, None) if pm is None: pm = self.emblem_cache[emblem] = QPixmap( I(emblem)).scaled( self.iconSize(), transformMode=Qt.SmoothTransformation) pixmaps.append(pm) num = len(pixmaps) w, h = pixmaps[0].width(), pixmaps[0].height() if num == 1: icon = self.rendered_emblem_cache[emblems] = QIcon( pixmaps[0]) else: canvas = QPixmap((num * w) + ((num - 1) * 2), h) canvas.fill(Qt.transparent) painter = QPainter(canvas) for i, pm in enumerate(pixmaps): painter.drawPixmap(i * (w + 2), 0, pm) painter.end() icon = self.rendered_emblem_cache[emblems] = canvas item.setData(0, Qt.DecorationRole, icon) cannot_be_renamed = container.names_that_must_not_be_changed ncx_mime = guess_type('a.ncx') def create_item(name, linear=None): imt = container.mime_map.get(name, guess_type(name)) icat = get_category(name, imt) category = 'text' if linear is not None else ({ 'text': 'misc' }.get(icat, icat)) item = QTreeWidgetItem( self.categories['text' if linear is not None else category], 1) flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if category == 'text': flags |= Qt.ItemIsDragEnabled if name not in cannot_be_renamed: flags |= Qt.ItemIsEditable item.setFlags(flags) item.setStatusTip(0, _('Full path: ') + name) item.setData(0, NAME_ROLE, name) item.setData(0, CATEGORY_ROLE, category) item.setData(0, LINEAR_ROLE, bool(linear)) item.setData(0, MIME_ROLE, imt) set_display_name(name, item) tooltips = [] emblems = [] if name in {cover_page_name, cover_image_name}: emblems.append('default_cover.png') tooltips.append( _('This file is the cover %s for this book') % (_('image') if name == cover_image_name else _('page'))) if name in container.opf_name: emblems.append('metadata.png') tooltips.append( _('This file contains all the metadata and book structure information' )) if imt == ncx_mime: emblems.append('toc.png') tooltips.append( _('This file contains the metadata table of contents')) if name not in manifested_names and not container.ok_to_be_unmanifested( name): emblems.append('dialog_question.png') tooltips.append( _('This file is not listed in the book manifest')) if linear is False: emblems.append('arrow-down.png') tooltips.append( _('This file is marked as non-linear in the spine\nDrag it to the top to make it linear' )) if linear is None and icat == 'text': # Text item outside spine emblems.append('dialog_warning.png') tooltips.append( _('This file is a text file that is not referenced in the spine' )) if category == 'text' and name in processed: # Duplicate entry in spine emblems.append('dialog_error.png') tooltips.append( _('This file occurs more than once in the spine')) render_emblems(item, emblems) if tooltips: item.setData(0, Qt.ToolTipRole, '\n'.join(tooltips)) return item for name, linear in container.spine_names: processed[name] = create_item(name, linear=linear) for name in container.name_path_map: if name in processed: continue processed[name] = create_item(name) for name, c in self.categories.iteritems(): c.setExpanded(True) if name != 'text': c.sortChildren(1, Qt.AscendingOrder) if preserve_state: self.set_state(state) if self.current_edited_name: item = self.item_from_name(self.current_edited_name) if item is not None: self.mark_item_as_current(item)
def build(self, container, preserve_state=True): if preserve_state: state = self.get_state() self.clear() self.root = self.invisibleRootItem() self.root.setFlags(Qt.ItemIsDragEnabled) self.categories = {} for category, text in ( ("text", _("Text")), ("styles", _("Styles")), ("images", _("Images")), ("fonts", _("Fonts")), ("misc", _("Miscellaneous")), ): self.categories[category] = i = QTreeWidgetItem(self.root, 0) i.setText(0, text) i.setData(0, Qt.DecorationRole, self.top_level_pixmap_cache[category]) f = i.font(0) f.setBold(True) i.setFont(0, f) i.setData(0, NAME_ROLE, category) flags = Qt.ItemIsEnabled if category == "text": flags |= Qt.ItemIsDropEnabled i.setFlags(flags) processed, seen = {}, {} cover_page_name = get_cover_page_name(container) cover_image_name = get_raster_cover_name(container) manifested_names = set() for names in container.manifest_type_map.itervalues(): manifested_names |= set(names) font_types = {guess_type("a." + x) for x in ("ttf", "otf", "woff")} def get_category(name, mt): category = "misc" if mt.startswith("image/"): category = "images" elif mt in font_types: category = "fonts" elif mt in OEB_STYLES: category = "styles" elif mt in OEB_DOCS: category = "text" ext = name.rpartition(".")[-1].lower() if ext in {"ttf", "otf", "woff"}: # Probably wrong mimetype in the OPF category = "fonts" return category def set_display_name(name, item): if name in processed: # We have an exact duplicate (can happen if there are # duplicates in the spine) item.setText(0, processed[name].text(0)) item.setText(1, processed[name].text(1)) return parts = name.split("/") text = parts[-1] while text in seen and parts: text = parts.pop() + "/" + text seen[text] = item item.setText(0, text) item.setText(1, hexlify(sort_key(text))) def render_emblems(item, emblems): emblems = tuple(emblems) if not emblems: return icon = self.rendered_emblem_cache.get(emblems, None) if icon is None: pixmaps = [] for emblem in emblems: pm = self.emblem_cache.get(emblem, None) if pm is None: pm = self.emblem_cache[emblem] = QPixmap(I(emblem)).scaled( self.iconSize(), transformMode=Qt.SmoothTransformation ) pixmaps.append(pm) num = len(pixmaps) w, h = pixmaps[0].width(), pixmaps[0].height() if num == 1: icon = self.rendered_emblem_cache[emblems] = QIcon(pixmaps[0]) else: canvas = QPixmap((num * w) + ((num - 1) * 2), h) canvas.fill(Qt.transparent) painter = QPainter(canvas) for i, pm in enumerate(pixmaps): painter.drawPixmap(i * (w + 2), 0, pm) painter.end() icon = self.rendered_emblem_cache[emblems] = canvas item.setData(0, Qt.DecorationRole, icon) ok_to_be_unmanifested = container.names_that_need_not_be_manifested cannot_be_renamed = container.names_that_must_not_be_changed def create_item(name, linear=None): imt = container.mime_map.get(name, guess_type(name)) icat = get_category(name, imt) category = "text" if linear is not None else ({"text": "misc"}.get(icat, icat)) item = QTreeWidgetItem(self.categories["text" if linear is not None else category], 1) flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if category == "text": flags |= Qt.ItemIsDragEnabled if name not in cannot_be_renamed: flags |= Qt.ItemIsEditable item.setFlags(flags) item.setStatusTip(0, _("Full path: ") + name) item.setData(0, NAME_ROLE, name) item.setData(0, CATEGORY_ROLE, category) item.setData(0, LINEAR_ROLE, bool(linear)) item.setData(0, MIME_ROLE, imt) set_display_name(name, item) # TODO: Add appropriate tooltips based on the emblems emblems = [] if name in {cover_page_name, cover_image_name}: emblems.append("default_cover.png") if name not in manifested_names and name not in ok_to_be_unmanifested: emblems.append("dialog_question.png") if linear is False: emblems.append("arrow-down.png") if linear is None and icat == "text": # Text item outside spine emblems.append("dialog_warning.png") if category == "text" and name in processed: # Duplicate entry in spine emblems.append("dialog_warning.png") render_emblems(item, emblems) return item for name, linear in container.spine_names: processed[name] = create_item(name, linear=linear) all_files = list(container.manifest_type_map.iteritems()) all_files.append((guess_type("a.opf"), [container.opf_name])) for name in container.name_path_map: if name in processed: continue processed[name] = create_item(name) for name, c in self.categories.iteritems(): c.setExpanded(True) if name != "text": c.sortChildren(1, Qt.AscendingOrder) if preserve_state: self.set_state(state)
def create_book(mi, path, fmt='epub', opf_name='metadata.opf', html_name='start.xhtml', toc_name='toc.ncx'): ''' Create an empty book in the specified format at the specified location. ''' path = os.path.abspath(path) lang = 'und' opf = metadata_to_opf(mi, as_string=False) for l in opf.xpath('//*[local-name()="language"]'): if l.text: lang = l.text break lang = lang_as_iso639_1(lang) or lang opfns = OPF_NAMESPACES['opf'] m = opf.makeelement('{%s}manifest' % opfns) opf.insert(1, m) i = m.makeelement('{%s}item' % opfns, href=html_name, id='start') i.set('media-type', guess_type('a.xhtml')) m.append(i) i = m.makeelement('{%s}item' % opfns, href=toc_name, id='ncx') i.set('media-type', guess_type(toc_name)) m.append(i) s = opf.makeelement('{%s}spine' % opfns, toc="ncx") opf.insert(2, s) i = s.makeelement('{%s}itemref' % opfns, idref='start') s.append(i) CONTAINER = '''\ <?xml version="1.0"?> <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> <rootfiles> <rootfile full-path="{0}" media-type="application/oebps-package+xml"/> </rootfiles> </container> '''.format(prepare_string_for_xml(opf_name, True)).encode('utf-8') HTML = '''\ <?xml version='1.0' encoding='utf-8'?> <html lang="{1}" xmlns="http://www.w3.org/1999/xhtml"> <head> <title>{0}</title> </head> <body> <h1>{0}</h1> </body> </html> '''.format(prepare_string_for_xml(mi.title), lang).encode('utf-8') ncx = etree.tostring(create_toc(mi, opf, html_name, lang), encoding='utf-8', xml_declaration=True, pretty_print=True) pretty_xml_tree(opf) opf = etree.tostring(opf, encoding='utf-8', xml_declaration=True, pretty_print=True) if fmt == 'azw3': with TemporaryDirectory('create-azw3') as tdir, CurrentDir(tdir): for name, data in ((opf_name, opf), (html_name, HTML), (toc_name, ncx)): with open(name, 'wb') as f: f.write(data) opf_to_azw3(opf_name, path, DevNull()) else: with ZipFile(path, 'w', compression=ZIP_STORED) as zf: zf.writestr('mimetype', b'application/epub+zip', compression=ZIP_STORED) zf.writestr('META-INF/', b'', 0755) zf.writestr('META-INF/container.xml', CONTAINER) zf.writestr(opf_name, opf) zf.writestr(html_name, HTML) zf.writestr(toc_name, ncx)
def image_activated(self, name): mt = current_container().mime_map.get(name, guess_type(name)) self.edit_file_requested(name, None, mt)
#!/usr/bin/env python2 # vim:fileencoding=utf-8 from __future__ import absolute_import, division, print_function, unicode_literals __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' from PyQt5.Qt import QTextCharFormat from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES from calibre.ebooks.oeb.polish.container import guess_type _xml_types = {'application/oebps-page-map+xml', 'application/vnd.adobe-page-template+xml', 'application/page-template+xml'} | { guess_type('a.'+x) for x in ('ncx', 'opf', 'svg', 'xpgt', 'xml')} _js_types = {'application/javascript', 'application/x-javascript'} def syntax_from_mime(name, mime): for syntax, types in (('html', OEB_DOCS), ('css', OEB_STYLES), ('xml', _xml_types)): if mime in types: return syntax if mime in _js_types: return 'javascript' if mime.startswith('text/'): return 'text' if mime.startswith('image/') and mime.partition('/')[-1].lower() in { 'jpeg', 'jpg', 'gif', 'png'}: return 'raster_image' if mime.endswith('+xml'): return 'xml'
def subset_all_fonts(container, font_stats, report): remove = set() total_old = total_new = 0 for name, mt in container.mime_map.iteritems(): if (mt in OEB_FONTS or name.rpartition('.')[-1].lower() in {'otf', 'ttf'}) and mt != guess_type('a.woff'): chars = font_stats.get(name, set()) path = container.name_path_map[name] total_old += os.path.getsize(path) if not chars: remove.add(name) report('Removed unused font: %s'%name) continue with open(path, 'r+b') as f: raw = f.read() font_name = get_font_names(raw)[-1] warnings = [] container.log('Subsetting font: %s'%(font_name or name)) try: nraw, old_sizes, new_sizes = subset(raw, chars, warnings=warnings) except UnsupportedFont as e: container.log.warning( 'Unsupported font: %s, ignoring. Error: %s'%( name, as_unicode(e))) continue for w in warnings: container.log.warn(w) olen = sum(old_sizes.itervalues()) nlen = sum(new_sizes.itervalues()) total_new += len(nraw) if nlen == olen: report('The font %s was already subset'%font_name) else: report('Decreased the font %s to %.1f%% of its original size'% (font_name, nlen/olen * 100)) f.seek(0), f.truncate(), f.write(nraw) for name in remove: container.remove_item(name) if remove: for name, mt in container.mime_map.iteritems(): if mt in OEB_STYLES: sheet = container.parsed(name) if remove_font_face_rules(container, sheet, remove, name): container.dirty(name) elif mt in OEB_DOCS: for style in XPath('//h:style')(container.parsed(name)): if style.get('type', 'text/css') == 'text/css' and style.text: sheet = container.parse_css(style.text, name) if remove_font_face_rules(container, sheet, remove, name): style.text = sheet.cssText container.dirty(name) if total_old > 0: report('Reduced total font size to %.1f%% of original'%( total_new/total_old*100)) else: report('No embedded fonts found')
#!/usr/bin/env python # vim:fileencoding=utf-8 __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' from qt.core import QTextCharFormat, QTextFormat from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES from calibre.ebooks.oeb.polish.container import guess_type _xml_types = {'application/oebps-page-map+xml', 'application/vnd.adobe-page-template+xml', 'application/page-template+xml'} | { guess_type('a.'+x) for x in ('ncx', 'opf', 'svg', 'xpgt', 'xml')} _js_types = {'application/javascript', 'application/x-javascript'} def syntax_from_mime(name, mime): for syntax, types in (('html', OEB_DOCS), ('css', OEB_STYLES), ('xml', _xml_types)): if mime in types: return syntax if mime in _js_types: return 'javascript' if mime.startswith('text/'): return 'text' if mime.startswith('image/') and mime.partition('/')[-1].lower() in { 'jpeg', 'jpg', 'gif', 'png', 'webp'}: return 'raster_image' if mime.endswith('+xml'): return 'xml'
def build(self, container, preserve_state=True): if container is None: return if preserve_state: state = self.get_state() self.clear() self.root = self.invisibleRootItem() self.root.setFlags(Qt.ItemIsDragEnabled) self.categories = {} for category, text, __ in CATEGORIES: self.categories[category] = i = QTreeWidgetItem(self.root, 0) i.setText(0, text) i.setData(0, Qt.DecorationRole, self.top_level_pixmap_cache[category]) f = i.font(0) f.setBold(True) i.setFont(0, f) i.setData(0, NAME_ROLE, category) flags = Qt.ItemIsEnabled if category == 'text': flags |= Qt.ItemIsDropEnabled i.setFlags(flags) processed, seen = {}, {} cover_page_name = get_cover_page_name(container) cover_image_name = get_raster_cover_name(container) manifested_names = set() for names in container.manifest_type_map.itervalues(): manifested_names |= set(names) def get_category(name, mt): category = 'misc' if mt.startswith('image/'): category = 'images' elif mt in OEB_FONTS: category = 'fonts' elif mt in OEB_STYLES: category = 'styles' elif mt in OEB_DOCS: category = 'text' ext = name.rpartition('.')[-1].lower() if ext in {'ttf', 'otf', 'woff'}: # Probably wrong mimetype in the OPF category = 'fonts' return category def set_display_name(name, item): if tprefs['file_list_shows_full_pathname']: text = name else: if name in processed: # We have an exact duplicate (can happen if there are # duplicates in the spine) item.setText(0, processed[name].text(0)) item.setText(1, processed[name].text(1)) return parts = name.split('/') text = parts.pop() while text in seen and parts: text = parts.pop() + '/' + text seen[text] = item item.setText(0, text) item.setText(1, hexlify(sort_key(text))) def render_emblems(item, emblems): emblems = tuple(emblems) if not emblems: return icon = self.rendered_emblem_cache.get(emblems, None) if icon is None: pixmaps = [] for emblem in emblems: pm = self.emblem_cache.get(emblem, None) if pm is None: pm = self.emblem_cache[emblem] = QIcon(I(emblem)).pixmap(self.iconSize()) pixmaps.append(pm) num = len(pixmaps) w, h = pixmaps[0].width(), pixmaps[0].height() if num == 1: icon = self.rendered_emblem_cache[emblems] = QIcon(pixmaps[0]) else: canvas = QPixmap((num * w) + ((num-1)*2), h) canvas.setDevicePixelRatio(pixmaps[0].devicePixelRatio()) canvas.fill(Qt.transparent) painter = QPainter(canvas) for i, pm in enumerate(pixmaps): painter.drawPixmap(int(i * (w + 2)/canvas.devicePixelRatio()), 0, pm) painter.end() icon = self.rendered_emblem_cache[emblems] = canvas item.setData(0, Qt.DecorationRole, icon) cannot_be_renamed = container.names_that_must_not_be_changed ncx_mime = guess_type('a.ncx') nav_items = frozenset(container.manifest_items_with_property('nav')) def create_item(name, linear=None): imt = container.mime_map.get(name, guess_type(name)) icat = get_category(name, imt) category = 'text' if linear is not None else ({'text':'misc'}.get(icat, icat)) item = QTreeWidgetItem(self.categories['text' if linear is not None else category], 1) flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if category == 'text': flags |= Qt.ItemIsDragEnabled if name not in cannot_be_renamed: flags |= Qt.ItemIsEditable item.setFlags(flags) item.setStatusTip(0, _('Full path: ') + name) item.setData(0, NAME_ROLE, name) item.setData(0, CATEGORY_ROLE, category) item.setData(0, LINEAR_ROLE, bool(linear)) item.setData(0, MIME_ROLE, imt) set_display_name(name, item) tooltips = [] emblems = [] if name in {cover_page_name, cover_image_name}: emblems.append('default_cover.png') tooltips.append(_('This file is the cover %s for this book') % (_('image') if name == cover_image_name else _('page'))) if name in container.opf_name: emblems.append('metadata.png') tooltips.append(_('This file contains all the metadata and book structure information')) if imt == ncx_mime or name in nav_items: emblems.append('toc.png') tooltips.append(_('This file contains the metadata table of contents')) if name not in manifested_names and not container.ok_to_be_unmanifested(name): emblems.append('dialog_question.png') tooltips.append(_('This file is not listed in the book manifest')) if linear is False: emblems.append('arrow-down.png') tooltips.append(_('This file is marked as non-linear in the spine\nDrag it to the top to make it linear')) if linear is None and icat == 'text': # Text item outside spine emblems.append('dialog_warning.png') tooltips.append(_('This file is a text file that is not referenced in the spine')) if category == 'text' and name in processed: # Duplicate entry in spine emblems.append('dialog_error.png') tooltips.append(_('This file occurs more than once in the spine')) render_emblems(item, emblems) if tooltips: item.setData(0, Qt.ToolTipRole, '\n'.join(tooltips)) return item for name, linear in container.spine_names: processed[name] = create_item(name, linear=linear) for name in container.name_path_map: if name in processed: continue processed[name] = create_item(name) for name, c in self.categories.iteritems(): c.setExpanded(True) if name != 'text': c.sortChildren(1, Qt.AscendingOrder) if preserve_state: self.set_state(state) if self.current_edited_name: item = self.item_from_name(self.current_edited_name) if item is not None: self.mark_item_as_current(item)
#!/usr/bin/env python # vim:fileencoding=utf-8 __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' from qt.core import QTextCharFormat, QTextFormat from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES from calibre.ebooks.oeb.polish.container import guess_type _xml_types = { 'application/oebps-page-map+xml', 'application/vnd.adobe-page-template+xml', 'application/page-template+xml' } | {guess_type('a.' + x) for x in ('ncx', 'opf', 'svg', 'xpgt', 'xml')} _js_types = {'application/javascript', 'application/x-javascript'} def syntax_from_mime(name, mime): for syntax, types in (('html', OEB_DOCS), ('css', OEB_STYLES), ('xml', _xml_types)): if mime in types: return syntax if mime in _js_types: return 'javascript' if mime.startswith('text/'): return 'text' if mime.startswith('image/') and mime.partition('/')[-1].lower() in { 'jpeg', 'jpg', 'gif', 'png' }:
def subset_all_fonts(container, font_stats, report): remove = set() total_old = total_new = 0 for name, mt in container.mime_map.iteritems(): if (mt in OEB_FONTS or name.rpartition('.')[-1].lower() in {'otf', 'ttf'}) and mt != guess_type('a.woff'): chars = font_stats.get(name, set()) path = container.name_path_map[name] total_old += os.path.getsize(path) if not chars: remove.add(name) report('Removed unused font: %s' % name) continue with open(path, 'r+b') as f: raw = f.read() font_name = get_font_names(raw)[-1] warnings = [] container.log('Subsetting font: %s' % (font_name or name)) try: nraw, old_sizes, new_sizes = subset(raw, chars, warnings=warnings) except UnsupportedFont as e: container.log.warning( 'Unsupported font: %s, ignoring. Error: %s' % (name, as_unicode(e))) continue for w in warnings: container.log.warn(w) olen = sum(old_sizes.itervalues()) nlen = sum(new_sizes.itervalues()) total_new += len(nraw) if nlen == olen: report('The font %s was already subset' % font_name) else: report( 'Decreased the font %s to %.1f%% of its original size' % (font_name, nlen / olen * 100)) f.seek(0), f.truncate(), f.write(nraw) for name in remove: container.remove_item(name) if remove: for name, mt in container.mime_map.iteritems(): if mt in OEB_STYLES: sheet = container.parsed(name) if remove_font_face_rules(container, sheet, remove, name): container.dirty(name) elif mt in OEB_DOCS: for style in XPath('//h:style')(container.parsed(name)): if style.get('type', 'text/css') == 'text/css' and style.text: sheet = container.parse_css(style.text, name) if remove_font_face_rules(container, sheet, remove, name): style.text = sheet.cssText container.dirty(name) if total_old > 0: report('Reduced total font size to %.1f%% of original' % (total_new / total_old * 100)) else: report('No embedded fonts found')