mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
EPUB Output: Generate inline ToC
EPUB Output: Add an option to insert an inline Table of COntents into the main text. Fixes #1201006 [Epub output: not possible to insert inline toc](https://bugs.launchpad.net/calibre/+bug/1201006)
This commit is contained in:
parent
dc97d6ad69
commit
12cded043f
@ -105,14 +105,23 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
' EPUB, putting all files into the top level.')
|
||||
),
|
||||
|
||||
OptionRecommendation(name='epub_inline_toc', recommended_value=False,
|
||||
help=_('Insert an inline Table of Contents that will appear as part of the main book content.')
|
||||
),
|
||||
|
||||
OptionRecommendation(name='epub_toc_at_end', recommended_value=False,
|
||||
help=_('Put the inserted inline Table of Contents at the end of the book instead of the start.')
|
||||
),
|
||||
|
||||
OptionRecommendation(name='toc_title', recommended_value=None,
|
||||
help=_('Title for any generated in-line table of contents.')
|
||||
),
|
||||
|
||||
])
|
||||
|
||||
recommendations = set([('pretty_print', True, OptionRecommendation.HIGH)])
|
||||
|
||||
|
||||
|
||||
def workaround_webkit_quirks(self): # {{{
|
||||
def workaround_webkit_quirks(self): # {{{
|
||||
from calibre.ebooks.oeb.base import XPath
|
||||
for x in self.oeb.spine:
|
||||
root = x.data
|
||||
@ -128,13 +137,13 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
pre.tag = 'div'
|
||||
# }}}
|
||||
|
||||
def upshift_markup(self): # {{{
|
||||
def upshift_markup(self): # {{{
|
||||
'Upgrade markup to comply with XHTML 1.1 where possible'
|
||||
from calibre.ebooks.oeb.base import XPath, XML
|
||||
for x in self.oeb.spine:
|
||||
root = x.data
|
||||
if (not root.get(XML('lang'))) and (root.get('lang')):
|
||||
root.set(XML('lang'), root.get('lang'))
|
||||
root.set(XML('lang'), root.get('lang'))
|
||||
body = XPath('//h:body')(root)
|
||||
if body:
|
||||
body = body[0]
|
||||
@ -159,12 +168,17 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
else:
|
||||
seen_names.add(name)
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
def convert(self, oeb, output_path, input_plugin, opts, log):
|
||||
self.log, self.opts, self.oeb = log, opts, oeb
|
||||
|
||||
if self.opts.epub_inline_toc:
|
||||
from calibre.ebooks.mobi.writer8.toc import TOCAdder
|
||||
opts.mobi_toc_at_start = not opts.epub_toc_at_end
|
||||
opts.mobi_passthrough = False
|
||||
opts.no_inline_toc = False
|
||||
TOCAdder(oeb, opts, replace_previous_inline_toc=True, ignore_existing_toc=True)
|
||||
|
||||
if self.opts.epub_flatten:
|
||||
from calibre.ebooks.oeb.transforms.filenames import FlatFilenames
|
||||
FlatFilenames()(oeb, opts)
|
||||
@ -234,7 +248,7 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
oeb_output = plugin_for_output_format('oeb')
|
||||
oeb_output.convert(oeb, tdir, input_plugin, opts, log)
|
||||
opf = [x for x in os.listdir(tdir) if x.endswith('.opf')][0]
|
||||
self.condense_ncx([os.path.join(tdir, x) for x in os.listdir(tdir)\
|
||||
self.condense_ncx([os.path.join(tdir, x) for x in os.listdir(tdir)
|
||||
if x.endswith('.ncx')][0])
|
||||
encryption = None
|
||||
if encrypted_fonts:
|
||||
@ -261,7 +275,7 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
zf.extractall(path=opts.extract_to)
|
||||
self.log.info('EPUB extracted to', opts.extract_to)
|
||||
|
||||
def encrypt_fonts(self, uris, tdir, uuid): # {{{
|
||||
def encrypt_fonts(self, uris, tdir, uuid): # {{{
|
||||
from binascii import unhexlify
|
||||
|
||||
key = re.sub(r'[^a-fA-F0-9]', '', uuid)
|
||||
@ -301,14 +315,14 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
</enc:EncryptedData>
|
||||
'''%(uri.replace('"', '\\"')))
|
||||
if fonts:
|
||||
ans = '''<encryption
|
||||
ans = '''<encryption
|
||||
xmlns="urn:oasis:names:tc:opendocument:xmlns:container"
|
||||
xmlns:enc="http://www.w3.org/2001/04/xmlenc#"
|
||||
xmlns:deenc="http://ns.adobe.com/digitaleditions/enc">
|
||||
'''
|
||||
ans += (u'\n'.join(fonts)).encode('utf-8')
|
||||
ans += '\n</encryption>'
|
||||
return ans
|
||||
ans += (u'\n'.join(fonts)).encode('utf-8')
|
||||
ans += '\n</encryption>'
|
||||
return ans
|
||||
# }}}
|
||||
|
||||
def condense_ncx(self, ncx_path):
|
||||
@ -323,7 +337,7 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
compressed = etree.tostring(tree.getroot(), encoding='utf-8')
|
||||
open(ncx_path, 'wb').write(compressed)
|
||||
|
||||
def workaround_ade_quirks(self): # {{{
|
||||
def workaround_ade_quirks(self): # {{{
|
||||
'''
|
||||
Perform various markup transforms to get the output to render correctly
|
||||
in the quirky ADE.
|
||||
@ -462,7 +476,7 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
|
||||
# }}}
|
||||
|
||||
def workaround_sony_quirks(self): # {{{
|
||||
def workaround_sony_quirks(self): # {{{
|
||||
'''
|
||||
Perform toc link transforms to alleviate slow loading.
|
||||
'''
|
||||
|
@ -34,9 +34,17 @@ TEMPLATE = '''
|
||||
</html>
|
||||
'''
|
||||
|
||||
def find_previous_calibre_inline_toc(oeb):
|
||||
if 'toc' in oeb.guide:
|
||||
href = urlnormalize(oeb.guide['toc'].href.partition('#')[0])
|
||||
if href in oeb.manifest.hrefs:
|
||||
item = oeb.manifest.hrefs[href]
|
||||
if (hasattr(item.data, 'xpath') and XPath('//h:body[@id="calibre_generated_inline_toc"]')(item.data)):
|
||||
return item
|
||||
|
||||
class TOCAdder(object):
|
||||
|
||||
def __init__(self, oeb, opts):
|
||||
def __init__(self, oeb, opts, replace_previous_inline_toc=False, ignore_existing_toc=False):
|
||||
self.oeb, self.opts, self.log = oeb, opts, oeb.log
|
||||
self.title = opts.toc_title or DEFAULT_TITLE
|
||||
self.at_start = opts.mobi_toc_at_start
|
||||
@ -44,6 +52,12 @@ class TOCAdder(object):
|
||||
self.added_toc_guide_entry = False
|
||||
self.has_toc = oeb.toc and oeb.toc.count() > 1
|
||||
|
||||
self.tocitem = tocitem = None
|
||||
if find_previous_calibre_inline_toc:
|
||||
tocitem = self.tocitem = find_previous_calibre_inline_toc(oeb)
|
||||
if ignore_existing_toc and 'toc' in oeb.guide:
|
||||
oeb.guide.remove('toc')
|
||||
|
||||
if 'toc' in oeb.guide:
|
||||
# Remove spurious toc entry from guide if it is not in spine or it
|
||||
# does not have any hyperlinks
|
||||
@ -81,13 +95,19 @@ class TOCAdder(object):
|
||||
for child in self.oeb.toc:
|
||||
self.process_toc_node(child, parent)
|
||||
|
||||
id, href = oeb.manifest.generate('contents', 'contents.xhtml')
|
||||
item = self.generated_item = oeb.manifest.add(id, href, XHTML_MIME,
|
||||
data=root)
|
||||
if self.at_start:
|
||||
oeb.spine.insert(0, item, linear=True)
|
||||
if tocitem is not None:
|
||||
href = tocitem.href
|
||||
if oeb.spine.index(tocitem) > -1:
|
||||
oeb.spine.remove(tocitem)
|
||||
tocitem.data = root
|
||||
else:
|
||||
oeb.spine.add(item, linear=False)
|
||||
id, href = oeb.manifest.generate('contents', 'contents.xhtml')
|
||||
tocitem = self.generated_item = oeb.manifest.add(id, href, XHTML_MIME,
|
||||
data=root)
|
||||
if self.at_start:
|
||||
oeb.spine.insert(0, tocitem, linear=True)
|
||||
else:
|
||||
oeb.spine.add(tocitem, linear=False)
|
||||
|
||||
oeb.guide.add('toc', 'Table of Contents', href)
|
||||
|
||||
@ -95,7 +115,10 @@ class TOCAdder(object):
|
||||
li = parent.makeelement(XHTML('li'))
|
||||
li.tail = '\n'+ ('\t'*level)
|
||||
parent.append(li)
|
||||
a = parent.makeelement(XHTML('a'), href=toc.href or '#')
|
||||
href = toc.href
|
||||
if self.tocitem is not None and href:
|
||||
href = self.tocitem.relhref(toc.href)
|
||||
a = parent.makeelement(XHTML('a'), href=href or '#')
|
||||
a.text = toc.title
|
||||
li.append(a)
|
||||
if toc.count() > 0:
|
||||
@ -115,3 +138,4 @@ class TOCAdder(object):
|
||||
self.oeb.guide.remove('toc')
|
||||
self.added_toc_guide_entry = False
|
||||
|
||||
|
||||
|
@ -21,6 +21,7 @@ class PluginWidget(Widget, Ui_Form):
|
||||
Widget.__init__(self, parent,
|
||||
['dont_split_on_page_breaks', 'flow_size',
|
||||
'no_default_epub_cover', 'no_svg_cover',
|
||||
'epub_inline_toc', 'epub_toc_at_end', 'toc_title',
|
||||
'preserve_cover_aspect_ratio', 'epub_flatten']
|
||||
)
|
||||
for i in range(2):
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<width>644</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -14,27 +14,6 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="opt_dont_split_on_page_breaks">
|
||||
<property name="text">
|
||||
<string>Do not &split on page breaks</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="opt_no_default_epub_cover">
|
||||
<property name="text">
|
||||
<string>No default &cover</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="opt_no_svg_cover">
|
||||
<property name="text">
|
||||
<string>No &SVG cover</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="opt_preserve_cover_aspect_ratio">
|
||||
<property name="text">
|
||||
@ -42,7 +21,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Split files &larger than:</string>
|
||||
@ -52,7 +31,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="opt_flow_size">
|
||||
<property name="suffix">
|
||||
<string> KB</string>
|
||||
@ -68,7 +47,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="6" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@ -81,6 +60,41 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="opt_no_default_epub_cover">
|
||||
<property name="text">
|
||||
<string>No default &cover</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="opt_no_svg_cover">
|
||||
<property name="text">
|
||||
<string>No &SVG cover</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="opt_epub_inline_toc">
|
||||
<property name="text">
|
||||
<string>Insert inline &Table of Contents</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_dont_split_on_page_breaks">
|
||||
<property name="text">
|
||||
<string>Do not &split on page breaks</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="opt_epub_toc_at_end">
|
||||
<property name="text">
|
||||
<string>Put inserted Table of Contents at the &end of the book</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="opt_epub_flatten">
|
||||
<property name="text">
|
||||
@ -88,6 +102,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&Title for inserted ToC:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_toc_title</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="opt_toc_title"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user