PDF Output: Add a new "page number map" setting to easily modify page numbers as needed in headers/fotters and the generated inline table of contents.

Fixes #1796902 [Feature: shift for page numbers in pdf](https://bugs.launchpad.net/calibre/+bug/1796902)
This commit is contained in:
Kovid Goyal 2018-10-13 10:16:02 +05:30
parent eeff70325d
commit d32a5ecfc7
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 42 additions and 13 deletions

View File

@ -303,7 +303,7 @@ OPTIONS = {
'pdf_footer_template', 'pdf_header_template', 'pdf_add_toc', 'pdf_footer_template', 'pdf_header_template', 'pdf_add_toc',
'toc_title', 'pdf_page_margin_left', 'pdf_page_margin_top', 'toc_title', 'pdf_page_margin_left', 'pdf_page_margin_top',
'pdf_page_margin_right', 'pdf_page_margin_bottom', 'pdf_page_margin_right', 'pdf_page_margin_bottom',
'pdf_use_document_margins',), 'pdf_use_document_margins', 'pdf_page_number_map',),
'pml': ('inline_toc', 'full_image_depth', 'pml_output_encoding'), 'pml': ('inline_toc', 'full_image_depth', 'pml_output_encoding'),

View File

@ -148,6 +148,10 @@ class PDFOutput(OutputFormatPlugin):
' This will cause the margins specified in the conversion settings to be ignored.' ' This will cause the margins specified in the conversion settings to be ignored.'
' If the document does not specify page margins, the conversion settings will be used as a fallback.') ' If the document does not specify page margins, the conversion settings will be used as a fallback.')
), ),
OptionRecommendation(name='pdf_page_number_map', recommended_value=None,
help=_('Adjust page numbers, as needed. Syntax is a JavaScript expression for the page number.'
' For example, "if (n < 3) 0; else n - 3;", where n is current page number.')
),
} }
def specialize_options(self, log, opts, input_fmt): def specialize_options(self, log, opts, input_fmt):

View File

@ -254,9 +254,10 @@ class PDFWriter(QObject):
raise Exception('PDF Output failed, see log for details') raise Exception('PDF Output failed, see log for details')
def render_inline_toc(self): def render_inline_toc(self):
evaljs = self.view.page().mainFrame().evaluateJavaScript
self.rendered_inline_toc = True self.rendered_inline_toc = True
from calibre.ebooks.pdf.render.toc import toc_as_html from calibre.ebooks.pdf.render.toc import toc_as_html
raw = toc_as_html(self.toc, self.doc, self.opts) raw = toc_as_html(self.toc, self.doc, self.opts, evaljs)
pt = PersistentTemporaryFile('_pdf_itoc.htm') pt = PersistentTemporaryFile('_pdf_itoc.htm')
pt.write(raw) pt.write(raw)
pt.close() pt.close()
@ -460,12 +461,15 @@ class PDFWriter(QObject):
if idx is not None: if idx is not None:
setattr(self, attr, sections[idx][0]) setattr(self, attr, sections[idx][0])
from calibre.ebooks.pdf.render.toc import calculate_page_number
while True: while True:
set_section(col, sections, 'current_section') set_section(col, sections, 'current_section')
set_section(col, tl_sections, 'current_tl_section') set_section(col, tl_sections, 'current_tl_section')
self.doc.init_page(page_margins) self.doc.init_page(page_margins)
num = calculate_page_number(self.current_page_num, self.opts.pdf_page_number_map, evaljs)
if self.header or self.footer: if self.header or self.footer:
if evaljs('paged_display.update_header_footer(%d)'%self.current_page_num) is True: if evaljs('paged_display.update_header_footer(%d)'%num) is True:
self.load_header_footer_images() self.load_header_footer_images()
self.painter.save() self.painter.save()

View File

