mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
PDF Output: Add an option to use page margins from the input document, specified via @page CSS rules. Allows individual HTML files in the input document to have different page margins in the output PDF. Fixes #1773319 [ePub-to-PDF conversion: display of full-page images](https://bugs.launchpad.net/calibre/+bug/1773319)
This commit is contained in:
parent
bd3274ff4a
commit
c90a748839
@ -80,9 +80,8 @@ class OptionRecommendation(object):
|
||||
raise ValueError('OpRec: %s: Recommended value not in choices'%
|
||||
self.option.name)
|
||||
if not (isinstance(self.recommended_value, (int, float, str, unicode)) or self.recommended_value is None):
|
||||
raise ValueError('OpRec: %s:'%self.option.name +
|
||||
repr(self.recommended_value) +
|
||||
' is not a string or a number')
|
||||
raise ValueError('OpRec: %s:'%self.option.name + repr(
|
||||
self.recommended_value) + ' is not a string or a number')
|
||||
|
||||
|
||||
class DummyReporter(object):
|
||||
@ -342,6 +341,13 @@ class OutputFormatPlugin(Plugin):
|
||||
return self.oeb.metadata.publication_type and \
|
||||
unicode(self.oeb.metadata.publication_type[0]).startswith('periodical:')
|
||||
|
||||
def specialize_options(self, log, opts, input_fmt):
|
||||
'''
|
||||
Can be used to change the values of conversion options, as used by the
|
||||
conversion pipeline.
|
||||
'''
|
||||
pass
|
||||
|
||||
def specialize_css_for_output(self, log, opts, item, stylizer):
|
||||
'''
|
||||
Can be used to make changes to the css during the CSS flattening
|
||||
|
@ -142,14 +142,25 @@ class PDFOutput(OutputFormatPlugin):
|
||||
help=_('The size of the bottom page margin, in pts. Default is 72pt.'
|
||||
' Overrides the common bottom page margin setting, unless set to zero.')
|
||||
),
|
||||
OptionRecommendation(name='pdf_use_document_margins', recommended_value=False,
|
||||
help=_('Use the page margins specified in the input document via @page CSS rules.'
|
||||
' 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.')
|
||||
),
|
||||
])
|
||||
|
||||
def specialize_options(self, log, opts, input_fmt):
|
||||
if opts.pdf_use_document_margins:
|
||||
# Prevent the conversion pipeline from overwriting document margins
|
||||
opts.margin_left = opts.margin_right = opts.margin_top = opts.margin_bottom = -1
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
from calibre.gui2 import must_use_qt, load_builtin_fonts
|
||||
from calibre.ebooks.oeb.transforms.split import Split
|
||||
# Turn off hinting in WebKit (requires a patched build of QtWebKit)
|
||||
os.environ['CALIBRE_WEBKIT_NO_HINTING'] = '1'
|
||||
self.filtered_font_warnings = set()
|
||||
self.stored_page_margins = getattr(opts, '_stored_page_margins', {})
|
||||
try:
|
||||
# split on page breaks, as the JS code to convert page breaks to
|
||||
# column breaks will not work because of QWebSettings.LocalContentCanAccessFileUrls
|
||||
@ -192,7 +203,7 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.cover_data = item.data
|
||||
|
||||
def process_fonts(self):
|
||||
''' Make sure all fonts are embeddable. Also remove some fonts that causes problems. '''
|
||||
''' Make sure all fonts are embeddable. Also remove some fonts that cause problems. '''
|
||||
from calibre.ebooks.oeb.base import urlnormalize
|
||||
from calibre.utils.fonts.utils import remove_embed_restriction
|
||||
|
||||
@ -244,6 +255,13 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.get_cover_data()
|
||||
|
||||
self.process_fonts()
|
||||
if self.opts.pdf_use_document_margins and self.stored_page_margins:
|
||||
import json
|
||||
for href, margins in self.stored_page_margins.iteritems():
|
||||
item = oeb_book.manifest.hrefs.get(href)
|
||||
root = item.data
|
||||
if hasattr(root, 'xpath') and margins:
|
||||
root.set('data-calibre-pdf-output-page-margins', json.dumps(margins))
|
||||
|
||||
with TemporaryDirectory('_pdf_out') as oeb_dir:
|
||||
from calibre.customize.ui import plugin_for_output_format
|
||||
|
@ -1081,6 +1081,7 @@ OptionRecommendation(name='search_replace',
|
||||
self.input_plugin.report_progress = ir
|
||||
if self.for_regex_wizard:
|
||||
self.input_plugin.for_viewer = True
|
||||
self.output_plugin.specialize_options(self.log, self.opts, self.input_fmt)
|
||||
with self.input_plugin:
|
||||
self.oeb = self.input_plugin(stream, self.opts,
|
||||
self.input_fmt, self.log,
|
||||
|
@ -15,6 +15,7 @@ import cssutils
|
||||
from cssutils.css import Property
|
||||
|
||||
from calibre import guess_type
|
||||
from calibre.ebooks import unit_convert
|
||||
from calibre.ebooks.oeb.base import (XHTML, XHTML_NS, CSS_MIME, OEB_STYLES,
|
||||
namespace, barename, XPath)
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
@ -218,6 +219,19 @@ class CSSFlattener(object):
|
||||
if epub3_nav is not None:
|
||||
self.opts.epub3_nav_parsed = epub3_nav.data
|
||||
|
||||
self.store_page_margins()
|
||||
|
||||
def store_page_margins(self):
|
||||
self.opts._stored_page_margins = {}
|
||||
for item, stylizer in self.stylizers.iteritems():
|
||||
margins = self.opts._stored_page_margins[item.href] = {}
|
||||
for prop, val in stylizer.page_rule.items():
|
||||
p, w = prop.partition('-')[::2]
|
||||
if p == 'margin':
|
||||
margins[w] = unit_convert(
|
||||
val, stylizer.profile.width_pts, stylizer.body_font_size,
|
||||
stylizer.profile.dpi, body_font_size=stylizer.body_font_size)
|
||||
|
||||
def get_embed_font_info(self, family, failure_critical=True):
|
||||
efi = []
|
||||
body_font_family = None
|
||||
|
@ -67,18 +67,7 @@ class PdfEngine(QPaintEngine):
|
||||
self.left_margin, self.top_margin = left_margin, top_margin
|
||||
self.right_margin, self.bottom_margin = right_margin, bottom_margin
|
||||
self.pixel_width, self.pixel_height = width, height
|
||||
# Setup a co-ordinate transform that allows us to use co-ords
|
||||
# from Qt's pixel based co-ordinate system with its origin at the top
|
||||
# left corner. PDF's co-ordinate system is based on pts and has its
|
||||
# origin in the bottom left corner. We also have to implement the page
|
||||
# margins. Therefore, we need to translate, scale and reflect about the
|
||||
# x-axis.
|
||||
dy = self.page_height - self.top_margin
|
||||
dx = self.left_margin
|
||||
sx = (self.page_width - self.left_margin - self.right_margin) / self.pixel_width
|
||||
sy = (self.page_height - self.top_margin - self.bottom_margin) / self.pixel_height
|
||||
|
||||
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
|
||||
self.pdf_system = self.create_transform()
|
||||
self.graphics = Graphics(self.pixel_width, self.pixel_height)
|
||||
self.errors_occurred = False
|
||||
self.errors, self.debug = errors, debug
|
||||
@ -95,6 +84,23 @@ class PdfEngine(QPaintEngine):
|
||||
if err:
|
||||
raise RuntimeError('Failed to load qt_hack with err: %s'%err)
|
||||
|
||||
def create_transform(self, left_margin=None, top_margin=None, right_margin=None, bottom_margin=None):
|
||||
# Setup a co-ordinate transform that allows us to use co-ords
|
||||
# from Qt's pixel based co-ordinate system with its origin at the top
|
||||
# left corner. PDF's co-ordinate system is based on pts and has its
|
||||
# origin in the bottom left corner. We also have to implement the page
|
||||
# margins. Therefore, we need to translate, scale and reflect about the
|
||||
# x-axis.
|
||||
left_margin = self.left_margin if left_margin is None else left_margin
|
||||
top_margin = self.top_margin if top_margin is None else top_margin
|
||||
right_margin = self.right_margin if right_margin is None else right_margin
|
||||
bottom_margin = self.bottom_margin if bottom_margin is None else bottom_margin
|
||||
dy = self.page_height - top_margin
|
||||
dx = left_margin
|
||||
sx = (self.page_width - left_margin - right_margin) / self.pixel_width
|
||||
sy = (self.page_height - top_margin - bottom_margin) / self.pixel_height
|
||||
return QTransform(sx, 0, 0, -sy, dx, dy)
|
||||
|
||||
def apply_graphics_state(self):
|
||||
self.graphics(self.pdf_system, self.painter())
|
||||
|
||||
@ -110,9 +116,12 @@ class PdfEngine(QPaintEngine):
|
||||
def do_stroke(self):
|
||||
return self.graphics.current_state.do_stroke
|
||||
|
||||
def init_page(self):
|
||||
def init_page(self, custom_margins=None):
|
||||
self.content_written_to_current_page = False
|
||||
if custom_margins is None:
|
||||
self.pdf.transform(self.pdf_system)
|
||||
else:
|
||||
self.pdf.transform(self.create_transform(*custom_margins))
|
||||
self.pdf.apply_fill(color=(1, 1, 1)) # QPainter has a default background brush of white
|
||||
self.graphics.reset()
|
||||
self.pdf.save_stack()
|
||||
@ -391,8 +400,8 @@ class PdfDevice(QPaintDevice): # {{{
|
||||
def end_page(self, *args, **kwargs):
|
||||
self.engine.end_page(*args, **kwargs)
|
||||
|
||||
def init_page(self):
|
||||
self.engine.init_page()
|
||||
def init_page(self, custom_margins=None):
|
||||
self.engine.init_page(custom_margins=custom_margins)
|
||||
|
||||
@property
|
||||
def full_page_rect(self):
|
||||
@ -414,6 +423,10 @@ class PdfDevice(QPaintDevice): # {{{
|
||||
return pt * (self.height()/self.page_height if vertical else
|
||||
self.width()/self.page_width)
|
||||
|
||||
def to_pt(self, px, vertical=True):
|
||||
return px * (self.page_height / self.height() if vertical else
|
||||
self.page_width / self.width())
|
||||
|
||||
def set_metadata(self, *args, **kwargs):
|
||||
self.engine.set_metadata(*args, **kwargs)
|
||||
|
||||
|
@ -365,6 +365,19 @@ class PDFWriter(QObject):
|
||||
''' % self.hyphenate_lang
|
||||
)
|
||||
|
||||
def convert_page_margins(self, doc_margins):
|
||||
ans = [0, 0, 0, 0]
|
||||
|
||||
def convert(name, idx, vertical=True):
|
||||
m = doc_margins.get(name)
|
||||
if m is None:
|
||||
ans[idx] = getattr(self.doc.engine, '{}_margin'.format(name))
|
||||
else:
|
||||
ans[idx] = m
|
||||
|
||||
convert('left', 0, False), convert('top', 1), convert('right', 2, False), convert('bottom', 3)
|
||||
return ans
|
||||
|
||||
def do_paged_render(self):
|
||||
if self.paged_js is None:
|
||||
import uuid
|
||||
@ -387,6 +400,20 @@ class PDFWriter(QObject):
|
||||
if self.opts.pdf_hyphenate:
|
||||
self.hyphenate(evaljs)
|
||||
|
||||
margin_top, margin_bottom = self.margin_top, self.margin_bottom
|
||||
page_margins = None
|
||||
if self.opts.pdf_use_document_margins:
|
||||
doc_margins = evaljs('document.documentElement.getAttribute("data-calibre-pdf-output-page-margins")')
|
||||
try:
|
||||
doc_margins = json.loads(doc_margins)
|
||||
except Exception:
|
||||
doc_margins = None
|
||||
if doc_margins and isinstance(doc_margins, dict):
|
||||
doc_margins = {k:float(v) for k, v in doc_margins.iteritems() if isinstance(v, (float, int)) and k in {'right', 'top', 'left', 'bottom'}}
|
||||
if doc_margins:
|
||||
margin_top = margin_bottom = 0
|
||||
page_margins = self.convert_page_margins(doc_margins)
|
||||
|
||||
amap = json.loads(evaljs('''
|
||||
document.body.style.backgroundColor = "white";
|
||||
paged_display.set_geometry(1, %d, %d, %d);
|
||||
@ -395,7 +422,7 @@ class PDFWriter(QObject):
|
||||
ret = book_indexing.all_links_and_anchors();
|
||||
window.scrollTo(0, 0); // This is needed as getting anchor positions could have caused the viewport to scroll
|
||||
JSON.stringify(ret);
|
||||
'''%(self.margin_top, 0, self.margin_bottom)))
|
||||
'''%(margin_top, 0, margin_bottom)))
|
||||
|
||||
if not isinstance(amap, dict):
|
||||
amap = {'links':[], 'anchors':{}} # Some javascript error occurred
|
||||
@ -429,7 +456,7 @@ class PDFWriter(QObject):
|
||||
while True:
|
||||
set_section(col, sections, 'current_section')
|
||||
set_section(col, tl_sections, 'current_tl_section')
|
||||
self.doc.init_page()
|
||||
self.doc.init_page(page_margins)
|
||||
if self.header or self.footer:
|
||||
if evaljs('paged_display.update_header_footer(%d)'%self.current_page_num) is True:
|
||||
self.load_header_footer_images()
|
||||
|
@ -5,7 +5,7 @@ __copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from PyQt5.Qt import QHBoxLayout, QFormLayout, QDoubleSpinBox
|
||||
from PyQt5.Qt import QHBoxLayout, QFormLayout, QDoubleSpinBox, QCheckBox, QVBoxLayout
|
||||
|
||||
from calibre.gui2.convert.pdf_output_ui import Ui_Form
|
||||
from calibre.gui2.convert import Widget
|
||||
@ -30,6 +30,7 @@ class PluginWidget(Widget, Ui_Form):
|
||||
'pdf_default_font_size', 'pdf_mono_font_size', 'pdf_page_numbers',
|
||||
'pdf_footer_template', 'pdf_header_template', 'pdf_add_toc', 'toc_title',
|
||||
'pdf_page_margin_left', 'pdf_page_margin_top', 'pdf_page_margin_right', 'pdf_page_margin_bottom',
|
||||
'pdf_use_document_margins',
|
||||
])
|
||||
self.db, self.book_id = db, book_id
|
||||
try:
|
||||
@ -48,13 +49,24 @@ class PluginWidget(Widget, Ui_Form):
|
||||
self.initialize_options(get_option, get_help, db, book_id)
|
||||
self.layout().setFieldGrowthPolicy(self.layout().ExpandingFieldsGrow)
|
||||
self.template_box.layout().setFieldGrowthPolicy(self.layout().AllNonFixedFieldsGrow)
|
||||
self.toggle_margins()
|
||||
|
||||
def toggle_margins(self):
|
||||
enabled = not self.opt_pdf_use_document_margins.isChecked()
|
||||
for which in 'left top right bottom'.split():
|
||||
getattr(self, 'opt_pdf_page_margin_' + which).setEnabled(enabled)
|
||||
|
||||
def setupUi(self, *a):
|
||||
Ui_Form.setupUi(self, *a)
|
||||
h = self.page_margins_box.h = QHBoxLayout(self.page_margins_box)
|
||||
v = self.page_margins_box.v = QVBoxLayout(self.page_margins_box)
|
||||
self.opt_pdf_use_document_margins = c = QCheckBox(_('Use page margins from the &document being converted'))
|
||||
v.addWidget(c)
|
||||
c.stateChanged.connect(self.toggle_margins)
|
||||
h = self.page_margins_box.h = QHBoxLayout()
|
||||
l = self.page_margins_box.l = QFormLayout()
|
||||
r = self.page_margins_box.r = QFormLayout()
|
||||
h.addLayout(l), h.addLayout(r)
|
||||
v.addLayout(h)
|
||||
|
||||
def margin(which):
|
||||
w = QDoubleSpinBox(self)
|
||||
|
Loading…
x
Reference in New Issue
Block a user