diff --git a/src/calibre/ebooks/oeb/polish/jacket.py b/src/calibre/ebooks/oeb/polish/jacket.py index 8ae65f3f9f..ee795c0011 100644 --- a/src/calibre/ebooks/oeb/polish/jacket.py +++ b/src/calibre/ebooks/oeb/polish/jacket.py @@ -11,14 +11,26 @@ from calibre.customize.ui import output_profiles from calibre.ebooks.conversion.config import load_defaults from calibre.ebooks.oeb.base import XPath, OPF from calibre.ebooks.oeb.polish.cover import find_cover_page -from calibre.ebooks.oeb.transforms.jacket import render_jacket as render +from calibre.ebooks.oeb.transforms.jacket import render_jacket as render, referenced_images -def render_jacket(mi): +def render_jacket(container, jacket): + mi = container.mi ps = load_defaults('page_setup') op = ps.get('output_profile', 'default') opmap = {x.short_name:x for x in output_profiles()} output_profile = opmap.get(op, opmap['default']) - return render(mi, output_profile) + root = render(mi, output_profile) + for img, path in referenced_images(root): + container.log('Embedding referenced image: %s into jacket' % path) + ext = path.rpartition('.')[-1] + jacket_item = container.generate_item('jacket_image.'+ext, id_prefix='jacket_img') + name = container.href_to_name(jacket_item.get('href'), container.opf_name) + with open(path, 'rb') as f: + container.parsed_cache[name] = f.read() + container.commit_item(name) + href = container.name_to_href(name, jacket) + img.set('src', href) + return root def is_legacy_jacket(root): return len(root.xpath( @@ -42,17 +54,25 @@ def find_existing_jacket(container): return name def replace_jacket(container, name): - root = render_jacket(container.mi) + root = render_jacket(container, name) container.parsed_cache[name] = root container.dirty(name) def remove_jacket(container): name = find_existing_jacket(container) if name is not None: + remove_jacket_images(container, name) container.remove_item(name) return True return False +def remove_jacket_images(container, name): + root = container.parsed_cache[name] + for img in root.xpath('//*[local-name() = "img" and @src]'): + iname = container.href_to_name(img.get('src'), name) + if container.has_name(iname): + container.remove_item(iname) + def add_or_replace_jacket(container): name = find_existing_jacket(container) found = True @@ -60,6 +80,9 @@ def add_or_replace_jacket(container): jacket_item = container.generate_item('jacket.xhtml', id_prefix='jacket') name = container.href_to_name(jacket_item.get('href'), container.opf_name) found = False + if found: + remove_jacket_images(container, name) + replace_jacket(container, name) if not found: # Insert new jacket into spine diff --git a/src/calibre/ebooks/oeb/transforms/jacket.py b/src/calibre/ebooks/oeb/transforms/jacket.py index 02abda0927..a9cc10dc3d 100644 --- a/src/calibre/ebooks/oeb/transforms/jacket.py +++ b/src/calibre/ebooks/oeb/transforms/jacket.py @@ -6,12 +6,13 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import sys +import sys, os from xml.sax.saxutils import escape from lxml import etree from calibre import guess_type, strftime +from calibre.constants import iswindows from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML, xml2text, urldefrag from calibre.library.comments import comments_to_html @@ -84,9 +85,17 @@ class Jacket(object): alt_comments=comments) id, href = self.oeb.manifest.generate('calibre_jacket', 'jacket.xhtml') - item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root) - self.oeb.spine.insert(0, item, True) - self.oeb.inserted_metadata_jacket = item + jacket = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root) + self.oeb.spine.insert(0, jacket, True) + self.oeb.inserted_metadata_jacket = jacket + for img, path in referenced_images(root): + self.oeb.log('Embedding referenced image %s into jacket' % path) + ext = path.rpartition('.')[-1].lower() + item_id, href = self.oeb.manifest.generate('jacket_image', 'jacket_img.'+ext) + with open(path, 'rb') as f: + item = self.oeb.manifest.add(item_id, href, guess_type(href)[0], data=f.read()) + item.unload_data_from_memory() + img.set('src', jacket.relhref(item.href)) def remove_existing_jacket(self): for x in self.oeb.spine[:4]: @@ -262,3 +271,13 @@ def linearize_jacket(oeb): e.tag = XHTML('span') break +def referenced_images(root): + for img in XPath('//h:img[@src]')(root): + src = img.get('src') + if src.startswith('file://'): + path = src[7:] + if iswindows and path.startswith('/'): + path = path[1:] + if os.path.exists(path): + yield img, path + diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index ac8ab13d20..921c8676e9 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -12,15 +12,16 @@ import sip from PyQt4.Qt import (QApplication, QFontInfo, QSize, QWidget, QPlainTextEdit, QToolBar, QVBoxLayout, QAction, QIcon, Qt, QTabWidget, QUrl, QFormLayout, - QSyntaxHighlighter, QColor, QChar, QColorDialog, QMenu, QDialog, - QHBoxLayout, QKeySequence, QLineEdit, QDialogButtonBox) + QSyntaxHighlighter, QColor, QChar, QColorDialog, QMenu, QDialog, QLabel, + QHBoxLayout, QKeySequence, QLineEdit, QDialogButtonBox, QPushButton) from PyQt4.QtWebKit import QWebView, QWebPage from calibre.ebooks.chardet import xml_to_unicode from calibre import xml_replace_entities, prepare_string_for_xml -from calibre.gui2 import open_url, error_dialog +from calibre.gui2 import open_url, error_dialog, choose_files from calibre.utils.soupparser import fromstring from calibre.utils.config import tweaks +from calibre.utils.imghdr import what class PageAction(QAction): # {{{ @@ -156,7 +157,7 @@ class EditorWidget(QWebView): # {{{ self.block_style_actions.append(ac) self.action_insert_link = QAction(QIcon(I('insert-link.png')), - _('Insert link'), self) + _('Insert link or image'), self) self.action_insert_link.triggered.connect(self.insert_link) self.pageAction(QWebPage.ToggleBold).changed.connect(self.update_link_action) self.action_insert_link.setEnabled(False) @@ -203,14 +204,18 @@ class EditorWidget(QWebView): # {{{ self.exec_command('hiliteColor', unicode(col.name())) def insert_link(self, *args): - link, name = self.ask_link() + link, name, is_image = self.ask_link() if not link: return - url = self.parse_link(unicode(link)) + url = self.parse_link(link) if url.isValid(): url = unicode(url.toString()) self.setFocus(Qt.OtherFocusReason) - if name: + if is_image: + self.exec_command('insertHTML', + '%s'%(prepare_string_for_xml(url, True), + prepare_string_for_xml(name or '', True))) + elif name: self.exec_command('insertHTML', '%s'%(prepare_string_for_xml(url, True), prepare_string_for_xml(name))) @@ -218,7 +223,7 @@ class EditorWidget(QWebView): # {{{ self.exec_command('createLink', url) else: error_dialog(self, _('Invalid URL'), - _('The url %r is invalid') % unicode(link), show=True) + _('The url %r is invalid') % link, show=True) def ask_link(self): d = QDialog(self) @@ -227,19 +232,43 @@ class EditorWidget(QWebView): # {{{ d.setLayout(l) d.url = QLineEdit(d) d.name = QLineEdit(d) + d.setMinimumWidth(600) d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) + d.br = b = QPushButton(_('&Browse')) + b.setIcon(QIcon(I('document_open.png'))) + def cf(): + files = choose_files(d, 'select link file', _('Choose file'), select_only_single_file=True) + if files: + d.url.setText(files[0]) + b.clicked.connect(cf) + d.la = la = QLabel(_( + 'Enter a URL. You can also choose to create a link to a file on ' + 'your computer. If the selected file is an image, it will be ' + 'inserted as an image. Note that if you create a link to a file on ' + 'your computer, it will stop working if the file is moved.')) + la.setWordWrap(True) + la.setStyleSheet('QLabel { margin-bottom: 1.5ex }') + l.setWidget(0, l.SpanningRole, la) l.addRow(_('Enter &URL:'), d.url) - l.addRow(_('Enter name (optional):'), d.name) + l.addRow(_('Enter &name (optional):'), d.name) + l.addRow(_('Choose a file on your computer:'), d.br) l.addRow(d.bb) d.bb.accepted.connect(d.accept) d.bb.rejected.connect(d.reject) - link, name = None, None + d.resize(d.sizeHint()) + link, name, is_image = None, None, False if d.exec_() == d.Accepted: link, name = unicode(d.url.text()).strip(), unicode(d.name.text()).strip() - return link, name + if link and os.path.exists(link): + with lopen(link, 'rb') as f: + q = what(f) + is_image = q in {'jpeg', 'png', 'gif'} + return link, name, is_image def parse_link(self, link): link = link.strip() + if link and os.path.exists(link): + return QUrl.fromLocalFile(link) has_schema = re.match(r'^[a-zA-Z]+:', link) if has_schema is not None: url = QUrl(link, QUrl.TolerantMode)