diff --git a/src/calibre/ebooks/pdf/render/engine.py b/src/calibre/ebooks/pdf/render/engine.py index aa1fa17cc3..7987bd9c6e 100644 --- a/src/calibre/ebooks/pdf/render/engine.py +++ b/src/calibre/ebooks/pdf/render/engine.py @@ -13,9 +13,7 @@ from functools import wraps, partial from future_builtins import map import sip -from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter, - QTransform, QImage, QByteArray, QBuffer, - qRgba) +from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QTransform, QBrush) from calibre.constants import plugins from calibre.ebooks.pdf.render.serialize import (PDFStream, Path) @@ -51,11 +49,19 @@ class Font(FontMetrics): class PdfEngine(QPaintEngine): + FEATURES = QPaintEngine.AllFeatures & ~( + QPaintEngine.PorterDuff | QPaintEngine.PerspectiveTransform + | QPaintEngine.ObjectBoundingModeGradients + | QPaintEngine.LinearGradientFill + | QPaintEngine.RadialGradientFill + | QPaintEngine.ConicalGradientFill + ) + 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): - QPaintEngine.__init__(self, self.features) + 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 @@ -80,9 +86,6 @@ class PdfEngine(QPaintEngine): self.errors_occurred = False self.errors, self.debug = errors, debug self.fonts = {} - i = QImage(1, 1, QImage.Format_ARGB32) - i.fill(qRgba(0, 0, 0, 255)) - self.alpha_bit = i.constBits().asstring(4).find(b'\xff') self.current_page_num = 1 self.current_page_inited = False self.qt_hack, err = plugins['qt_hack'] @@ -107,13 +110,6 @@ class PdfEngine(QPaintEngine): self.pdf.save_stack() self.current_page_inited = True - @property - def features(self): - # gradient_flags = self.MaskedBrush | self.PatternBrush | self.PatternTransform - return (self.Antialiasing | self.AlphaBlend | self.ConstantOpacity | - self.PainterPaths | self.PaintOutsidePaintEvent | - self.PrimitiveTransform | self.PixmapTransform) #| gradient_flags - def begin(self, device): if not hasattr(self, 'pdf'): try: @@ -149,7 +145,23 @@ class PdfEngine(QPaintEngine): def type(self): return QPaintEngine.Pdf - # TODO: Tiled pixmap + 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.apply_graphics_state() + brush = QBrush(pixmap) + color, opacity, pattern, do_fill = self.graphics.convert_brush( + brush, -point, 1.0, self.pdf, self.pdf_system, + self.painter().transform()) + self.pdf.save_stack() + self.pdf.apply_fill(color, pattern) + bl = rect.topLeft() + 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): @@ -160,8 +172,8 @@ class PdfEngine(QPaintEngine): image = pixmap.toImage() ref = self.add_image(image, pixmap.cacheKey()) if ref is not None: - self.pdf.draw_image(rect.x(), rect.height()+rect.y(), rect.width(), - -rect.height(), ref) + 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): @@ -171,72 +183,8 @@ class PdfEngine(QPaintEngine): image.copy(source_rect)) ref = self.add_image(image, image.cacheKey()) if ref is not None: - self.pdf.draw_image(rect.x(), rect.height()+rect.y(), rect.width(), - -rect.height(), ref) - - def add_image(self, img, cache_key): - if img.isNull(): return - ref = self.pdf.get_image(cache_key) - if ref is not None: - return ref - - fmt = img.format() - image = QImage(img) - if (image.depth() == 1 and img.colorTable().size() == 2 and - img.colorTable().at(0) == QColor(Qt.black).rgba() and - img.colorTable().at(1) == QColor(Qt.white).rgba()): - if fmt == QImage.Format_MonoLSB: - image = image.convertToFormat(QImage.Format_Mono) - fmt = QImage.Format_Mono - else: - if (fmt != QImage.Format_RGB32 and fmt != QImage.Format_ARGB32): - image = image.convertToFormat(QImage.Format_ARGB32) - fmt = QImage.Format_ARGB32 - - w = image.width() - h = image.height() - d = image.depth() - - if fmt == QImage.Format_Mono: - bytes_per_line = (w + 7) >> 3 - data = image.constBits().asstring(bytes_per_line * h) - return self.pdf.write_image(data, w, h, d, cache_key=cache_key) - - ba = QByteArray() - buf = QBuffer(ba) - image.save(buf, 'jpeg', 94) - data = bytes(ba.data()) - has_alpha = has_mask = False - soft_mask = mask = None - - if fmt == QImage.Format_ARGB32: - tmask = image.constBits().asstring(4*w*h)[self.alpha_bit::4] - sdata = bytearray(tmask) - vals = set(sdata) - vals.discard(255) - has_mask = bool(vals) - vals.discard(0) - has_alpha = bool(vals) - - if has_alpha: - soft_mask = self.pdf.write_image(tmask, w, h, 8) - elif has_mask: - # dither the soft mask to 1bit and add it. This also helps PDF - # viewers without transparency support - bytes_per_line = (w + 7) >> 3 - mdata = bytearray(0 for i in xrange(bytes_per_line * h)) - spos = mpos = 0 - for y in xrange(h): - for x in xrange(w): - if sdata[spos]: - mdata[mpos + x>>3] |= (0x80 >> (x&7)) - spos += 1 - mpos += bytes_per_line - mdata = bytes(mdata) - mask = self.pdf.write_image(mdata, w, h, 1) - - return self.pdf.write_image(data, w, h, 32, mask=mask, dct=True, - soft_mask=soft_mask, cache_key=cache_key) + self.pdf.draw_image(rect.x(), rect.y(), rect.width(), + rect.height(), ref) @store_error def updateState(self, state): @@ -411,55 +359,4 @@ class PdfDevice(QPaintDevice): # {{{ # }}} -if __name__ == '__main__': - from PyQt4.Qt import (QBrush, QColor, QPoint, QPixmap, QPainterPath) - QBrush, QColor, QPoint, QPixmap, QPainterPath - app = QApplication([]) - p = QPainter() - with open('/t/painter.pdf', 'wb') as f: - dev = PdfDevice(f, compress=False) - p.begin(dev) - dev.init_page() - xmax, ymax = p.viewport().width(), p.viewport().height() - b = p.brush() - try: - 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 xrange(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, 2048, 2048, QPixmap(I('library.png'))) - p.drawRect(0, 0, 2048, 2048) - - 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(300, 300), 'Some—text not By’s ū --- Д AV ff ff') - finally: - p.end() - if dev.engine.errors_occurred: - raise SystemExit(1) diff --git a/src/calibre/ebooks/pdf/render/graphics.py b/src/calibre/ebooks/pdf/render/graphics.py index 68efb2514a..384809598a 100644 --- a/src/calibre/ebooks/pdf/render/graphics.py +++ b/src/calibre/ebooks/pdf/render/graphics.py @@ -9,13 +9,14 @@ __docformat__ = 'restructuredtext en' from math import sqrt -from PyQt4.Qt import (QBrush, QPen, Qt, QPointF, QTransform, QPainterPath, - QPaintEngine) +from PyQt4.Qt import ( + QBrush, QPen, Qt, QPointF, QTransform, QPainterPath, QPaintEngine, QImage) -from calibre.ebooks.pdf.render.common import Array +from calibre.ebooks.pdf.render.common import ( + Name, Array, fmtnum, Stream, Dictionary) from calibre.ebooks.pdf.render.serialize import Path, Color -def convert_path(path): +def convert_path(path): # {{{ p = Path() i = 0 while i < path.elementCount(): @@ -38,7 +39,201 @@ def convert_path(path): if not added: raise ValueError('Invalid curve to operation') return p +# }}} +class TilingPattern(Stream): + + def __init__(self, cache_key, matrix, w=8, h=8, paint_type=2, compress=False): + Stream.__init__(self, compress=compress) + self.paint_type = paint_type + self.w, self.h = w, h + self.matrix = (matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(), + matrix.dx(), matrix.dy()) + self.resources = Dictionary() + self.cache_key = (self.__class__.__name__, cache_key, self.matrix) + + def add_extra_keys(self, d): + d['Type'] = Name('Pattern') + d['PatternType'] = 1 + d['PaintType'] = self.paint_type + d['TilingType'] = 1 + d['BBox'] = Array([0, 0, self.w, self.h]) + d['XStep'] = self.w + d['YStep'] = self.h + d['Matrix'] = Array(self.matrix) + d['Resources'] = self.resources + +class QtPattern(TilingPattern): + + qt_patterns = ( # {{{ + "0 J\n" + "6 w\n" + "[] 0 d\n" + "4 0 m\n" + "4 8 l\n" + "0 4 m\n" + "8 4 l\n" + "S\n", # Dense1Pattern + + "0 J\n" + "2 w\n" + "[6 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[] 0 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[6 2] -3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # Dense2Pattern + + "0 J\n" + "2 w\n" + "[6 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] -1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[6 2] -3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # Dense3Pattern + + "0 J\n" + "2 w\n" + "[2 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] -1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[2 2] 1 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # Dense4Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] 1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[2 6] 3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # Dense5Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 6] 3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # Dense6Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n", # Dense7Pattern + + "1 w\n" + "0 4 m\n" + "8 4 l\n" + "S\n", # HorPattern + + "1 w\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # VerPattern + + "1 w\n" + "4 0 m\n" + "4 8 l\n" + "0 4 m\n" + "8 4 l\n" + "S\n", # CrossPattern + + "1 w\n" + "-1 5 m\n" + "5 -1 l\n" + "3 9 m\n" + "9 3 l\n" + "S\n", # BDiagPattern + + "1 w\n" + "-1 3 m\n" + "5 9 l\n" + "3 -1 m\n" + "9 5 l\n" + "S\n", # FDiagPattern + + "1 w\n" + "-1 3 m\n" + "5 9 l\n" + "3 -1 m\n" + "9 5 l\n" + "-1 5 m\n" + "5 -1 l\n" + "3 9 m\n" + "9 3 l\n" + "S\n", # DiagCrossPattern + ) # }}} + + def __init__(self, pattern_num, matrix): + super(QtPattern, self).__init__(pattern_num, matrix) + self.write(self.qt_patterns[pattern_num-2]) + +class TexturePattern(TilingPattern): + + def __init__(self, pixmap, matrix, pdf): + image = pixmap.toImage() + cache_key = pixmap.cacheKey() + imgref = pdf.add_image(image, cache_key) + paint_type = (2 if image.format() in {QImage.Format_MonoLSB, + QImage.Format_Mono} else 1) + super(TexturePattern, self).__init__( + cache_key, matrix, w=image.width(), h=image.height(), + paint_type=paint_type) + m = (self.w, 0, 0, -self.h, 0, self.h) + self.resources['XObject'] = Dictionary({'Texture':imgref}) + self.write_line('%s cm /Texture Do'%(' '.join(map(fmtnum, m)))) class GraphicsState(object): @@ -54,6 +249,7 @@ class GraphicsState(object): self.clip = QPainterPath() self.do_fill = False self.do_stroke = True + self.qt_pattern_cache = {} def __eq__(self, other): for x in self.FIELDS: @@ -140,6 +336,43 @@ class Graphics(object): self.current_state = self.pending_state self.pending_state = None + def convert_brush(self, brush, brush_origin, global_opacity, pdf, + pdf_system, qt_system): + # Convert a QBrush to PDF operators + style = brush.style() + + pattern = color = None + opacity = 1.0 + do_fill = True + + matrix = (QTransform.fromTranslate(brush_origin.x(), brush_origin.y()) + * pdf_system * qt_system.inverted()[0]) + vals = list(brush.color().getRgbF()) + + if style <= Qt.DiagCrossPattern: + opacity = global_opacity * vals[-1] + color = vals[:3] + + if style > Qt.SolidPattern: + pattern = pdf.add_pattern(QtPattern(style, matrix)) + + if opacity < 1e-4 or style == Qt.NoBrush: + do_fill = False + + elif style == Qt.TexturePattern: + pat = TexturePattern(brush.texture(), matrix, pdf) + opacity = global_opacity + if pat.paint_type == 2: + opacity *= vals[-1] + color = vals[:3] + pattern = pdf.add_pattern(pat) + + if opacity < 1e-4 or style == Qt.NoBrush: + do_fill = False + + # TODO: Add support for gradient fills + return color, opacity, pattern, do_fill + def apply_stroke(self, state, pdf, pdf_system, painter): # TODO: Handle pens with non solid brushes by setting the colorspace # for stroking to a pattern @@ -172,7 +405,7 @@ class Graphics(object): Qt.DashDotDotLine:[3, 2, 1, 2, 1, 2]}.get(pen.style(), []) if ps: pdf.serialize(Array(ps)) - pdf.current_page.write(' d ') + pdf.current_page.write(' 0 d ') # Stroke fill b = pen.brush() @@ -186,11 +419,8 @@ class Graphics(object): def apply_fill(self, state, pdf, pdf_system, painter): self.pending_state.do_fill = True - b = state.fill - if b.style() == Qt.NoBrush: - self.pending_state.do_fill = False - vals = list(b.color().getRgbF()) - vals[-1] *= state.opacity - color = Color(*vals) - pdf.set_fill_color(color) + color, opacity, pattern, self.pending_state.do_fill = self.convert_brush( + state.fill, state.brush_origin, state.opacity, pdf, pdf_system, + painter.transform()) + pdf.apply_fill(color, pattern, opacity) diff --git a/src/calibre/ebooks/pdf/render/serialize.py b/src/calibre/ebooks/pdf/render/serialize.py index be78ddda66..54a5f674b4 100644 --- a/src/calibre/ebooks/pdf/render/serialize.py +++ b/src/calibre/ebooks/pdf/render/serialize.py @@ -12,6 +12,8 @@ from future_builtins import map from itertools import izip from collections import namedtuple +from PyQt4.Qt import QBuffer, QByteArray, QImage, Qt, QColor, qRgba + from calibre.constants import (__appname__, __version__) from calibre.ebooks.pdf.render.common import ( Reference, EOL, serialize, Stream, Dictionary, String, Name, Array, @@ -90,6 +92,7 @@ class Page(Stream): self.opacities = {} self.fonts = {} self.xobjects = {} + self.patterns = {} def set_opacity(self, opref): if opref not in self.opacities: @@ -108,6 +111,11 @@ class Page(Stream): self.xobjects[imgref] = 'Image%d'%len(self.xobjects) return self.xobjects[imgref] + def add_pattern(self, patternref): + if patternref not in self.patterns: + self.patterns[patternref] = 'Pat%d'%len(self.patterns) + return self.patterns[patternref] + def add_resources(self): r = Dictionary() if self.opacities: @@ -125,6 +133,13 @@ class Page(Stream): for ref, name in self.xobjects.iteritems(): xobjects[name] = ref r['XObject'] = xobjects + if self.patterns: + r['ColorSpace'] = Dictionary({'PCSp':Array( + [Name('Pattern'), Name('DeviceRGB')])}) + patterns = Dictionary() + for ref, name in self.patterns.iteritems(): + patterns[name] = ref + r['Pattern'] = patterns if r: self.page_dict['Resources'] = r @@ -299,8 +314,12 @@ class PDFStream(object): self.stroke_opacities, self.fill_opacities = {}, {} self.font_manager = FontManager(self.objects, self.compress) self.image_cache = {} + self.pattern_cache = {} self.debug = debug self.links = Links(self, mark_links, page_size) + i = QImage(1, 1, QImage.Format_ARGB32) + i.fill(qRgba(0, 0, 0, 255)) + self.alpha_bit = i.constBits().asstring(4).find(b'\xff') @property def page_tree(self): @@ -380,13 +399,12 @@ class PDFStream(object): self.current_page.set_opacity(self.stroke_opacities[opacity]) self.current_page.write_line(' '.join(map(fmtnum, color[:3])) + ' SC') - def set_fill_color(self, color): - opacity = color.opacity + def set_fill_opacity(self, opacity): + opacity = float(opacity) if opacity not in self.fill_opacities: op = Dictionary({'Type':Name('ExtGState'), 'ca': opacity}) self.fill_opacities[opacity] = self.objects.add(op) self.current_page.set_opacity(self.fill_opacities[opacity]) - self.current_page.write_line(' '.join(map(fmtnum, color[:3])) + ' sc') def end_page(self): pageref = self.current_page.end(self.objects, self.stream) @@ -425,13 +443,93 @@ class PDFStream(object): self.objects.commit(r, self.stream) return r - def draw_image(self, x, y, xscale, yscale, imgref): + def add_image(self, img, cache_key): + ref = self.get_image(cache_key) + if ref is not None: + return ref + + fmt = img.format() + image = QImage(img) + if (image.depth() == 1 and img.colorTable().size() == 2 and + img.colorTable().at(0) == QColor(Qt.black).rgba() and + img.colorTable().at(1) == QColor(Qt.white).rgba()): + if fmt == QImage.Format_MonoLSB: + image = image.convertToFormat(QImage.Format_Mono) + fmt = QImage.Format_Mono + else: + if (fmt != QImage.Format_RGB32 and fmt != QImage.Format_ARGB32): + image = image.convertToFormat(QImage.Format_ARGB32) + fmt = QImage.Format_ARGB32 + + w = image.width() + h = image.height() + d = image.depth() + + if fmt == QImage.Format_Mono: + bytes_per_line = (w + 7) >> 3 + data = image.constBits().asstring(bytes_per_line * h) + return self.write_image(data, w, h, d, cache_key=cache_key) + + ba = QByteArray() + buf = QBuffer(ba) + image.save(buf, 'jpeg', 94) + data = bytes(ba.data()) + has_alpha = has_mask = False + soft_mask = mask = None + + if fmt == QImage.Format_ARGB32: + tmask = image.constBits().asstring(4*w*h)[self.alpha_bit::4] + sdata = bytearray(tmask) + vals = set(sdata) + vals.discard(255) + has_mask = bool(vals) + vals.discard(0) + has_alpha = bool(vals) + + if has_alpha: + soft_mask = self.write_image(tmask, w, h, 8) + elif has_mask: + # dither the soft mask to 1bit and add it. This also helps PDF + # viewers without transparency support + bytes_per_line = (w + 7) >> 3 + mdata = bytearray(0 for i in xrange(bytes_per_line * h)) + spos = mpos = 0 + for y in xrange(h): + for x in xrange(w): + if sdata[spos]: + mdata[mpos + x>>3] |= (0x80 >> (x&7)) + spos += 1 + mpos += bytes_per_line + mdata = bytes(mdata) + mask = self.write_image(mdata, w, h, 1) + + return self.write_image(data, w, h, 32, mask=mask, dct=True, + soft_mask=soft_mask, cache_key=cache_key) + + def add_pattern(self, pattern): + if pattern.cache_key not in self.pattern_cache: + self.pattern_cache[pattern.cache_key] = self.objects.add(pattern) + return self.current_page.add_pattern(self.pattern_cache[pattern.cache_key]) + + def draw_image(self, x, y, width, height, imgref): name = self.current_page.add_image(imgref) - self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(xscale), - fmtnum(yscale), fmtnum(x), fmtnum(y))) + self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(width), + fmtnum(-height), fmtnum(x), fmtnum(y+height))) serialize(Name(name), self.current_page) self.current_page.write_line(' Do Q') + def apply_fill(self, color=None, pattern=None, opacity=None): + if opacity is not None: + self.set_fill_opacity(opacity) + wl = self.current_page.write_line + if color is not None and pattern is None: + wl(' '.join(map(fmtnum, color)) + ' rg') + elif color is None and pattern is not None: + wl('/Pattern cs /%s scn'%pattern) + elif color is not None and pattern is not None: + col = ' '.join(map(fmtnum, color)) + wl('/PCSp cs %s /%s scn'%(col, pattern)) + def end(self): if self.current_page.getvalue(): self.end_page() diff --git a/src/calibre/ebooks/pdf/render/test.py b/src/calibre/ebooks/pdf/render/test.py new file mode 100644 index 0000000000..3ea447dfc7 --- /dev/null +++ b/src/calibre/ebooks/pdf/render/test.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os +from tempfile import gettempdir + +from PyQt4.Qt import (QBrush, QColor, QPoint, QPixmap, QPainterPath, QRectF, + QApplication, QPainter, Qt, QImage) +QBrush, QColor, QPoint, QPixmap, QPainterPath, QRectF, Qt + +from calibre.ebooks.pdf.render.engine import PdfDevice + +def full(dev): + p = QPainter(dev) + if isinstance(dev, PdfDevice): + dev.init_page() + + xmax, ymax = p.viewport().width(), p.viewport().height() + b = p.brush() + try: + 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 xrange(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('console.png')) + w = xmax/4 + p.fillRect(0, ymax/3, w, w, b) + p.fillRect(xmax/3, ymax/3, w, w, QBrush(pix)) + p.drawTiledPixmap(QRectF(2*xmax/3, ymax/3, w, w), pix) + finally: + p.end() + if isinstance(dev, PdfDevice): + if dev.engine.errors_occurred: + raise SystemExit(1) + +def main(): + app = QApplication([]) + app + tdir = gettempdir() + pdf = os.path.join(tdir, 'painter.pdf') + func = full + with open(pdf, 'wb') as f: + dev = PdfDevice(f, xdpi=100, ydpi=100, compress=False) + img = QImage(dev.width(), dev.height(), + QImage.Format_ARGB32_Premultiplied) + img.setDotsPerMeterX(100*39.37) + img.setDotsPerMeterY(100*39.37) + img.fill(Qt.white) + func(dev) + func(img) + path = os.path.join(tdir, 'painter.png') + img.save(path) + print ('PDF written to:', pdf) + print ('Image written to:', path) + +if __name__ == '__main__': + main() + +