mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
PDF: Printable ToC
PDF Output: Add an option to generate a printable Table of Contents (that lists page numbers). Useful if you intend to print out the PDF.
This commit is contained in:
parent
5210324ff2
commit
ec81b1a07e
@ -808,3 +808,26 @@ the page will be used.
|
||||
bottom margins to large enough values, under the Page Setup section of the
|
||||
conversion dialog.
|
||||
|
||||
You can also insert a printable Table of Contents at the end of the PDF that
|
||||
lists the page numbers for every section. This is very useful if you intend to
|
||||
print out the PDF to paper. If you wish to use the PDF on an electronic device,
|
||||
then the PDF Outline provides this functionality and is generated by default.
|
||||
|
||||
You can customize the look of the the generated Table of contents by using the
|
||||
Extra CSS conversion setting under the Look & Feel part of the conversion
|
||||
dialog. The default css used is listed below, simply copy it and make whatever
|
||||
changes you like.
|
||||
|
||||
.. code-block:: css
|
||||
|
||||
.calibre-pdf-toc table { width: 100%% }
|
||||
|
||||
.calibre-pdf-toc table tr td:last-of-type { text-align: right }
|
||||
|
||||
.calibre-pdf-toc .level-0 {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.calibre-pdf-toc .level-1 td:first-of-type { padding-left: 1.4em }
|
||||
.calibre-pdf-toc .level-2 td:first-of-type { padding-left: 2.8em }
|
||||
|
||||
|
@ -29,7 +29,7 @@ class PDFMetadata(object): # {{{
|
||||
self.author = _(u'Unknown')
|
||||
self.tags = u''
|
||||
|
||||
if oeb_metadata != None:
|
||||
if oeb_metadata is not None:
|
||||
if len(oeb_metadata.title) >= 1:
|
||||
self.title = oeb_metadata.title[0].value
|
||||
if len(oeb_metadata.creator) >= 1:
|
||||
@ -109,6 +109,9 @@ class PDFOutput(OutputFormatPlugin):
|
||||
OptionRecommendation(name='pdf_header_template', recommended_value=None,
|
||||
help=_('An HTML template used to generate %s on every page.'
|
||||
' The strings _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_ will be replaced by their current values.')%_('headers')),
|
||||
OptionRecommendation(name='pdf_add_toc', recommended_value=False,
|
||||
help=_('Add a Table of Contents at the end of the PDF that lists page numbers. '
|
||||
'Useful if you want to print out the PDF. If this PDF is intended for electronic use, use the PDF Outline instead.')),
|
||||
])
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
@ -122,7 +125,6 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.metadata = oeb_book.metadata
|
||||
self.cover_data = None
|
||||
|
||||
|
||||
if input_plugin.is_image_collection:
|
||||
log.debug('Converting input as an image collection...')
|
||||
self.convert_images(input_plugin.get_images())
|
||||
@ -156,7 +158,8 @@ class PDFOutput(OutputFormatPlugin):
|
||||
# fonts to Qt
|
||||
family_map = {}
|
||||
for item in list(self.oeb.manifest):
|
||||
if not hasattr(item.data, 'cssRules'): continue
|
||||
if not hasattr(item.data, 'cssRules'):
|
||||
continue
|
||||
remove = set()
|
||||
for i, rule in enumerate(item.data.cssRules):
|
||||
if rule.type == rule.FONT_FACE_RULE:
|
||||
@ -195,11 +198,14 @@ class PDFOutput(OutputFormatPlugin):
|
||||
# family name of the embedded font (they may be different in general).
|
||||
font_warnings = set()
|
||||
for item in self.oeb.manifest:
|
||||
if not hasattr(item.data, 'cssRules'): continue
|
||||
if not hasattr(item.data, 'cssRules'):
|
||||
continue
|
||||
for i, rule in enumerate(item.data.cssRules):
|
||||
if rule.type != rule.STYLE_RULE: continue
|
||||
if rule.type != rule.STYLE_RULE:
|
||||
continue
|
||||
ff = rule.style.getProperty('font-family')
|
||||
if ff is None: continue
|
||||
if ff is None:
|
||||
continue
|
||||
val = ff.propertyValue
|
||||
for i in xrange(val.length):
|
||||
try:
|
||||
@ -279,3 +285,5 @@ class PDFOutput(OutputFormatPlugin):
|
||||
if close:
|
||||
out_stream.close()
|
||||
|
||||
|
||||
|
||||
|
@ -21,6 +21,7 @@ from calibre.ebooks.oeb.display.webview import load_html
|
||||
from calibre.ebooks.pdf.render.common import (inch, cm, mm, pica, cicero,
|
||||
didot, PAPER_SIZES)
|
||||
from calibre.ebooks.pdf.render.engine import PdfDevice
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
|
||||
def get_page_size(opts, for_comic=False): # {{{
|
||||
use_profile = not (opts.override_profile_size or
|
||||
@ -36,7 +37,7 @@ def get_page_size(opts, for_comic=False): # {{{
|
||||
page_size = (factor * w, factor * h)
|
||||
else:
|
||||
page_size = None
|
||||
if opts.custom_size != None:
|
||||
if opts.custom_size is not None:
|
||||
width, sep, height = opts.custom_size.partition('x')
|
||||
if height:
|
||||
try:
|
||||
@ -237,11 +238,23 @@ class PDFWriter(QObject):
|
||||
if self.doc.errors_occurred:
|
||||
raise Exception('PDF Output failed, see log for details')
|
||||
|
||||
def render_inline_toc(self):
|
||||
self.rendered_inline_toc = True
|
||||
from calibre.ebooks.pdf.render.toc import toc_as_html
|
||||
raw = toc_as_html(self.toc, self.doc, self.opts)
|
||||
pt = PersistentTemporaryFile('_pdf_itoc.htm')
|
||||
pt.write(raw)
|
||||
pt.close()
|
||||
self.render_queue.append(pt.name)
|
||||
self.render_next()
|
||||
|
||||
def render_book(self):
|
||||
if self.doc.errors_occurred:
|
||||
return self.loop.exit(1)
|
||||
try:
|
||||
if not self.render_queue:
|
||||
if self.toc is not None and len(self.toc) > 0 and not hasattr(self, 'rendered_inline_toc'):
|
||||
return self.render_inline_toc()
|
||||
self.loop.exit()
|
||||
else:
|
||||
self.render_next()
|
||||
@ -383,3 +396,4 @@ class PDFWriter(QObject):
|
||||
amap['anchors'])
|
||||
|
||||
|
||||
|
||||
|
@ -187,6 +187,12 @@ class PageTree(Dictionary):
|
||||
def get_ref(self, num):
|
||||
return self['Kids'][num-1]
|
||||
|
||||
def get_num(self, pageref):
|
||||
try:
|
||||
return self['Kids'].index(pageref) + 1
|
||||
except ValueError:
|
||||
return -1
|
||||
|
||||
class HashingStream(object):
|
||||
|
||||
def __init__(self, f):
|
||||
@ -329,12 +335,14 @@ class PDFStream(object):
|
||||
(fmtnum(x) if isinstance(x, (int, long, float)) else x) + ' ')
|
||||
|
||||
def draw_path(self, path, stroke=True, fill=False, fill_rule='winding'):
|
||||
if not path.ops: return
|
||||
if not path.ops:
|
||||
return
|
||||
self.write_path(path)
|
||||
self.current_page.write_line(self.PATH_OPS[(stroke, fill, fill_rule)])
|
||||
|
||||
def add_clip(self, path, fill_rule='winding'):
|
||||
if not path.ops: return
|
||||
if not path.ops:
|
||||
return
|
||||
self.write_path(path)
|
||||
op = 'W' if fill_rule == 'winding' else 'W*'
|
||||
self.current_page.write_line(op + ' ' + 'n')
|
||||
@ -503,3 +511,4 @@ class PDFStream(object):
|
||||
self.write_line('%d'%startxref)
|
||||
self.stream.write('%%EOF')
|
||||
|
||||
|
||||
|
76
src/calibre/ebooks/pdf/render/toc.py
Normal file
76
src/calibre/ebooks/pdf/render/toc.py
Normal file
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os
|
||||
|
||||
from lxml.html import tostring
|
||||
from lxml.html.builder import (HTML, HEAD, BODY, TABLE, TR, TD, H1, STYLE)
|
||||
|
||||
def convert_node(toc, table, level, pdf):
|
||||
tr = TR(
|
||||
TD(toc.text or _('Unknown')), TD(),
|
||||
)
|
||||
tr.set('class', 'level-%d' % level)
|
||||
anchors = pdf.links.anchors
|
||||
|
||||
path = toc.abspath or None
|
||||
frag = toc.fragment or None
|
||||
if path is None:
|
||||
return
|
||||
path = os.path.normcase(os.path.abspath(path))
|
||||
if path not in anchors:
|
||||
return None
|
||||
a = anchors[path]
|
||||
dest = a.get(frag, a[None])
|
||||
num = pdf.page_tree.obj.get_num(dest[0])
|
||||
tr[1].text = type('')(num)
|
||||
table.append(tr)
|
||||
|
||||
|
||||
def process_children(toc, table, level, pdf):
|
||||
for child in toc:
|
||||
convert_node(child, table, level, pdf)
|
||||
process_children(child, table, level+1, pdf)
|
||||
|
||||
def toc_as_html(toc, pdf, opts):
|
||||
pdf = pdf.engine.pdf
|
||||
indents = []
|
||||
for i in xrange(1, 7):
|
||||
indents.extend((i, 1.4*i))
|
||||
html = HTML(
|
||||
HEAD(
|
||||
STYLE(
|
||||
'''
|
||||
.calibre-pdf-toc table { width: 100%% }
|
||||
|
||||
.calibre-pdf-toc table tr td:last-of-type { text-align: right }
|
||||
|
||||
.calibre-pdf-toc .level-0 {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.calibre-pdf-toc .level-%d td:first-of-type { padding-left: %.1gem }
|
||||
.calibre-pdf-toc .level-%d td:first-of-type { padding-left: %.1gem }
|
||||
.calibre-pdf-toc .level-%d td:first-of-type { padding-left: %.1gem }
|
||||
.calibre-pdf-toc .level-%d td:first-of-type { padding-left: %.1gem }
|
||||
.calibre-pdf-toc .level-%d td:first-of-type { padding-left: %.1gem }
|
||||
.calibre-pdf-toc .level-%d td:first-of-type { padding-left: %.1gem }
|
||||
''' % tuple(indents) + (opts.extra_css or '')
|
||||
)
|
||||
),
|
||||
BODY(
|
||||
H1(_('Table of Contents')),
|
||||
TABLE(),
|
||||
)
|
||||
)
|
||||
body = html[1]
|
||||
body.set('class', 'calibre-pdf-toc')
|
||||
|
||||
process_children(toc, body[1], 0, pdf)
|
||||
|
||||
return tostring(html, pretty_print=True, include_meta_content_type=True, encoding='utf-8')
|
@ -23,7 +23,7 @@ class PluginWidget(Widget, Ui_Form):
|
||||
'preserve_cover_aspect_ratio', 'pdf_serif_family', 'unit',
|
||||
'pdf_sans_family', 'pdf_mono_family', 'pdf_standard_font',
|
||||
'pdf_default_font_size', 'pdf_mono_font_size', 'pdf_page_numbers',
|
||||
'pdf_footer_template', 'pdf_header_template',
|
||||
'pdf_footer_template', 'pdf_header_template', 'pdf_add_toc',
|
||||
])
|
||||
self.db, self.book_id = db, book_id
|
||||
|
||||
|
@ -77,21 +77,21 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_preserve_cover_aspect_ratio">
|
||||
<property name="text">
|
||||
<string>Preserve &aspect ratio of cover</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_pdf_page_numbers">
|
||||
<property name="text">
|
||||
<string>Add page &numbers to the bottom of every page</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Se&rif family:</string>
|
||||
@ -101,10 +101,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="10" column="1">
|
||||
<widget class="QFontComboBox" name="opt_pdf_serif_family"/>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>&Sans family:</string>
|
||||
@ -114,10 +114,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="11" column="1">
|
||||
<widget class="QFontComboBox" name="opt_pdf_sans_family"/>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>&Monospace family:</string>
|
||||
@ -127,10 +127,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<item row="12" column="1">
|
||||
<widget class="QFontComboBox" name="opt_pdf_mono_family"/>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>S&tandard font:</string>
|
||||
@ -140,10 +140,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<item row="13" column="1">
|
||||
<widget class="QComboBox" name="opt_pdf_standard_font"/>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Default font si&ze:</string>
|
||||
@ -153,14 +153,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="14" column="1">
|
||||
<widget class="QSpinBox" name="opt_pdf_default_font_size">
|
||||
<property name="suffix">
|
||||
<string> px</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Monospace &font size:</string>
|
||||
@ -170,14 +170,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<item row="15" column="1">
|
||||
<widget class="QSpinBox" name="opt_pdf_mono_font_size">
|
||||
<property name="suffix">
|
||||
<string> px</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0" colspan="2">
|
||||
<item row="16" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Page headers and footers</string>
|
||||
@ -225,6 +225,13 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_pdf_add_toc">
|
||||
<property name="text">
|
||||
<string>Add a printable &Table of Contents at the end</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user