Dont use iframes for header/footers

This breaks backwards compat with javascript, but backwards compat is
not really preserved anyway. Much better performance without iframes
This commit is contained in:
Kovid Goyal 2019-07-28 07:09:40 +05:30
parent e64b8b11fb
commit b8dc1d791c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 53 additions and 53 deletions

View File

@ -841,14 +841,17 @@ can even put different content on even and odd pages, for example the following
header template will show the title on odd pages and the author on even pages::
<header style="justify-content: flex-end">
<div class="even_page">_AUTHOR_</div>
<div class="odd_page"><i>_TITLE_</i></div>
<div class="even-page">_AUTHOR_</div>
<div class="odd-page"><i>_TITLE_</i></div>
</header>
calibre will automatically replace :code:`_TITLE_` and :code:`_AUTHOR_` with
the title and author of the document being converted. You can also display
text at the left and right edges and change the font size, as demonstrated with
this header template::
the title and author of the document being converted. Setting
:code:`justify-content` to :code:`flex-end` will cause the text to be right
aligned.
You can also display text at the left and right edges and change the font size,
as demonstrated with this header template::
<header style="justify-content: space-between; font-size: smaller">
<div>_TITLE_</div>
@ -873,8 +876,10 @@ You can even use JavaScript inside the header and footer templates, for
example, the following template will cause page numbers to start at 4 instead
of 1::
<footer><div id="pagenum"></div></footer>
<script>document.getElementById("pagenum").innerHTML = "" + (_PAGENUM_ + 3)</script>
<footer>
<div></div>
<script>document.currentScript.parentNode.querySelector("div").innerHTML = "" + (_PAGENUM_ + 3)</script>
</footer>
.. note:: When adding headers and footers make sure you set the page top and
bottom margins to large enough values, under the :guilabel:`PDF Output`

View File

@ -21,11 +21,12 @@ from PyQt5.Qt import (
)
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineProfile
from html5_parser import parse
from calibre import detect_ncpus, prepare_string_for_xml
from calibre.constants import __version__, iswindows
from calibre.ebooks.metadata.xmp import metadata_to_xmp_packet
from calibre.ebooks.oeb.base import XHTML, xml2text
from calibre.ebooks.oeb.base import XHTML
from calibre.ebooks.oeb.polish.container import Container as ContainerBase
from calibre.ebooks.oeb.polish.toc import get_toc
from calibre.ebooks.pdf.image_writer import (
@ -859,39 +860,20 @@ def add_header_footer(manager, opts, pdf_doc, container, page_number_display_map
body = root[-1]
body.attrib.pop('id', None)
body.set('style', 'margin: 0; padding: 0; border-width: 0')
skeleton = xml2text(root, method='html')
job = job_for_name(container, name, Margins(0, 0, 0, 0), page_layout)
def m(tag_name, text=None, **attrs):
def m(tag_name, text=None, style=None, **attrs):
ans = root.makeelement(XHTML(tag_name), **attrs)
if text is not None:
ans.text = text
if style is not None:
style = '; '.join('{}: {}'.format(k, v) for k, v in iteritems(style))
ans.set('style', style)
return ans
justify = 'flex-end'
if header_template:
justify = 'space-between' if footer_template else 'flex-start'
del root[0][:]
root[0].append(m('style', '''
* {{ margin: 0; padding: 0; border-width: 0; box-sizing: border-box; }}
div {{
page-break-inside: avoid;
page-break-after:always;
display: flex;
flex-direction: column;
height: 100%;
margin-bottom: 0pt;
justify-content: {justify}
}}
'''.format(justify=justify)))
root[0].append(m('script', '''
function iframe_loaded(iframe) {
for (let i of document.getElementsByTagName('iframe')) {
if (i.contentWindow.document.readyState !== 'complete') return;
}
document.title = 'iframe-loading-complete'
}
'''))
def create_toc_stack(iterator):
ans = []
@ -930,45 +912,58 @@ def add_header_footer(manager, opts, pdf_doc, container, page_number_display_map
yield 0, x
toplevel_toc_map = stack_to_map(create_toc_stack(tc()))
def create_iframe(margins, f, is_footer=False):
def create_container(page_num, margins):
style = {
'page-break-inside': 'avoid',
'page-break-after': 'always',
'display': 'flex',
'flex-direction': 'column',
'height': '100%',
'justify-content': justify,
'margin-left': '{}pt'.format(margins.left),
'margin-right': '{}pt'.format(margins.right),
'height': '{}pt'.format(margins.bottom if is_footer else margins.top)}
style = '; '.join('{}: {}'.format(k, v) for k, v in iteritems(style))
return m('iframe', seamless='seamless', style=style, onload='iframe_loaded(this)', srcdoc=f)
'margin-top': '0',
'margin-bottom': '0',
'padding': '0',
'border-width': '0',
}
def format_template(template, page_num):
extra_style = 'header, footer { margin: 0; padding: 0; border-width: 0; height: 100vh; display: flex; align-items: center }'
if page_num % 2:
extra_style += '.even_page { display: none }'
else:
extra_style += '.odd_page { display: none }'
ans = m('div', style=style, id='p{}'.format(page_num))
return ans
def format_template(template, page_num, height):
template = template.replace('_PAGENUM_', unicode_type(page_number_display_map[page_num]))
template = template.replace('_TITLE_', prepare_string_for_xml(pdf_metadata.title, True))
template = template.replace('_AUTHOR_', prepare_string_for_xml(pdf_metadata.author, True))
template = template.replace('_TOP_LEVEL_SECTION_', prepare_string_for_xml(toplevel_toc_map[page_num - 1]))
template = template.replace('_SECTION_', prepare_string_for_xml(page_toc_map[page_num - 1]))
template += '<style>{}</style>'.format(extra_style)
repl = skeleton.replace('</body>', template + '</body>', 1)
if repl == skeleton:
raise ValueError('Failed to insert template into skeleton: ' + skeleton)
return repl
troot = parse(template, namespace_elements=True)
ans = troot[-1][0]
style = ans.get('style') or ''
style = (
'margin: 0; padding: 0; height: {height}pt; border-width: 0;'
'display: flex; align-items: center;').format(height=height) + style
ans.set('style', style)
for child in ans.xpath('descendant-or-self::*[@class]'):
cls = frozenset(child.get('class').split())
q = 'even-page' if page_num % 2 else 'odd-page'
if q in cls or q.replace('-', '_') in cls:
style = child.get('style') or ''
child.set('style', style + '; display: none')
return ans
for page_num in range(1, pdf_doc.page_count() + 1):
div = m('div')
body.append(div)
margins = page_margins_map[page_num - 1]
div = create_container(page_num, margins)
body.append(div)
if header_template:
f = format_template(header_template, page_num)
div.append(create_iframe(margins, f))
div.append(format_template(header_template, page_num, margins.top))
if footer_template:
f = format_template(footer_template, page_num)
div.append(create_iframe(margins, f, True))
div.append(format_template(footer_template, page_num, margins.bottom))
container.commit()
results = manager.convert_html_files([job], wait_for_title='iframe-loading-complete')
# print(open(job[0]).read())
results = manager.convert_html_files([job], settle_time=1)
data = results[name]
if not isinstance(data, bytes):
raise SystemExit(data)