Add support for non-solid fills, tiled pixmaps, refactor testing

This commit is contained in:
Kovid Goyal 2012-12-31 13:33:38 +05:30
parent fe37caf9b5
commit ed89e9b465
4 changed files with 476 additions and 152 deletions

View File

@ -13,9 +13,7 @@ from functools import wraps, partial
from future_builtins import map from future_builtins import map
import sip import sip
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter, from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QTransform, QBrush)
QTransform, QImage, QByteArray, QBuffer,
qRgba)
from calibre.constants import plugins from calibre.constants import plugins
from calibre.ebooks.pdf.render.serialize import (PDFStream, Path) from calibre.ebooks.pdf.render.serialize import (PDFStream, Path)
@ -51,11 +49,19 @@ class Font(FontMetrics):
class PdfEngine(QPaintEngine): 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, def __init__(self, file_object, page_width, page_height, left_margin,
top_margin, right_margin, bottom_margin, width, height, top_margin, right_margin, bottom_margin, width, height,
errors=print, debug=print, compress=True, errors=print, debug=print, compress=True,
mark_links=False): mark_links=False):
QPaintEngine.__init__(self, self.features) QPaintEngine.__init__(self, self.FEATURES)
self.file_object = file_object self.file_object = file_object
self.compress, self.mark_links = compress, mark_links self.compress, self.mark_links = compress, mark_links
self.page_height, self.page_width = page_height, page_width self.page_height, self.page_width = page_height, page_width
@ -80,9 +86,6 @@ class PdfEngine(QPaintEngine):
self.errors_occurred = False self.errors_occurred = False
self.errors, self.debug = errors, debug self.errors, self.debug = errors, debug
self.fonts = {} 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_num = 1
self.current_page_inited = False self.current_page_inited = False
self.qt_hack, err = plugins['qt_hack'] self.qt_hack, err = plugins['qt_hack']
@ -107,13 +110,6 @@ class PdfEngine(QPaintEngine):
self.pdf.save_stack() self.pdf.save_stack()
self.current_page_inited = True 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): def begin(self, device):
if not hasattr(self, 'pdf'): if not hasattr(self, 'pdf'):
try: try:
@ -149,7 +145,23 @@ class PdfEngine(QPaintEngine):
def type(self): def type(self):
return QPaintEngine.Pdf 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 @store_error
def drawPixmap(self, rect, pixmap, source_rect): def drawPixmap(self, rect, pixmap, source_rect):
@ -160,8 +172,8 @@ class PdfEngine(QPaintEngine):
image = pixmap.toImage() image = pixmap.toImage()
ref = self.add_image(image, pixmap.cacheKey()) ref = self.add_image(image, pixmap.cacheKey())
if ref is not None: if ref is not None:
self.pdf.draw_image(rect.x(), rect.height()+rect.y(), rect.width(), self.pdf.draw_image(rect.x(), rect.y(), rect.width(),
-rect.height(), ref) rect.height(), ref)
@store_error @store_error
def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor): def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor):
@ -171,72 +183,8 @@ class PdfEngine(QPaintEngine):
image.copy(source_rect)) image.copy(source_rect))
ref = self.add_image(image, image.cacheKey()) ref = self.add_image(image, image.cacheKey())
if ref is not None: if ref is not None:
self.pdf.draw_image(rect.x(), rect.height()+rect.y(), rect.width(), self.pdf.draw_image(rect.x(), rect.y(), rect.width(),
-rect.height(), ref) 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)
@store_error @store_error
def updateState(self, state): 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 Bys ū --- Д AV ff ff')
finally:
p.end()
if dev.engine.errors_occurred:
raise SystemExit(1)

View File

@ -9,13 +9,14 @@ __docformat__ = 'restructuredtext en'
from math import sqrt from math import sqrt
from PyQt4.Qt import (QBrush, QPen, Qt, QPointF, QTransform, QPainterPath, from PyQt4.Qt import (
QPaintEngine) 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 from calibre.ebooks.pdf.render.serialize import Path, Color
def convert_path(path): def convert_path(path): # {{{
p = Path() p = Path()
i = 0 i = 0
while i < path.elementCount(): while i < path.elementCount():
@ -38,7 +39,201 @@ def convert_path(path):
if not added: if not added:
raise ValueError('Invalid curve to operation') raise ValueError('Invalid curve to operation')
return p 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): class GraphicsState(object):
@ -54,6 +249,7 @@ class GraphicsState(object):
self.clip = QPainterPath() self.clip = QPainterPath()
self.do_fill = False self.do_fill = False
self.do_stroke = True self.do_stroke = True
self.qt_pattern_cache = {}
def __eq__(self, other): def __eq__(self, other):
for x in self.FIELDS: for x in self.FIELDS:
@ -140,6 +336,43 @@ class Graphics(object):
self.current_state = self.pending_state self.current_state = self.pending_state
self.pending_state = None 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): def apply_stroke(self, state, pdf, pdf_system, painter):
# TODO: Handle pens with non solid brushes by setting the colorspace # TODO: Handle pens with non solid brushes by setting the colorspace
# for stroking to a pattern # for stroking to a pattern
@ -172,7 +405,7 @@ class Graphics(object):
Qt.DashDotDotLine:[3, 2, 1, 2, 1, 2]}.get(pen.style(), []) Qt.DashDotDotLine:[3, 2, 1, 2, 1, 2]}.get(pen.style(), [])
if ps: if ps:
pdf.serialize(Array(ps)) pdf.serialize(Array(ps))
pdf.current_page.write(' d ') pdf.current_page.write(' 0 d ')
# Stroke fill # Stroke fill
b = pen.brush() b = pen.brush()
@ -186,11 +419,8 @@ class Graphics(object):
def apply_fill(self, state, pdf, pdf_system, painter): def apply_fill(self, state, pdf, pdf_system, painter):
self.pending_state.do_fill = True self.pending_state.do_fill = True
b = state.fill color, opacity, pattern, self.pending_state.do_fill = self.convert_brush(
if b.style() == Qt.NoBrush: state.fill, state.brush_origin, state.opacity, pdf, pdf_system,
self.pending_state.do_fill = False painter.transform())
vals = list(b.color().getRgbF()) pdf.apply_fill(color, pattern, opacity)
vals[-1] *= state.opacity
color = Color(*vals)
pdf.set_fill_color(color)