@ -12,7 +12,14 @@ from lxml.html import tostring
from lxml.html.builder import (HTML, HEAD, BODY, TABLE, TR, TD, H2, STYLE) from lxml.html.builder import (HTML, HEAD, BODY, TABLE, TR, TD, H2, STYLE)
def convert_node(toc, table, level, pdf): def calculate_page_number(num, map_expression, evaljs):
if map_expression:
num = int(evaljs('(function(){{var n={}; return {};}})()'.format(
num, map_expression)))
return num
def convert_node(toc, table, level, pdf, pdf_page_number_map, evaljs):
tr = TR( tr = TR(
TD(toc.text or _('Unknown')), TD(), TD(toc.text or _('Unknown')), TD(),
) )
@ -28,18 +35,18 @@ def convert_node(toc, table, level, pdf):
return None return None
a = anchors[path] a = anchors[path]
dest = a.get(frag, a[None]) dest = a.get(frag, a[None])
num = pdf.page_tree.obj.get_num(dest[0]) num = calculate_page_number(pdf.page_tree.obj.get_num(dest[0]), pdf_page_number_map, evaljs)
tr[1].text = type('')(num) tr[1].text = type('')(num)
table.append(tr) table.append(tr)
def process_children(toc, table, level, pdf): def process_children(toc, table, level, pdf, pdf_page_number_map, evaljs):
for child in toc: for child in toc:
convert_node(child, table, level, pdf) convert_node(child, table, level, pdf, pdf_page_number_map, evaljs)
process_children(child, table, level+1, pdf) process_children(child, table, level+1, pdf, pdf_page_number_map, evaljs)
def toc_as_html(toc, pdf, opts): def toc_as_html(toc, pdf, opts, evaljs):
pdf = pdf.engine.pdf pdf = pdf.engine.pdf
indents = [] indents = []
for i in xrange(1, 7): for i in xrange(1, 7):
@ -73,6 +80,6 @@ def toc_as_html(toc, pdf, opts):
body = html[1] body = html[1]
body.set('class', 'calibre-pdf-toc') body.set('class', 'calibre-pdf-toc')
process_children(toc, body[1], 0, pdf) process_children(toc, body[1], 0, pdf, opts.pdf_page_number_map, evaljs)
return tostring(html, pretty_print=True, include_meta_content_type=True, encoding='utf-8') return tostring(html, pretty_print=True, include_meta_content_type=True, encoding='utf-8')

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>638</width> <width>638</width>
<height>588</height> <height>634</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -197,14 +197,27 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="16" column="0" colspan="2"> <item row="16" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Page &amp;number map:</string>
</property>
<property name="buddy">
<cstring>opt_pdf_page_number_map</cstring>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QLineEdit" name="opt_pdf_page_number_map"/>
</item>
<item row="17" column="0" colspan="2">
<widget class="QGroupBox" name="page_margins_box"> <widget class="QGroupBox" name="page_margins_box">
<property name="title"> <property name="title">
<string>Page margins</string> <string>Page margins</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="0" colspan="2"> <item row="18" column="0" colspan="2">
<widget class="QGroupBox" name="template_box"> <widget class="QGroupBox" name="template_box">
<property name="title"> <property name="title">
<string>Page headers and footers</string> <string>Page headers and footers</string>

View File

@ -658,6 +658,7 @@ def pdf_output(container):
g.appendChild(choices('pdf_standard_font', _('S&tandard font:'), ui_data.font_types)) g.appendChild(choices('pdf_standard_font', _('S&tandard font:'), ui_data.font_types))
g.appendChild(int_spin('pdf_default_font_size', _('Default font si&ze:'), unit='px')) g.appendChild(int_spin('pdf_default_font_size', _('Default font si&ze:'), unit='px'))
g.appendChild(int_spin('pdf_mono_font_size', _('Default font si&ze:'), unit='px')) g.appendChild(int_spin('pdf_mono_font_size', _('Default font si&ze:'), unit='px'))
g.appendChild(lineedit('pdf_page_number_map', _('Page number &map:')))
g.appendChild(checkbox('pdf_use_document_margins', _('Use page margins from the &document being converted'))) g.appendChild(checkbox('pdf_use_document_margins', _('Use page margins from the &document being converted')))
g.appendChild(float_spin('pdf_page_margin_left', indent + _('Left page margin'), unit='pt', min=-100, max=500)) g.appendChild(float_spin('pdf_page_margin_left', indent + _('Left page margin'), unit='pt', min=-100, max=500))
g.appendChild(float_spin('pdf_page_margin_top', indent + _('Top page margin'), unit='pt', min=-100, max=500)) g.appendChild(float_spin('pdf_page_margin_top', indent + _('Top page margin'), unit='pt', min=-100, max=500))