mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on porting the PDF output plugin to use web engine
This commit is contained in:
parent
b7e57597a3
commit
b5b0891421
@ -144,14 +144,6 @@
|
||||
"sip_files": "calibre/utils/imageops/imageops.sip",
|
||||
"inc_dirs": "calibre/utils/imageops"
|
||||
},
|
||||
{
|
||||
"name": "qt_hack",
|
||||
"sources": "calibre/ebooks/pdf/render/qt_hack.cpp",
|
||||
"headers": "calibre/ebooks/pdf/render/qt_hack.h",
|
||||
"sip_files": "calibre/ebooks/pdf/render/qt_hack.sip",
|
||||
"inc_dirs": "calibre/ebooks/pdf/render",
|
||||
"qt_private": "core gui"
|
||||
},
|
||||
{
|
||||
"name": "lzma_binding",
|
||||
"sources": "lzma/*.c",
|
||||
|
@ -176,7 +176,6 @@ class Plugins(collections.Mapping):
|
||||
'html',
|
||||
'freetype',
|
||||
'imageops',
|
||||
'qt_hack',
|
||||
'hunspell',
|
||||
'_patiencediff_c',
|
||||
'bzzdec',
|
||||
|
@ -11,17 +11,16 @@ Convert OEB ebook format to PDF.
|
||||
|
||||
import glob, os
|
||||
|
||||
from calibre.constants import iswindows
|
||||
from calibre.customize.conversion import (OutputFormatPlugin,
|
||||
OptionRecommendation)
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from polyglot.builtins import iteritems, unicode_type
|
||||
|
||||
UNITS = ['millimeter', 'centimeter', 'point', 'inch' , 'pica' , 'didot',
|
||||
'cicero', 'devicepixel']
|
||||
UNITS = ('millimeter', 'centimeter', 'point', 'inch' , 'pica' , 'didot',
|
||||
'cicero', 'devicepixel')
|
||||
|
||||
PAPER_SIZES = ['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'b0', 'b1',
|
||||
'b2', 'b3', 'b4', 'b5', 'b6', 'legal', 'letter']
|
||||
PAPER_SIZES = ('a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'b0', 'b1',
|
||||
'b2', 'b3', 'b4', 'b5', 'b6', 'legal', 'letter')
|
||||
|
||||
|
||||
class PDFMetadata(object): # {{{
|
||||
@ -53,7 +52,7 @@ class PDFOutput(OutputFormatPlugin):
|
||||
author = 'Kovid Goyal'
|
||||
file_type = 'pdf'
|
||||
commit_name = 'pdf_output'
|
||||
ui_data = {'paper_sizes': PAPER_SIZES, 'units': UNITS, 'font_types': ['serif', 'sans', 'mono']}
|
||||
ui_data = {'paper_sizes': PAPER_SIZES, 'units': UNITS, 'font_types': ('serif', 'sans', 'mono')}
|
||||
|
||||
options = {
|
||||
OptionRecommendation(name='use_profile_size', recommended_value=False,
|
||||
@ -63,13 +62,13 @@ class PDFOutput(OutputFormatPlugin):
|
||||
OptionRecommendation(name='unit', recommended_value='inch',
|
||||
level=OptionRecommendation.LOW, short_switch='u', choices=UNITS,
|
||||
help=_('The unit of measure for page sizes. Default is inch. Choices '
|
||||
'are %s '
|
||||
'Note: This does not override the unit for margins!') % UNITS),
|
||||
'are {} '
|
||||
'Note: This does not override the unit for margins!').format(UNITS)),
|
||||
OptionRecommendation(name='paper_size', recommended_value='letter',
|
||||
level=OptionRecommendation.LOW, choices=PAPER_SIZES,
|
||||
help=_('The size of the paper. This size will be overridden when a '
|
||||
'non default output profile is used. Default is letter. Choices '
|
||||
'are %s') % PAPER_SIZES),
|
||||
'are {}').format(PAPER_SIZES)),
|
||||
OptionRecommendation(name='custom_size', recommended_value=None,
|
||||
help=_('Custom size of the document. Use the form widthxheight '
|
||||
'e.g. `123x321` to specify the width and height. '
|
||||
@ -163,44 +162,34 @@ class PDFOutput(OutputFormatPlugin):
|
||||
|
||||
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
|
||||
Split()(oeb_book, opts)
|
||||
must_use_qt()
|
||||
load_builtin_fonts()
|
||||
must_use_qt()
|
||||
load_builtin_fonts()
|
||||
|
||||
self.oeb = oeb_book
|
||||
self.input_plugin, self.opts, self.log = input_plugin, opts, log
|
||||
self.output_path = output_path
|
||||
from calibre.ebooks.oeb.base import OPF, OPF2_NS
|
||||
from lxml import etree
|
||||
from io import BytesIO
|
||||
package = etree.Element(OPF('package'),
|
||||
attrib={'version': '2.0', 'unique-identifier': 'dummy'},
|
||||
nsmap={None: OPF2_NS})
|
||||
from calibre.ebooks.metadata.opf2 import OPF
|
||||
self.oeb.metadata.to_opf2(package)
|
||||
self.metadata = OPF(BytesIO(etree.tostring(package))).to_book_metadata()
|
||||
self.cover_data = None
|
||||
self.oeb = oeb_book
|
||||
self.input_plugin, self.opts, self.log = input_plugin, opts, log
|
||||
self.output_path = output_path
|
||||
from calibre.ebooks.oeb.base import OPF, OPF2_NS
|
||||
from lxml import etree
|
||||
from io import BytesIO
|
||||
package = etree.Element(OPF('package'),
|
||||
attrib={'version': '2.0', 'unique-identifier': 'dummy'},
|
||||
nsmap={None: OPF2_NS})
|
||||
from calibre.ebooks.metadata.opf2 import OPF
|
||||
self.oeb.metadata.to_opf2(package)
|
||||
self.metadata = OPF(BytesIO(etree.tostring(package))).to_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())
|
||||
else:
|
||||
log.debug('Converting input as a text based book...')
|
||||
self.convert_text(oeb_book)
|
||||
finally:
|
||||
os.environ.pop('CALIBRE_WEBKIT_NO_HINTING', None)
|
||||
if input_plugin.is_image_collection:
|
||||
log.debug('Converting input as an image collection...')
|
||||
self.convert_images(input_plugin.get_images())
|
||||
else:
|
||||
log.debug('Converting input as a text based book...')
|
||||
self.convert_text(oeb_book)
|
||||
|
||||
def convert_images(self, images):
|
||||
from calibre.ebooks.pdf.render.from_html import ImagePDFWriter
|
||||
self.write(ImagePDFWriter, images, None)
|
||||
from calibre.ebooks.pdf.image_writer import convert
|
||||
convert(images, self.output_path, self.opts)
|
||||
|
||||
def get_cover_data(self):
|
||||
oeb = self.oeb
|
||||
@ -210,8 +199,8 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.cover_data = item.data
|
||||
|
||||
def process_fonts(self):
|
||||
''' Make sure all fonts are embeddable. Also remove some fonts that cause problems. '''
|
||||
from calibre.ebooks.oeb.base import urlnormalize, css_text
|
||||
''' Make sure all fonts are embeddable '''
|
||||
from calibre.ebooks.oeb.base import urlnormalize
|
||||
from calibre.utils.fonts.utils import remove_embed_restriction
|
||||
|
||||
processed = set()
|
||||
@ -240,19 +229,6 @@ class PDFOutput(OutputFormatPlugin):
|
||||
if nraw != raw:
|
||||
ff.data = nraw
|
||||
self.oeb.container.write(path, nraw)
|
||||
elif iswindows and rule.type == rule.STYLE_RULE:
|
||||
from tinycss.fonts3 import parse_font_family, serialize_font_family
|
||||
s = rule.style
|
||||
f = s.getProperty('font-family')
|
||||
if f is not None:
|
||||
font_families = parse_font_family(css_text(f.propertyValue))
|
||||
ff = [x for x in font_families if x.lower() != 'courier']
|
||||
if len(ff) != len(font_families):
|
||||
if 'courier' not in self.filtered_font_warnings:
|
||||
# See https://bugs.launchpad.net/bugs/1665835
|
||||
self.filtered_font_warnings.add('courier')
|
||||
self.log.warn('Removing courier font family as it does not render on windows')
|
||||
f.propertyValue.cssText = serialize_font_family(ff or ['monospace'])
|
||||
|
||||
def convert_text(self, oeb_book):
|
||||
from calibre.ebooks.metadata.opf2 import OPF
|
||||
@ -271,18 +247,6 @@ class PDFOutput(OutputFormatPlugin):
|
||||
if hasattr(root, 'xpath') and margins:
|
||||
root.set('data-calibre-pdf-output-page-margins', json.dumps(margins))
|
||||
|
||||
# Remove javascript
|
||||
for item in self.oeb.spine:
|
||||
root = item.data
|
||||
if hasattr(root, 'xpath'):
|
||||
for script in root.xpath('//*[local-name()="script"]'):
|
||||
script.text = None
|
||||
script.attrib.clear()
|
||||
for elem in root.iter('*'):
|
||||
for attr in tuple(elem.attrib):
|
||||
if attr.startswith('on'):
|
||||
elem.set(attr, '')
|
||||
|
||||
with TemporaryDirectory('_pdf_out') as oeb_dir:
|
||||
from calibre.customize.ui import plugin_for_output_format
|
||||
oeb_output = plugin_for_output_format('oeb')
|
||||
|
@ -14,10 +14,10 @@ from calibre import guess_type
|
||||
from calibre.constants import numeric_version, __appname__
|
||||
from calibre.ebooks.docx.names import DOCXNamespace
|
||||
from calibre.ebooks.metadata import authors_to_string
|
||||
from calibre.ebooks.pdf.render.common import PAPER_SIZES
|
||||
from calibre.utils.date import utcnow
|
||||
from calibre.utils.localization import canonicalize_lang, lang_as_iso639_1
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
from calibre.ebooks.pdf.render.common import PAPER_SIZES
|
||||
from polyglot.builtins import iteritems, map, unicode_type, native_string_type
|
||||
|
||||
|
||||
|
96
src/calibre/ebooks/pdf/image_writer.py
Normal file
96
src/calibre/ebooks/pdf/image_writer.py
Normal file
@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from PyQt5.Qt import (
|
||||
QMarginsF, QPageLayout, QPageSize, QPainter, QPdfWriter, QSize
|
||||
)
|
||||
|
||||
from calibre import fit_image
|
||||
from calibre.ebooks.docx.writer.container import cicero, cm, didot, inch, mm, pica
|
||||
from calibre.utils.img import image_from_path
|
||||
|
||||
# Page layout {{{
|
||||
|
||||
|
||||
def get_page_size(opts, for_comic=False):
|
||||
use_profile = opts.use_profile_size and opts.output_profile.short_name != 'default' and opts.output_profile.width <= 9999
|
||||
if use_profile:
|
||||
w = (opts.output_profile.comic_screen_size[0] if for_comic else
|
||||
opts.output_profile.width)
|
||||
h = (opts.output_profile.comic_screen_size[1] if for_comic else
|
||||
opts.output_profile.height)
|
||||
dpi = opts.output_profile.dpi
|
||||
factor = 72.0 / dpi
|
||||
page_size = QPageSize(QSize(factor * w, factor * h), matchPolicy=QPageSize.ExactMatch)
|
||||
else:
|
||||
page_size = None
|
||||
if opts.custom_size is not None:
|
||||
width, sep, height = opts.custom_size.partition('x')
|
||||
if height:
|
||||
try:
|
||||
width = float(width.replace(',', '.'))
|
||||
height = float(height.replace(',', '.'))
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if opts.unit == 'devicepixel':
|
||||
factor = 72.0 / opts.output_profile.dpi
|
||||
else:
|
||||
factor = {
|
||||
'point':1.0, 'inch':inch, 'cicero':cicero,
|
||||
'didot':didot, 'pica':pica, 'millimeter':mm,
|
||||
'centimeter':cm
|
||||
}[opts.unit]
|
||||
page_size = QPageSize(QSize(factor*width, factor*height), matchPolicy=QPageSize.ExactMatch)
|
||||
if page_size is None:
|
||||
page_size = QPageSize(getattr(QPageSize, opts.paper_size.capitalize()))
|
||||
return page_size
|
||||
|
||||
|
||||
def get_page_layout(opts, for_comic=False):
|
||||
page_size = get_page_size(opts, for_comic)
|
||||
|
||||
def m(which):
|
||||
return max(0, getattr(opts, 'pdf_page_margin_' + which) or getattr(opts, 'margin_' + which))
|
||||
|
||||
margins = QMarginsF(m('left'), m('top'), m('right'), m('bottom'))
|
||||
ans = QPageLayout(page_size, QPageLayout.Portrait, margins)
|
||||
return ans
|
||||
# }}}
|
||||
|
||||
|
||||
def draw_image_page(painter, img, preserve_aspect_ratio=True):
|
||||
page_rect = painter.viewport()
|
||||
if preserve_aspect_ratio:
|
||||
aspect_ratio = float(img.width())/img.height()
|
||||
nw, nh = page_rect.width(), page_rect.height()
|
||||
if aspect_ratio > 1:
|
||||
nh = int(page_rect.width()/aspect_ratio)
|
||||
else: # Width is smaller than height
|
||||
nw = page_rect.height()*aspect_ratio
|
||||
__, nnw, nnh = fit_image(nw, nh, page_rect.width(),
|
||||
page_rect.height())
|
||||
dx = int((page_rect.width() - nnw)/2.)
|
||||
dy = int((page_rect.height() - nnh)/2.)
|
||||
page_rect.translate(dx, dy)
|
||||
page_rect.setHeight(nnh)
|
||||
page_rect.setWidth(nnw)
|
||||
painter.drawImage(page_rect, img)
|
||||
|
||||
|
||||
def convert(images, output_path, opts):
|
||||
writer = QPdfWriter(output_path)
|
||||
writer.setPageLayout(get_page_layout(opts, for_comic=True))
|
||||
painter = QPainter()
|
||||
painter.begin(writer)
|
||||
try:
|
||||
for i, path in enumerate(images):
|
||||
if i > 0:
|
||||
writer.newPage()
|
||||
img = image_from_path(path)
|
||||
draw_image_page(painter, img)
|
||||
finally:
|
||||
painter.end()
|
@ -1,433 +0,0 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys, traceback, math
|
||||
from collections import namedtuple
|
||||
from functools import wraps, partial
|
||||
from polyglot.builtins import map, zip
|
||||
|
||||
from PyQt5.Qt import (QPaintEngine, QPaintDevice, Qt, QTransform, QBrush)
|
||||
|
||||
from calibre.constants import plugins
|
||||
from calibre.ebooks.pdf.render.serialize import (PDFStream, Path)
|
||||
from calibre.ebooks.pdf.render.common import inch, A4, fmtnum
|
||||
from calibre.ebooks.pdf.render.graphics import convert_path, Graphics
|
||||
from calibre.utils.fonts.sfnt.container import Sfnt, UnsupportedFont
|
||||
from calibre.utils.fonts.sfnt.metrics import FontMetrics
|
||||
from polyglot.builtins import codepoint_to_chr, itervalues
|
||||
|
||||
Point = namedtuple('Point', 'x y')
|
||||
ColorState = namedtuple('ColorState', 'color opacity do')
|
||||
GlyphInfo = namedtuple('GlyphInfo', 'name size stretch positions indices')
|
||||
|
||||
|
||||
def repr_transform(t):
|
||||
vals = map(fmtnum, (t.m11(), t.m12(), t.m21(), t.m22(), t.dx(), t.dy()))
|
||||
return '[%s]'%' '.join(vals)
|
||||
|
||||
|
||||
def store_error(func):
|
||||
|
||||
@wraps(func)
|
||||
def errh(self, *args, **kwargs):
|
||||
try:
|
||||
func(self, *args, **kwargs)
|
||||
except:
|
||||
self.errors_occurred = True
|
||||
self.errors(traceback.format_exc())
|
||||
|
||||
return errh
|
||||
|
||||
|
||||
class Font(FontMetrics):
|
||||
|
||||
def __init__(self, sfnt):
|
||||
FontMetrics.__init__(self, sfnt)
|
||||
self.glyph_map = {}
|
||||
|
||||
|
||||
class PdfEngine(QPaintEngine):
|
||||
|
||||
FEATURES = QPaintEngine.AllFeatures & ~(
|
||||
QPaintEngine.PorterDuff | QPaintEngine.PerspectiveTransform | QPaintEngine.ObjectBoundingModeGradients | QPaintEngine.RadialGradientFill | QPaintEngine.ConicalGradientFill) # noqa
|
||||
|
||||
def __init__(self, file_object, page_width, page_height, left_margin,
|
||||
top_margin, right_margin, bottom_margin, width, height,
|
||||
errors=print, debug=print, compress=True,
|
||||
mark_links=False, opts=None, page_margins=(0, 0, 0, 0)):
|
||||
QPaintEngine.__init__(self, self.FEATURES)
|
||||
self.file_object = file_object
|
||||
self.compress, self.mark_links = compress, mark_links
|
||||
self.page_height, self.page_width = page_height, page_width
|
||||
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
|
||||
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
|
||||
self.fonts = {}
|
||||
self.current_page_num = 1
|
||||
self.current_page_inited = False
|
||||
self.content_written_to_current_page = False
|
||||
self.qt_hack, err = plugins['qt_hack']
|
||||
self.has_footers = opts is not None and (opts.pdf_page_numbers or opts.pdf_footer_template is not None)
|
||||
self.has_headers = opts is not None and opts.pdf_header_template is not None
|
||||
ml, mr, mt, mb = page_margins
|
||||
self.header_height = mt
|
||||
self.footer_height = mb
|
||||
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())
|
||||
|
||||
def resolve_fill(self, rect):
|
||||
self.graphics.resolve_fill(rect, self.pdf_system,
|
||||
self.painter().transform())
|
||||
|
||||
@property
|
||||
def do_fill(self):
|
||||
return self.graphics.current_state.do_fill
|
||||
|
||||
@property
|
||||
def do_stroke(self):
|
||||
return self.graphics.current_state.do_stroke
|
||||
|
||||
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()
|
||||
self.current_page_inited = True
|
||||
|
||||
def begin(self, device):
|
||||
if not hasattr(self, 'pdf'):
|
||||
try:
|
||||
self.pdf = PDFStream(self.file_object, (self.page_width,
|
||||
self.page_height), compress=self.compress,
|
||||
mark_links=self.mark_links,
|
||||
debug=self.debug)
|
||||
self.graphics.begin(self.pdf)
|
||||
except:
|
||||
self.errors(traceback.format_exc())
|
||||
self.errors_occurred = True
|
||||
return False
|
||||
return True
|
||||
|
||||
def end_page(self, is_last_page=False):
|
||||
if self.current_page_inited:
|
||||
self.pdf.restore_stack()
|
||||
drop_page = is_last_page and not self.content_written_to_current_page
|
||||
self.pdf.end_page(drop_page=drop_page)
|
||||
self.current_page_inited = False
|
||||
self.current_page_num += 0 if drop_page else 1
|
||||
return self.content_written_to_current_page
|
||||
|
||||
def end(self):
|
||||
try:
|
||||
self.end_page()
|
||||
self.pdf.end()
|
||||
except:
|
||||
self.errors(traceback.format_exc())
|
||||
self.errors_occurred = True
|
||||
return False
|
||||
finally:
|
||||
self.pdf = self.file_object = None
|
||||
return True
|
||||
|
||||
def type(self):
|
||||
return QPaintEngine.Pdf
|
||||
|
||||
def add_image(self, img, cache_key):
|
||||
if img.isNull():
|
||||
return
|
||||
return self.pdf.add_image(img, cache_key)
|
||||
|
||||
@store_error
|
||||
def drawTiledPixmap(self, rect, pixmap, point):
|
||||
self.content_written_to_current_page = 'drawTiledPixmap'
|
||||
self.apply_graphics_state()
|
||||
brush = QBrush(pixmap)
|
||||
bl = rect.topLeft()
|
||||
color, opacity, pattern, do_fill = self.graphics.convert_brush(
|
||||
brush, bl-point, 1.0, self.pdf_system,
|
||||
self.painter().transform())
|
||||
self.pdf.save_stack()
|
||||
self.pdf.apply_fill(color, pattern)
|
||||
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
||||
stroke=False, fill=True)
|
||||
self.pdf.restore_stack()
|
||||
|
||||
@store_error
|
||||
def drawPixmap(self, rect, pixmap, source_rect):
|
||||
self.content_written_to_current_page = 'drawPixmap'
|
||||
self.apply_graphics_state()
|
||||
source_rect = source_rect.toRect()
|
||||
pixmap = (pixmap if source_rect == pixmap.rect() else
|
||||
pixmap.copy(source_rect))
|
||||
image = pixmap.toImage()
|
||||
ref = self.add_image(image, pixmap.cacheKey())
|
||||
if ref is not None:
|
||||
self.pdf.draw_image(rect.x(), rect.y(), rect.width(),
|
||||
rect.height(), ref)
|
||||
|
||||
@store_error
|
||||
def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor):
|
||||
self.content_written_to_current_page = 'drawImage'
|
||||
self.apply_graphics_state()
|
||||
source_rect = source_rect.toRect()
|
||||
image = (image if source_rect == image.rect() else
|
||||
image.copy(source_rect))
|
||||
ref = self.add_image(image, image.cacheKey())
|
||||
if ref is not None:
|
||||
self.pdf.draw_image(rect.x(), rect.y(), rect.width(),
|
||||
rect.height(), ref)
|
||||
|
||||
@store_error
|
||||
def updateState(self, state):
|
||||
self.graphics.update_state(state, self.painter())
|
||||
|
||||
@store_error
|
||||
def drawPath(self, path):
|
||||
self.content_written_to_current_page = 'drawPath'
|
||||
self.apply_graphics_state()
|
||||
p = convert_path(path)
|
||||
fill_rule = {Qt.OddEvenFill:'evenodd',
|
||||
Qt.WindingFill:'winding'}[path.fillRule()]
|
||||
self.pdf.draw_path(p, stroke=self.do_stroke,
|
||||
fill=self.do_fill, fill_rule=fill_rule)
|
||||
|
||||
@store_error
|
||||
def drawPoints(self, points):
|
||||
self.content_written_to_current_page = 'drawPoints'
|
||||
self.apply_graphics_state()
|
||||
p = Path()
|
||||
for point in points:
|
||||
p.move_to(point.x(), point.y())
|
||||
p.line_to(point.x(), point.y() + 0.001)
|
||||
self.pdf.draw_path(p, stroke=self.do_stroke, fill=False)
|
||||
|
||||
@store_error
|
||||
def drawRects(self, rects):
|
||||
self.apply_graphics_state()
|
||||
with self.graphics:
|
||||
for rect in rects:
|
||||
self.resolve_fill(rect)
|
||||
bl = rect.topLeft()
|
||||
if self.do_stroke or self.do_fill:
|
||||
self.content_written_to_current_page = 'drawRects'
|
||||
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
||||
stroke=self.do_stroke, fill=self.do_fill)
|
||||
|
||||
def create_sfnt(self, text_item):
|
||||
get_table = partial(self.qt_hack.get_sfnt_table, text_item)
|
||||
try:
|
||||
ans = Font(Sfnt(get_table))
|
||||
except UnsupportedFont as e:
|
||||
raise UnsupportedFont('The font %s is not a valid sfnt. Error: %s'%(
|
||||
text_item.font().family(), e))
|
||||
glyph_map = self.qt_hack.get_glyph_map(text_item)
|
||||
gm = {}
|
||||
ans.ignore_glyphs = set()
|
||||
for uc, glyph_id in enumerate(glyph_map):
|
||||
if glyph_id not in gm:
|
||||
gm[glyph_id] = codepoint_to_chr(uc)
|
||||
if uc in (0xad, 0x200b):
|
||||
ans.ignore_glyphs.add(glyph_id)
|
||||
ans.full_glyph_map = gm
|
||||
return ans
|
||||
|
||||
@store_error
|
||||
def drawTextItem(self, point, text_item):
|
||||
# return super(PdfEngine, self).drawTextItem(point, text_item)
|
||||
self.apply_graphics_state()
|
||||
gi = GlyphInfo(*self.qt_hack.get_glyphs(point, text_item))
|
||||
if not gi.indices:
|
||||
return
|
||||
metrics = self.fonts.get(gi.name)
|
||||
if metrics is None:
|
||||
from calibre.utils.fonts.utils import get_all_font_names
|
||||
try:
|
||||
names = get_all_font_names(gi.name, True)
|
||||
names = ' '.join('%s=%s'%(k, names[k]) for k in sorted(names))
|
||||
except Exception:
|
||||
names = 'Unknown'
|
||||
self.debug('Loading font: %s' % names)
|
||||
try:
|
||||
self.fonts[gi.name] = metrics = self.create_sfnt(text_item)
|
||||
except UnsupportedFont:
|
||||
self.debug('Failed to load font: %s, drawing text as outlines...' % names)
|
||||
return super(PdfEngine, self).drawTextItem(point, text_item)
|
||||
indices, positions = [], []
|
||||
ignore_glyphs = metrics.ignore_glyphs
|
||||
for glyph_id, gpos in zip(gi.indices, gi.positions):
|
||||
if glyph_id not in ignore_glyphs:
|
||||
indices.append(glyph_id), positions.append(gpos)
|
||||
for glyph_id in indices:
|
||||
try:
|
||||
metrics.glyph_map[glyph_id] = metrics.full_glyph_map[glyph_id]
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
glyphs = []
|
||||
last_x = last_y = 0
|
||||
for glyph_index, (x, y) in zip(indices, positions):
|
||||
glyphs.append((x-last_x, last_y - y, glyph_index))
|
||||
last_x, last_y = x, y
|
||||
|
||||
if not self.content_written_to_current_page:
|
||||
dy = self.graphics.current_state.transform.dy()
|
||||
ypositions = [y + dy for x, y in positions]
|
||||
miny = min(ypositions or (0,))
|
||||
maxy = max(ypositions or (self.pixel_height,))
|
||||
page_top = self.header_height if self.has_headers else 0
|
||||
page_bottom = self.pixel_height - (self.footer_height if self.has_footers else 0)
|
||||
if page_top <= miny <= page_bottom or page_top <= maxy <= page_bottom:
|
||||
self.content_written_to_current_page = 'drawTextItem'
|
||||
else:
|
||||
self.debug('Text in header/footer: miny=%s maxy=%s page_top=%s page_bottom=%s'% (
|
||||
miny, maxy, page_top, page_bottom))
|
||||
self.pdf.draw_glyph_run([gi.stretch, 0, 0, -1, 0, 0], gi.size, metrics,
|
||||
glyphs)
|
||||
|
||||
@store_error
|
||||
def drawPolygon(self, points, mode):
|
||||
self.content_written_to_current_page = 'drawPolygon'
|
||||
self.apply_graphics_state()
|
||||
if not points:
|
||||
return
|
||||
p = Path()
|
||||
p.move_to(points[0].x(), points[0].y())
|
||||
for point in points[1:]:
|
||||
p.line_to(point.x(), point.y())
|
||||
p.close()
|
||||
fill_rule = {self.OddEvenMode:'evenodd',
|
||||
self.WindingMode:'winding'}.get(mode, 'evenodd')
|
||||
self.pdf.draw_path(p, stroke=True, fill_rule=fill_rule,
|
||||
fill=(mode in (self.OddEvenMode, self.WindingMode, self.ConvexMode)))
|
||||
|
||||
def set_metadata(self, *args, **kwargs):
|
||||
self.pdf.set_metadata(*args, **kwargs)
|
||||
|
||||
def add_outline(self, toc):
|
||||
self.pdf.links.add_outline(toc)
|
||||
|
||||
def add_links(self, current_item, start_page, links, anchors):
|
||||
for pos in itervalues(anchors):
|
||||
pos['left'], pos['top'] = self.pdf_system.map(pos['left'], pos['top'])
|
||||
for link in links:
|
||||
pos = link[1]
|
||||
llx = pos['left']
|
||||
lly = pos['top'] + pos['height']
|
||||
urx = pos['left'] + pos['width']
|
||||
ury = pos['top']
|
||||
llx, lly = self.pdf_system.map(llx, lly)
|
||||
urx, ury = self.pdf_system.map(urx, ury)
|
||||
link[1] = pos['column'] + start_page
|
||||
link.append((llx, lly, urx, ury))
|
||||
self.pdf.links.add(current_item, start_page, links, anchors)
|
||||
|
||||
|
||||
class PdfDevice(QPaintDevice): # {{{
|
||||
|
||||
def __init__(self, file_object, page_size=A4, left_margin=inch,
|
||||
top_margin=inch, right_margin=inch, bottom_margin=inch,
|
||||
xdpi=1200, ydpi=1200, errors=print, debug=print,
|
||||
compress=True, mark_links=False, opts=None, page_margins=(0, 0, 0, 0)):
|
||||
QPaintDevice.__init__(self)
|
||||
self.xdpi, self.ydpi = xdpi, ydpi
|
||||
self.page_width, self.page_height = page_size
|
||||
self.body_width = self.page_width - left_margin - right_margin
|
||||
self.body_height = self.page_height - top_margin - bottom_margin
|
||||
self.left_margin, self.right_margin = left_margin, right_margin
|
||||
self.top_margin, self.bottom_margin = top_margin, bottom_margin
|
||||
self.engine = PdfEngine(file_object, self.page_width, self.page_height,
|
||||
left_margin, top_margin, right_margin,
|
||||
bottom_margin, self.width(), self.height(),
|
||||
errors=errors, debug=debug, compress=compress,
|
||||
mark_links=mark_links, opts=opts, page_margins=page_margins)
|
||||
self.add_outline = self.engine.add_outline
|
||||
self.add_links = self.engine.add_links
|
||||
|
||||
def paintEngine(self):
|
||||
return self.engine
|
||||
|
||||
def metric(self, m):
|
||||
if m in (self.PdmDpiX, self.PdmPhysicalDpiX):
|
||||
return self.xdpi
|
||||
if m in (self.PdmDpiY, self.PdmPhysicalDpiY):
|
||||
return self.ydpi
|
||||
if m == self.PdmDepth:
|
||||
return 32
|
||||
if m == self.PdmNumColors:
|
||||
return sys.maxsize
|
||||
if m == self.PdmWidthMM:
|
||||
return int(round(self.body_width * 0.35277777777778))
|
||||
if m == self.PdmHeightMM:
|
||||
return int(round(self.body_height * 0.35277777777778))
|
||||
if m == self.PdmWidth:
|
||||
return int(round(self.body_width * self.xdpi / 72.0))
|
||||
if m == self.PdmHeight:
|
||||
return int(round(self.body_height * self.ydpi / 72.0))
|
||||
return 0
|
||||
|
||||
def end_page(self, *args, **kwargs):
|
||||
self.engine.end_page(*args, **kwargs)
|
||||
|
||||
def init_page(self, custom_margins=None):
|
||||
self.engine.init_page(custom_margins=custom_margins)
|
||||
|
||||
@property
|
||||
def full_page_rect(self):
|
||||
page_width = int(math.ceil(self.page_width * self.xdpi / 72.0))
|
||||
lm = int(math.ceil(self.left_margin * self.xdpi / 72.0))
|
||||
page_height = int(math.ceil(self.page_height * self.ydpi / 72.0))
|
||||
tm = int(math.ceil(self.top_margin * self.ydpi / 72.0))
|
||||
return (-lm, -tm, page_width+1, page_height+1)
|
||||
|
||||
@property
|
||||
def current_page_num(self):
|
||||
return self.engine.current_page_num
|
||||
|
||||
@property
|
||||
def errors_occurred(self):
|
||||
return self.engine.errors_occurred
|
||||
|
||||
def to_px(self, pt, vertical=True):
|
||||
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)
|
||||
|
||||
# }}}
|
@ -1,565 +0,0 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import json, os, numbers
|
||||
from math import floor
|
||||
from collections import defaultdict
|
||||
|
||||
from PyQt5.Qt import (
|
||||
QObject, QPainter, Qt, QSize, QTimer, QEventLoop, QPixmap, QRect, pyqtSlot)
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||
|
||||
from calibre import fit_image
|
||||
from calibre.constants import iswindows
|
||||
from calibre.ebooks.oeb.display.webview import load_html
|
||||
from calibre.ebooks.pdf.render.common import (inch, cm, mm, pica, cicero,
|
||||
didot, PAPER_SIZES, current_log)
|
||||
from calibre.ebooks.pdf.render.engine import PdfDevice
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.utils.resources import load_hyphenator_dicts
|
||||
from calibre.utils.monotonic import monotonic
|
||||
from polyglot.builtins import iteritems, itervalues, map, unicode_type
|
||||
|
||||
|
||||
def get_page_size(opts, for_comic=False): # {{{
|
||||
use_profile = opts.use_profile_size and opts.output_profile.short_name != 'default' and opts.output_profile.width <= 9999
|
||||
if use_profile:
|
||||
w = (opts.output_profile.comic_screen_size[0] if for_comic else
|
||||
opts.output_profile.width)
|
||||
h = (opts.output_profile.comic_screen_size[1] if for_comic else
|
||||
opts.output_profile.height)
|
||||
dpi = opts.output_profile.dpi
|
||||
factor = 72.0 / dpi
|
||||
page_size = (factor * w, factor * h)
|
||||
else:
|
||||
page_size = None
|
||||
if opts.custom_size is not None:
|
||||
width, sep, height = opts.custom_size.partition('x')
|
||||
if height:
|
||||
try:
|
||||
width = float(width.replace(',', '.'))
|
||||
height = float(height.replace(',', '.'))
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if opts.unit == 'devicepixel':
|
||||
factor = 72.0 / opts.output_profile.dpi
|
||||
else:
|
||||
factor = {'point':1.0, 'inch':inch, 'cicero':cicero,
|
||||
'didot':didot, 'pica':pica, 'millimeter':mm,
|
||||
'centimeter':cm}[opts.unit]
|
||||
page_size = (factor*width, factor*height)
|
||||
if page_size is None:
|
||||
page_size = PAPER_SIZES[opts.paper_size]
|
||||
return page_size
|
||||
# }}}
|
||||
|
||||
|
||||
class Page(QWebPage): # {{{
|
||||
|
||||
def __init__(self, opts, log):
|
||||
from calibre.gui2 import secure_web_page
|
||||
self.log = log
|
||||
QWebPage.__init__(self)
|
||||
settings = self.settings()
|
||||
settings.setFontSize(QWebSettings.DefaultFontSize,
|
||||
opts.pdf_default_font_size)
|
||||
settings.setFontSize(QWebSettings.DefaultFixedFontSize,
|
||||
opts.pdf_mono_font_size)
|
||||
settings.setFontSize(QWebSettings.MinimumLogicalFontSize, 8)
|
||||
settings.setFontSize(QWebSettings.MinimumFontSize, 8)
|
||||
secure_web_page(settings)
|
||||
|
||||
std = {'serif':opts.pdf_serif_family, 'sans':opts.pdf_sans_family,
|
||||
'mono':opts.pdf_mono_family}.get(opts.pdf_standard_font,
|
||||
opts.pdf_serif_family)
|
||||
if std:
|
||||
settings.setFontFamily(QWebSettings.StandardFont, std)
|
||||
if opts.pdf_serif_family:
|
||||
settings.setFontFamily(QWebSettings.SerifFont, opts.pdf_serif_family)
|
||||
if opts.pdf_sans_family:
|
||||
settings.setFontFamily(QWebSettings.SansSerifFont,
|
||||
opts.pdf_sans_family)
|
||||
if opts.pdf_mono_family:
|
||||
settings.setFontFamily(QWebSettings.FixedFont, opts.pdf_mono_family)
|
||||
self.longjs_counter = 0
|
||||
|
||||
def javaScriptConsoleMessage(self, msg, lineno, msgid):
|
||||
self.log.debug(u'JS:', unicode_type(msg))
|
||||
|
||||
def javaScriptAlert(self, frame, msg):
|
||||
self.log(unicode_type(msg))
|
||||
|
||||
@pyqtSlot(result=bool)
|
||||
def shouldInterruptJavaScript(self):
|
||||
if self.longjs_counter < 10:
|
||||
self.log('Long running javascript, letting it proceed')
|
||||
self.longjs_counter += 1
|
||||
return False
|
||||
self.log.warn('Long running javascript, aborting it')
|
||||
return True
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
def draw_image_page(page_rect, painter, p, preserve_aspect_ratio=True):
|
||||
if preserve_aspect_ratio:
|
||||
aspect_ratio = float(p.width())/p.height()
|
||||
nw, nh = page_rect.width(), page_rect.height()
|
||||
if aspect_ratio > 1:
|
||||
nh = int(page_rect.width()/aspect_ratio)
|
||||
else: # Width is smaller than height
|
||||
nw = page_rect.height()*aspect_ratio
|
||||
__, nnw, nnh = fit_image(nw, nh, page_rect.width(),
|
||||
page_rect.height())
|
||||
dx = int((page_rect.width() - nnw)/2.)
|
||||
dy = int((page_rect.height() - nnh)/2.)
|
||||
page_rect.translate(dx, dy)
|
||||
page_rect.setHeight(nnh)
|
||||
page_rect.setWidth(nnw)
|
||||
painter.drawPixmap(page_rect, p, p.rect())
|
||||
|
||||
|
||||
class PDFWriter(QObject):
|
||||
|
||||
@pyqtSlot(result=unicode_type)
|
||||
def title(self):
|
||||
return self.doc_title
|
||||
|
||||
@pyqtSlot(result=unicode_type)
|
||||
def author(self):
|
||||
return self.doc_author
|
||||
|
||||
@pyqtSlot(result=unicode_type)
|
||||
def section(self):
|
||||
return self.current_section
|
||||
|
||||
@pyqtSlot(result=unicode_type)
|
||||
def tl_section(self):
|
||||
return self.current_tl_section
|
||||
|
||||
def __init__(self, opts, log, cover_data=None, toc=None):
|
||||
from calibre.gui2 import must_use_qt
|
||||
must_use_qt()
|
||||
QObject.__init__(self)
|
||||
|
||||
self.logger = self.log = log
|
||||
self.mathjax_dir = P('mathjax', allow_user_override=False)
|
||||
current_log(log)
|
||||
self.opts = opts
|
||||
self.cover_data = cover_data
|
||||
self.paged_js = None
|
||||
self.toc = toc
|
||||
|
||||
self.loop = QEventLoop()
|
||||
self.view = QWebView()
|
||||
self.page = Page(opts, self.log)
|
||||
self.view.setPage(self.page)
|
||||
self.view.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform)
|
||||
self.view.loadFinished.connect(self.render_html,
|
||||
type=Qt.QueuedConnection)
|
||||
self.view.loadProgress.connect(self.load_progress)
|
||||
self.ignore_failure = None
|
||||
self.hang_check_timer = t = QTimer(self)
|
||||
t.timeout.connect(self.hang_check)
|
||||
t.setInterval(1000)
|
||||
|
||||
for x in (Qt.Horizontal, Qt.Vertical):
|
||||
self.view.page().mainFrame().setScrollBarPolicy(x,
|
||||
Qt.ScrollBarAlwaysOff)
|
||||
self.report_progress = lambda x, y: x
|
||||
self.current_section = ''
|
||||
self.current_tl_section = ''
|
||||
|
||||
def dump(self, items, out_stream, pdf_metadata):
|
||||
opts = self.opts
|
||||
page_size = get_page_size(self.opts)
|
||||
xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY()
|
||||
|
||||
def margin(which):
|
||||
val = getattr(opts, 'pdf_page_margin_' + which)
|
||||
if val == 0.0:
|
||||
val = getattr(opts, 'margin_' + which)
|
||||
return val
|
||||
ml, mr, mt, mb = map(margin, 'left right top bottom'.split())
|
||||
# We cannot set the side margins in the webview as there is no right
|
||||
# margin for the last page (the margins are implemented with
|
||||
# -webkit-column-gap)
|
||||
self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml,
|
||||
top_margin=0, right_margin=mr, bottom_margin=0,
|
||||
xdpi=xdpi, ydpi=ydpi, errors=self.log.error,
|
||||
debug=self.log.debug, compress=not
|
||||
opts.uncompressed_pdf, opts=opts,
|
||||
mark_links=opts.pdf_mark_links, page_margins=(ml, mr, mt, mb))
|
||||
self.footer = opts.pdf_footer_template
|
||||
if self.footer:
|
||||
self.footer = self.footer.strip()
|
||||
if not self.footer and opts.pdf_page_numbers:
|
||||
self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>'
|
||||
self.header = opts.pdf_header_template
|
||||
if self.header:
|
||||
self.header = self.header.strip()
|
||||
min_margin = 1.5 * opts._final_base_font_size
|
||||
if self.footer and mb < min_margin:
|
||||
self.log.warn('Bottom margin is too small for footer, increasing it to %.1fpts' % min_margin)
|
||||
mb = min_margin
|
||||
if self.header and mt < min_margin:
|
||||
self.log.warn('Top margin is too small for header, increasing it to %.1fpts' % min_margin)
|
||||
mt = min_margin
|
||||
|
||||
self.page.setViewportSize(QSize(self.doc.width(), self.doc.height()))
|
||||
self.render_queue = items
|
||||
self.total_items = len(items)
|
||||
|
||||
mt, mb = map(self.doc.to_px, (mt, mb))
|
||||
self.margin_top, self.margin_bottom = map(lambda x:int(floor(x)), (mt, mb))
|
||||
|
||||
self.painter = QPainter(self.doc)
|
||||
try:
|
||||
self.book_language = pdf_metadata.mi.languages[0]
|
||||
except Exception:
|
||||
self.book_language = 'eng'
|
||||
self.doc.set_metadata(title=pdf_metadata.title,
|
||||
author=pdf_metadata.author,
|
||||
tags=pdf_metadata.tags, mi=pdf_metadata.mi)
|
||||
self.doc_title = pdf_metadata.title
|
||||
self.doc_author = pdf_metadata.author
|
||||
self.painter.save()
|
||||
try:
|
||||
if self.cover_data is not None:
|
||||
p = QPixmap()
|
||||
try:
|
||||
p.loadFromData(self.cover_data)
|
||||
except TypeError:
|
||||
self.log.warn('This ebook does not have a raster cover, cannot generate cover for PDF'
|
||||
'. Cover type: %s' % type(self.cover_data))
|
||||
if not p.isNull():
|
||||
self.doc.init_page()
|
||||
draw_image_page(QRect(*self.doc.full_page_rect),
|
||||
self.painter, p,
|
||||
preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio)
|
||||
self.doc.end_page()
|
||||
finally:
|
||||
self.painter.restore()
|
||||
|
||||
QTimer.singleShot(0, self.render_book)
|
||||
if self.loop.exec_() == 1:
|
||||
raise Exception('PDF Output failed, see log for details')
|
||||
|
||||
if self.toc is not None and len(self.toc) > 0:
|
||||
self.doc.add_outline(self.toc)
|
||||
|
||||
self.painter.end()
|
||||
|
||||
if self.doc.errors_occurred:
|
||||
raise Exception('PDF Output failed, see log for details')
|
||||
|
||||
def render_inline_toc(self):
|
||||
evaljs = self.view.page().mainFrame().evaluateJavaScript
|
||||
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, evaljs)
|
||||
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.opts.pdf_add_toc and 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()
|
||||
except:
|
||||
self.logger.exception('Rendering failed')
|
||||
self.loop.exit(1)
|
||||
|
||||
def render_next(self):
|
||||
item = unicode_type(self.render_queue.pop(0))
|
||||
|
||||
self.logger.debug('Processing %s...' % item)
|
||||
self.current_item = item
|
||||
load_html(item, self.view)
|
||||
self.last_load_progress_at = monotonic()
|
||||
self.hang_check_timer.start()
|
||||
|
||||
def load_progress(self, progress):
|
||||
self.last_load_progress_at = monotonic()
|
||||
|
||||
def hang_check(self):
|
||||
if monotonic() - self.last_load_progress_at > 60:
|
||||
self.log.warn('Timed out waiting for %s to render' % self.current_item)
|
||||
self.ignore_failure = self.current_item
|
||||
self.view.stop()
|
||||
|
||||
def render_html(self, ok):
|
||||
self.hang_check_timer.stop()
|
||||
if self.ignore_failure == self.current_item:
|
||||
ok = True
|
||||
self.ignore_failure = None
|
||||
if ok:
|
||||
try:
|
||||
self.do_paged_render()
|
||||
except:
|
||||
self.log.exception('Rendering failed')
|
||||
self.loop.exit(1)
|
||||
return
|
||||
else:
|
||||
# The document is so corrupt that we can't render the page.
|
||||
self.logger.error('Document %s cannot be rendered.' % self.current_item)
|
||||
self.loop.exit(1)
|
||||
return
|
||||
done = self.total_items - len(self.render_queue)
|
||||
self.report_progress(done/self.total_items,
|
||||
_('Rendered %s'%os.path.basename(self.current_item)))
|
||||
self.render_book()
|
||||
|
||||
@property
|
||||
def current_page_num(self):
|
||||
return self.doc.current_page_num
|
||||
|
||||
def load_mathjax(self):
|
||||
evaljs = self.view.page().mainFrame().evaluateJavaScript
|
||||
mjpath = self.mathjax_dir.replace(os.sep, '/')
|
||||
if iswindows:
|
||||
mjpath = u'/' + mjpath
|
||||
if bool(evaljs('''
|
||||
window.mathjax.base = %s;
|
||||
mathjax.check_for_math(); mathjax.math_present
|
||||
'''%(json.dumps(mjpath, ensure_ascii=False)))):
|
||||
self.log.debug('Math present, loading MathJax')
|
||||
while not bool(evaljs('mathjax.math_loaded')):
|
||||
self.loop.processEvents(self.loop.ExcludeUserInputEvents)
|
||||
# give the MathJax fonts time to load
|
||||
for i in range(5):
|
||||
self.loop.processEvents(self.loop.ExcludeUserInputEvents)
|
||||
evaljs('document.getElementById("MathJax_Message").style.display="none";')
|
||||
|
||||
def load_header_footer_images(self):
|
||||
from calibre.utils.monotonic import monotonic
|
||||
evaljs = self.view.page().mainFrame().evaluateJavaScript
|
||||
st = monotonic()
|
||||
while not evaljs('paged_display.header_footer_images_loaded()'):
|
||||
self.loop.processEvents(self.loop.ExcludeUserInputEvents)
|
||||
if monotonic() - st > 5:
|
||||
self.log.warn('Header and footer images have not loaded in 5 seconds, ignoring')
|
||||
break
|
||||
|
||||
def get_sections(self, anchor_map, only_top_level=False):
|
||||
sections = defaultdict(list)
|
||||
ci = os.path.abspath(os.path.normcase(self.current_item))
|
||||
if self.toc is not None:
|
||||
tocentries = self.toc.top_level_items() if only_top_level else self.toc.flat()
|
||||
for toc in tocentries:
|
||||
path = toc.abspath or None
|
||||
frag = toc.fragment or None
|
||||
if path is None:
|
||||
continue
|
||||
path = os.path.abspath(os.path.normcase(path))
|
||||
if path == ci:
|
||||
col = 0
|
||||
if frag and frag in anchor_map:
|
||||
col = anchor_map[frag]['column']
|
||||
sections[col].append(toc.text or _('Untitled'))
|
||||
|
||||
return sections
|
||||
|
||||
def hyphenate(self, evaljs):
|
||||
evaljs(u'''\
|
||||
Hyphenator.config(
|
||||
{
|
||||
'minwordlength' : 6,
|
||||
// 'hyphenchar' : '|',
|
||||
'displaytogglebox' : false,
|
||||
'remoteloading' : false,
|
||||
'doframes' : true,
|
||||
'defaultlanguage' : 'en',
|
||||
'storagetype' : 'session',
|
||||
'onerrorhandler' : function (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
Hyphenator.hyphenate(document.body, "%s");
|
||||
''' % 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
|
||||
from calibre.utils.resources import compiled_coffeescript as cc
|
||||
self.paged_js = cc('ebooks.oeb.display.utils').decode('utf-8')
|
||||
self.paged_js += cc('ebooks.oeb.display.indexing').decode('utf-8')
|
||||
self.paged_js += cc('ebooks.oeb.display.paged').decode('utf-8')
|
||||
self.paged_js += cc('ebooks.oeb.display.mathjax').decode('utf-8')
|
||||
if self.opts.pdf_hyphenate:
|
||||
self.paged_js += P('viewer/hyphenate/Hyphenator.js', data=True).decode('utf-8')
|
||||
hjs, self.hyphenate_lang = load_hyphenator_dicts({}, self.book_language)
|
||||
self.paged_js += hjs
|
||||
self.hf_uuid = unicode_type(uuid.uuid4()).replace('-', '')
|
||||
|
||||
self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self)
|
||||
self.view.page().longjs_counter = 0
|
||||
evaljs = self.view.page().mainFrame().evaluateJavaScript
|
||||
evaljs(self.paged_js)
|
||||
self.load_mathjax()
|
||||
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 iteritems(doc_margins) if isinstance(v, numbers.Number) 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";
|
||||
// Qt WebKit cannot handle opacity with the Pdf backend
|
||||
s = document.createElement('style');
|
||||
s.textContent = '* {opacity: 1 !important}';
|
||||
document.documentElement.appendChild(s);
|
||||
paged_display.set_geometry(1, %d, %d, %d);
|
||||
paged_display.layout();
|
||||
paged_display.fit_images();
|
||||
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);
|
||||
'''%(margin_top, 0, margin_bottom)))
|
||||
|
||||
if not isinstance(amap, dict):
|
||||
amap = {'links':[], 'anchors':{}} # Some javascript error occurred
|
||||
for val in itervalues(amap['anchors']):
|
||||
if isinstance(val, dict) and 'column' in val:
|
||||
val['column'] = int(val['column'])
|
||||
for href, val in amap['links']:
|
||||
if isinstance(val, dict) and 'column' in val:
|
||||
val['column'] = int(val['column'])
|
||||
sections = self.get_sections(amap['anchors'])
|
||||
tl_sections = self.get_sections(amap['anchors'], True)
|
||||
col = 0
|
||||
|
||||
if self.header:
|
||||
evaljs('paged_display.header_template = ' + json.dumps(self.header))
|
||||
if self.footer:
|
||||
evaljs('paged_display.footer_template = ' + json.dumps(self.footer))
|
||||
if self.header or self.footer:
|
||||
evaljs('paged_display.create_header_footer("%s");'%self.hf_uuid)
|
||||
|
||||
start_page = self.current_page_num
|
||||
|
||||
mf = self.view.page().mainFrame()
|
||||
|
||||
def set_section(col, sections, attr):
|
||||
# If this page has no section, use the section from the previous page
|
||||
idx = col if col in sections else col - 1 if col - 1 in sections else None
|
||||
if idx is not None:
|
||||
setattr(self, attr, sections[idx][0])
|
||||
|
||||
from calibre.ebooks.pdf.render.toc import calculate_page_number
|
||||
|
||||
while True:
|
||||
set_section(col, sections, 'current_section')
|
||||
set_section(col, tl_sections, 'current_tl_section')
|
||||
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 evaljs('paged_display.update_header_footer(%d)'%num) is True:
|
||||
self.load_header_footer_images()
|
||||
|
||||
self.painter.save()
|
||||
mf.render(self.painter, mf.ContentsLayer)
|
||||
self.painter.restore()
|
||||
try:
|
||||
nsl = int(evaljs('paged_display.next_screen_location()'))
|
||||
except (TypeError, ValueError):
|
||||
break
|
||||
self.doc.end_page(nsl <= 0)
|
||||
if nsl <= 0:
|
||||
break
|
||||
evaljs('window.scrollTo(%d, 0); paged_display.position_header_footer();'%nsl)
|
||||
if self.doc.errors_occurred:
|
||||
break
|
||||
col += 1
|
||||
|
||||
if not self.doc.errors_occurred and self.doc.current_page_num > 1:
|
||||
self.doc.add_links(self.current_item, start_page, amap['links'],
|
||||
amap['anchors'])
|
||||
|
||||
|
||||
class ImagePDFWriter(object):
|
||||
|
||||
def __init__(self, opts, log, cover_data=None, toc=None):
|
||||
from calibre.gui2 import must_use_qt
|
||||
must_use_qt()
|
||||
|
||||
self.logger = self.log = log
|
||||
self.opts = opts
|
||||
self.cover_data = cover_data
|
||||
self.toc = toc
|
||||
|
||||
def dump(self, items, out_stream, pdf_metadata):
|
||||
opts = self.opts
|
||||
page_size = get_page_size(self.opts)
|
||||
ml, mr = opts.margin_left, opts.margin_right
|
||||
self.doc = PdfDevice(
|
||||
out_stream, page_size=page_size, left_margin=ml,
|
||||
top_margin=opts.margin_top, right_margin=mr,
|
||||
bottom_margin=opts.margin_bottom,
|
||||
errors=self.log.error, debug=self.log.debug, compress=not
|
||||
opts.uncompressed_pdf, opts=opts, mark_links=opts.pdf_mark_links)
|
||||
self.painter = QPainter(self.doc)
|
||||
self.doc.set_metadata(title=pdf_metadata.title,
|
||||
author=pdf_metadata.author,
|
||||
tags=pdf_metadata.tags, mi=pdf_metadata.mi)
|
||||
self.doc_title = pdf_metadata.title
|
||||
self.doc_author = pdf_metadata.author
|
||||
|
||||
for imgpath in items:
|
||||
self.log.debug('Processing %s...' % imgpath)
|
||||
self.doc.init_page()
|
||||
p = QPixmap()
|
||||
with lopen(imgpath, 'rb') as f:
|
||||
if not p.loadFromData(f.read()):
|
||||
raise ValueError('Could not read image from: {}'.format(imgpath))
|
||||
draw_image_page(QRect(*self.doc.full_page_rect),
|
||||
self.painter, p,
|
||||
preserve_aspect_ratio=True)
|
||||
self.doc.end_page()
|
||||
if self.toc is not None and len(self.toc) > 0:
|
||||
self.doc.add_outline(self.toc)
|
||||
|
||||
self.painter.end()
|
||||
|
||||
if self.doc.errors_occurred:
|
||||
raise Exception('PDF Output failed, see log for details')
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
* qt_hack.cpp
|
||||
* Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "qt_hack.h"
|
||||
|
||||
#include <QtEndian>
|
||||
|
||||
#include "private/qtextengine_p.h"
|
||||
#include "private/qfontengine_p.h"
|
||||
|
||||
#if PY_MAJOR_VERSION > 2
|
||||
#define BYTES_FMT "y#"
|
||||
#else
|
||||
#define BYTES_FMT "s#"
|
||||
#endif
|
||||
|
||||
PyObject* get_glyphs(const QPointF &p, const QTextItem &text_item) {
|
||||
const quint32 *tag = reinterpret_cast<const quint32 *>("name");
|
||||
QTextItemInt ti = static_cast<const QTextItemInt &>(text_item);
|
||||
QFontEngine *fe = ti.fontEngine;
|
||||
qreal size = ti.fontEngine->fontDef.pixelSize;
|
||||
#ifdef Q_WS_WIN
|
||||
if (false && ti.fontEngine->type() == QFontEngine::Win) {
|
||||
// This is used in the Qt sourcecode, but it gives incorrect results,
|
||||
// so I have disabled it. I dont understand how it works in qpdf.cpp
|
||||
QFontEngineWin *fe = static_cast<QFontEngineWin *>(ti.fontEngine);
|
||||
// I think this should be tmHeight - tmInternalLeading, but pixelSize
|
||||
// seems to work on windows as well, so leave it as pixelSize
|
||||
size = fe->tm.tmHeight;
|
||||
}
|
||||
#endif
|
||||
int synthesized = ti.fontEngine->synthesized();
|
||||
qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
|
||||
|
||||
QVarLengthArray<glyph_t> glyphs;
|
||||
QVarLengthArray<QFixedPoint> positions;
|
||||
QTransform m = QTransform::fromTranslate(p.x(), p.y());
|
||||
fe->getGlyphPositions(ti.glyphs, m, ti.flags, glyphs, positions);
|
||||
|
||||
PyObject *points = NULL, *indices = NULL, *temp = NULL;
|
||||
|
||||
points = PyTuple_New(positions.count());
|
||||
if (points == NULL) return PyErr_NoMemory();
|
||||
for (int i = 0; i < positions.count(); i++) {
|
||||
temp = Py_BuildValue("dd", positions[i].x.toReal()/stretch, positions[i].y.toReal());
|
||||
if (temp == NULL) { Py_DECREF(points); return NULL; }
|
||||
PyTuple_SET_ITEM(points, i, temp); temp = NULL;
|
||||
}
|
||||
|
||||
indices = PyTuple_New(glyphs.count());
|
||||
if (indices == NULL) { Py_DECREF(points); return PyErr_NoMemory(); }
|
||||
for (int i = 0; i < glyphs.count(); i++) {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
temp = PyLong_FromLong((long)glyphs[i]);
|
||||
#else
|
||||
temp = PyInt_FromLong((long)glyphs[i]);
|
||||
#endif
|
||||
if (temp == NULL) { Py_DECREF(indices); Py_DECREF(points); return PyErr_NoMemory(); }
|
||||
PyTuple_SET_ITEM(indices, i, temp); temp = NULL;
|
||||
}
|
||||
const QByteArray table(fe->getSfntTable(qToBigEndian(*tag)));
|
||||
return Py_BuildValue(BYTES_FMT "ffOO", table.constData(), table.size(), size, stretch, points, indices);
|
||||
}
|
||||
|
||||
PyObject* get_sfnt_table(const QTextItem &text_item, const char* tag_name) {
|
||||
QTextItemInt ti = static_cast<const QTextItemInt &>(text_item);
|
||||
const quint32 *tag = reinterpret_cast<const quint32 *>(tag_name);
|
||||
const QByteArray table(ti.fontEngine->getSfntTable(qToBigEndian(*tag)));
|
||||
return Py_BuildValue(BYTES_FMT, table.constData(), table.size());
|
||||
}
|
||||
|
||||
PyObject* get_glyph_map(const QTextItem &text_item) {
|
||||
QTextItemInt ti = static_cast<const QTextItemInt &>(text_item);
|
||||
QGlyphLayoutArray<10> glyphs;
|
||||
int nglyphs = 10;
|
||||
PyObject *t = NULL, *ans = PyTuple_New(0x10000);
|
||||
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
|
||||
for (uint uc = 0; uc < 0x10000; ++uc) {
|
||||
QChar ch(uc);
|
||||
ti.fontEngine->stringToCMap(&ch, 1, &glyphs, &nglyphs, QFontEngine::GlyphIndicesOnly);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
t = PyLong_FromLong(glyphs.glyphs[0]);
|
||||
#else
|
||||
t = PyInt_FromLong(glyphs.glyphs[0]);
|
||||
#endif
|
||||
if (t == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); }
|
||||
PyTuple_SET_ITEM(ans, uc, t); t = NULL;
|
||||
}
|
||||
return ans;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
* qt_hack.h
|
||||
* Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Per python C-API docs, Python.h must always be the first header
|
||||
#include <Python.h>
|
||||
#include <QGlyphRun>
|
||||
#include <QTextItem>
|
||||
#include <QPointF>
|
||||
|
||||
PyObject* get_glyphs(const QPointF &p, const QTextItem &text_item);
|
||||
|
||||
PyObject* get_sfnt_table(const QTextItem &text_item, const char* tag_name);
|
||||
|
||||
PyObject* get_glyph_map(const QTextItem &text_item);
|
@ -1,16 +0,0 @@
|
||||
//Define the SIP wrapper to the qt_hack code
|
||||
//Author - Kovid Goyal <kovid@kovidgoyal.net>
|
||||
|
||||
%Module(name=qt_hack)
|
||||
|
||||
%Import QtCore/QtCoremod.sip
|
||||
%Import QtGui/QtGuimod.sip
|
||||
%ModuleCode
|
||||
#include <qt_hack.h>
|
||||
%End
|
||||
|
||||
PyObject* get_glyphs(const QPointF &p, const QTextItem &text_item);
|
||||
|
||||
PyObject* get_sfnt_table(const QTextItem &text_item, const char* tag_name);
|
||||
|
||||
PyObject* get_glyph_map(const QTextItem &text_item);
|
@ -296,6 +296,7 @@ class PDFStream(object):
|
||||
self.image_cache = {}
|
||||
self.pattern_cache, self.shader_cache = {}, {}
|
||||
self.debug = debug
|
||||
self.page_size = page_size
|
||||
self.links = Links(self, mark_links, page_size)
|
||||
i = QImage(1, 1, QImage.Format_ARGB32)
|
||||
i.fill(qRgba(0, 0, 0, 255))
|
||||
@ -483,9 +484,11 @@ class PDFStream(object):
|
||||
return self.shader_cache[shader.cache_key]
|
||||
|
||||
def draw_image(self, x, y, width, height, imgref):
|
||||
self.draw_image_with_transform(imgref, scaling=(width, -height), translation=(x, y + height))
|
||||
|
||||
def draw_image_with_transform(self, imgref, translation=(0, 0), scaling=(1, 1)):
|
||||
name = self.current_page.add_image(imgref)
|
||||
self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(width),
|
||||
fmtnum(-height), fmtnum(x), fmtnum(y+height)))
|
||||
self.current_page.write('q {} 0 0 {} {} {} cm '.format(*(tuple(scaling) + tuple(translation))))
|
||||
serialize(Name(name), self.current_page)
|
||||
self.current_page.write_line(' Do Q')
|
||||
|
||||
|
@ -1,139 +0,0 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
|
||||
from PyQt5.Qt import (QBrush, QColor, QPoint, QPixmap, QPainterPath, QRectF,
|
||||
QApplication, QPainter, Qt, QImage, QLinearGradient,
|
||||
QPointF, QPen)
|
||||
QBrush, QColor, QPoint, QPixmap, QPainterPath, QRectF, Qt, QPointF
|
||||
|
||||
from calibre.ebooks.pdf.render.engine import PdfDevice
|
||||
from polyglot.builtins import range
|
||||
|
||||
|
||||
def full(p, xmax, ymax):
|
||||
p.drawRect(0, 0, xmax, ymax)
|
||||
p.drawPolyline(QPoint(0, 0), QPoint(xmax, 0), QPoint(xmax, ymax),
|
||||
QPoint(0, ymax), QPoint(0, 0))
|
||||
pp = QPainterPath()
|
||||
pp.addRect(0, 0, xmax, ymax)
|
||||
p.drawPath(pp)
|
||||
p.save()
|
||||
for i in range(3):
|
||||
col = [0, 0, 0, 200]
|
||||
col[i] = 255
|
||||
p.setOpacity(0.3)
|
||||
p.fillRect(0, 0, xmax/10, xmax/10, QBrush(QColor(*col)))
|
||||
p.setOpacity(1)
|
||||
p.drawRect(0, 0, xmax/10, xmax/10)
|
||||
p.translate(xmax/10, xmax/10)
|
||||
p.scale(1, 1.5)
|
||||
p.restore()
|
||||
|
||||
# p.scale(2, 2)
|
||||
# p.rotate(45)
|
||||
p.drawPixmap(0, 0, xmax/4, xmax/4, QPixmap(I('library.png')))
|
||||
p.drawRect(0, 0, xmax/4, xmax/4)
|
||||
|
||||
f = p.font()
|
||||
f.setPointSize(20)
|
||||
# f.setLetterSpacing(f.PercentageSpacing, 200)
|
||||
f.setUnderline(True)
|
||||
# f.setOverline(True)
|
||||
# f.setStrikeOut(True)
|
||||
f.setFamily('Calibri')
|
||||
p.setFont(f)
|
||||
# p.setPen(QColor(0, 0, 255))
|
||||
# p.scale(2, 2)
|
||||
# p.rotate(45)
|
||||
p.drawText(QPoint(xmax/3.9, 30), 'Some—text not By’s ū --- Д AV ff ff')
|
||||
|
||||
b = QBrush(Qt.HorPattern)
|
||||
b.setColor(QColor(Qt.blue))
|
||||
pix = QPixmap(I('lt.png'))
|
||||
w = xmax/4
|
||||
p.fillRect(0, ymax/3, w, w, b)
|
||||
p.fillRect(xmax/3, ymax/3, w, w, QBrush(pix))
|
||||
x, y = 2*xmax/3, ymax/3
|
||||
p.drawTiledPixmap(QRectF(x, y, w, w), pix, QPointF(10, 10))
|
||||
|
||||
x, y = 1, ymax/1.9
|
||||
g = QLinearGradient(QPointF(x, y), QPointF(x+w, y+w))
|
||||
g.setColorAt(0, QColor('#00f'))
|
||||
g.setColorAt(1, QColor('#fff'))
|
||||
p.fillRect(x, y, w, w, QBrush(g))
|
||||
|
||||
|
||||
def run(dev, func):
|
||||
p = QPainter(dev)
|
||||
if isinstance(dev, PdfDevice):
|
||||
dev.init_page()
|
||||
xmax, ymax = p.viewport().width(), p.viewport().height()
|
||||
try:
|
||||
func(p, xmax, ymax)
|
||||
finally:
|
||||
p.end()
|
||||
if isinstance(dev, PdfDevice):
|
||||
if dev.engine.errors_occurred:
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def brush(p, xmax, ymax):
|
||||
x = 0
|
||||
y = 0
|
||||
w = xmax/2
|
||||
g = QLinearGradient(QPointF(x, y+w/3), QPointF(x, y+(2*w/3)))
|
||||
g.setColorAt(0, QColor('#f00'))
|
||||
g.setColorAt(0.5, QColor('#fff'))
|
||||
g.setColorAt(1, QColor('#00f'))
|
||||
g.setSpread(g.ReflectSpread)
|
||||
p.fillRect(x, y, w, w, QBrush(g))
|
||||
p.drawRect(x, y, w, w)
|
||||
|
||||
|
||||
def pen(p, xmax, ymax):
|
||||
pix = QPixmap(I('lt.png'))
|
||||
pen = QPen(QBrush(pix), 60)
|
||||
p.setPen(pen)
|
||||
p.drawRect(0, xmax/3, xmax/3, xmax/2)
|
||||
|
||||
|
||||
def text(p, xmax, ymax):
|
||||
f = p.font()
|
||||
f.setPixelSize(24)
|
||||
f.setFamily('Candara')
|
||||
p.setFont(f)
|
||||
p.drawText(QPoint(0, 100),
|
||||
'Test intra glyph spacing ffagain imceo')
|
||||
|
||||
|
||||
def main():
|
||||
app = QApplication([])
|
||||
app
|
||||
tdir = os.path.abspath('.')
|
||||
pdf = os.path.join(tdir, 'painter.pdf')
|
||||
func = full
|
||||
dpi = 100
|
||||
with open(pdf, 'wb') as f:
|
||||
dev = PdfDevice(f, xdpi=dpi, ydpi=dpi, compress=False)
|
||||
img = QImage(dev.width(), dev.height(),
|
||||
QImage.Format_ARGB32_Premultiplied)
|
||||
img.setDotsPerMeterX(dpi*39.37)
|
||||
img.setDotsPerMeterY(dpi*39.37)
|
||||
img.fill(Qt.white)
|
||||
run(dev, func)
|
||||
run(img, func)
|
||||
path = os.path.join(tdir, 'painter.png')
|
||||
img.save(path)
|
||||
print('PDF written to:', pdf)
|
||||
print('Image written to:', path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
x
Reference in New Issue
Block a user