mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add support for non-solid fills, tiled pixmaps, refactor testing
This commit is contained in:
parent
fe37caf9b5
commit
ed89e9b465
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
99
src/calibre/ebooks/pdf/render/test.py
Normal file
99
src/calibre/ebooks/pdf/render/test.py
Normal 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 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()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user