Пример #1
0
 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
Пример #2
0
 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)
Пример #3
0
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
Пример #4
0
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
Пример #5
0
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'
Пример #6
0
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'
Пример #7
0
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"
Пример #8
0
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'
Пример #9
0
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'
Пример #10
0
    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)
Пример #11
0
        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
Пример #12
0
 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)
Пример #13
0
    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)
Пример #14
0
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
Пример #15
0
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
Пример #16
0
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)
Пример #17
0
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)
Пример #18
0
        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
Пример #19
0
        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
Пример #20
0
 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,
     )
Пример #21
0
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
Пример #22
0
 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
Пример #23
0
 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
Пример #24
0
 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
Пример #25
0
 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})
Пример #26
0
 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)
Пример #27
0
        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
Пример #28
0
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)
Пример #29
0
 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)
Пример #30
0
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
Пример #31
0
 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)
Пример #32
0
        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
Пример #33
0
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}
Пример #34
0
 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)
Пример #35
0
    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)
Пример #36
0
    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)
Пример #37
0
    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)
Пример #38
0
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)
Пример #39
0
 def image_activated(self, name):
     mt = current_container().mime_map.get(name, guess_type(name))
     self.edit_file_requested(name, None, mt)
Пример #40
0
#!/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'
Пример #41
0
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')
Пример #42
0
#!/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'
Пример #43
0
    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)
Пример #44
0
#!/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'
    }:
Пример #45
0
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')