View File

@ -12,6 +12,8 @@ from future_builtins import map
from itertools import izip from itertools import izip
from collections import namedtuple from collections import namedtuple
from PyQt4.Qt import QBuffer, QByteArray, QImage, Qt, QColor, qRgba
from calibre.constants import (__appname__, __version__) from calibre.constants import (__appname__, __version__)
from calibre.ebooks.pdf.render.common import ( from calibre.ebooks.pdf.render.common import (
Reference, EOL, serialize, Stream, Dictionary, String, Name, Array, Reference, EOL, serialize, Stream, Dictionary, String, Name, Array,
@ -90,6 +92,7 @@ class Page(Stream):
self.opacities = {} self.opacities = {}
self.fonts = {} self.fonts = {}
self.xobjects = {} self.xobjects = {}
self.patterns = {}
def set_opacity(self, opref): def set_opacity(self, opref):
if opref not in self.opacities: if opref not in self.opacities:
@ -108,6 +111,11 @@ class Page(Stream):
self.xobjects[imgref] = 'Image%d'%len(self.xobjects) self.xobjects[imgref] = 'Image%d'%len(self.xobjects)
return self.xobjects[imgref] 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): def add_resources(self):
r = Dictionary() r = Dictionary()
if self.opacities: if self.opacities:
@ -125,6 +133,13 @@ class Page(Stream):
for ref, name in self.xobjects.iteritems(): for ref, name in self.xobjects.iteritems():
xobjects[name] = ref xobjects[name] = ref
r['XObject'] = xobjects 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: if r:
self.page_dict['Resources'] = r self.page_dict['Resources'] = r
@ -299,8 +314,12 @@ class PDFStream(object):
self.stroke_opacities, self.fill_opacities = {}, {} self.stroke_opacities, self.fill_opacities = {}, {}
self.font_manager = FontManager(self.objects, self.compress) self.font_manager = FontManager(self.objects, self.compress)
self.image_cache = {} self.image_cache = {}
self.pattern_cache = {}
self.debug = debug self.debug = debug
self.links = Links(self, mark_links, page_size) 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 @property
def page_tree(self): def page_tree(self):
@ -380,13 +399,12 @@ class PDFStream(object):
self.current_page.set_opacity(self.stroke_opacities[opacity]) self.current_page.set_opacity(self.stroke_opacities[opacity])
self.current_page.write_line(' '.join(map(fmtnum, color[:3])) + ' SC') self.current_page.write_line(' '.join(map(fmtnum, color[:3])) + ' SC')
def set_fill_color(self, color): def set_fill_opacity(self, opacity):
opacity = color.opacity opacity = float(opacity)
if opacity not in self.fill_opacities: if opacity not in self.fill_opacities:
op = Dictionary({'Type':Name('ExtGState'), 'ca': opacity}) op = Dictionary({'Type':Name('ExtGState'), 'ca': opacity})
self.fill_opacities[opacity] = self.objects.add(op) self.fill_opacities[opacity] = self.objects.add(op)
self.current_page.set_opacity(self.fill_opacities[opacity]) self.current_page.set_opacity(self.fill_opacities[opacity])
self.current_page.write_line(' '.join(map(fmtnum, color[:3])) + ' sc')
def end_page(self): def end_page(self):
pageref = self.current_page.end(self.objects, self.stream) pageref = self.current_page.end(self.objects, self.stream)
@ -425,13 +443,93 @@ class PDFStream(object):
self.objects.commit(r, self.stream) self.objects.commit(r, self.stream)
return r 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) name = self.current_page.add_image(imgref)
self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(xscale), self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(width),
fmtnum(yscale), fmtnum(x), fmtnum(y))) fmtnum(-height), fmtnum(x), fmtnum(y+height)))
serialize(Name(name), self.current_page) serialize(Name(name), self.current_page)
self.current_page.write_line(' Do Q') 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): def end(self):
if self.current_page.getvalue(): if self.current_page.getvalue():
self.end_page() self.end_page()

View File

@ -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 <kovid at kovidgoyal.net>'
__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 Bys ū --- Д 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()