mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Workaround for Qt's broken emulation of gradients with texture patterns
This commit is contained in:
parent
32165539ea
commit
9f13e30737
@ -93,7 +93,11 @@ class PdfEngine(QPaintEngine):
|
||||
raise RuntimeError('Failed to load qt_hack with err: %s'%err)
|
||||
|
||||
def apply_graphics_state(self):
|
||||
self.graphics(self.pdf, self.pdf_system, self.painter())
|
||||
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):
|
||||
@ -117,6 +121,7 @@ class PdfEngine(QPaintEngine):
|
||||
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
|
||||
@ -155,7 +160,7 @@ class PdfEngine(QPaintEngine):
|
||||
brush = QBrush(pixmap)
|
||||
bl = rect.topLeft()
|
||||
color, opacity, pattern, do_fill = self.graphics.convert_brush(
|
||||
brush, bl-point, 1.0, self.pdf, self.pdf_system,
|
||||
brush, bl-point, 1.0, self.pdf_system,
|
||||
self.painter().transform())
|
||||
self.pdf.save_stack()
|
||||
self.pdf.apply_fill(color, pattern)
|
||||
@ -211,10 +216,12 @@ class PdfEngine(QPaintEngine):
|
||||
@store_error
|
||||
def drawRects(self, rects):
|
||||
self.apply_graphics_state()
|
||||
for rect in rects:
|
||||
bl = rect.topLeft()
|
||||
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
||||
stroke=self.do_stroke, fill=self.do_fill)
|
||||
with self.graphics:
|
||||
for rect in rects:
|
||||
self.resolve_fill(rect)
|
||||
bl = rect.topLeft()
|
||||
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)
|
||||
|
@ -8,6 +8,7 @@ __copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from math import sqrt
|
||||
from collections import namedtuple
|
||||
|
||||
from PyQt4.Qt import (
|
||||
QBrush, QPen, Qt, QPointF, QTransform, QPainterPath, QPaintEngine, QImage)
|
||||
@ -41,6 +42,8 @@ def convert_path(path): # {{{
|
||||
return p
|
||||
# }}}
|
||||
|
||||
Brush = namedtuple('Brush', 'origin brush color')
|
||||
|
||||
class TilingPattern(Stream):
|
||||
|
||||
def __init__(self, cache_key, matrix, w=8, h=8, paint_type=2, compress=False):
|
||||
@ -222,18 +225,25 @@ class QtPattern(TilingPattern):
|
||||
|
||||
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))))
|
||||
def __init__(self, pixmap, matrix, pdf, clone=None):
|
||||
if clone is None:
|
||||
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))))
|
||||
else:
|
||||
super(TexturePattern, self).__init__(
|
||||
clone.cache_key[1], matrix, w=clone.w, h=clone.h,
|
||||
paint_type=clone.paint_type)
|
||||
self.resources['XObject'] = Dictionary(clone.resources['XObject'])
|
||||
self.write(clone.getvalue())
|
||||
|
||||
class GraphicsState(object):
|
||||
|
||||
@ -275,6 +285,9 @@ class Graphics(object):
|
||||
self.current_state = GraphicsState()
|
||||
self.pending_state = None
|
||||
|
||||
def begin(self, pdf):
|
||||
self.pdf = pdf
|
||||
|
||||
def update_state(self, state, painter):
|
||||
flags = state.state()
|
||||
if self.pending_state is None:
|
||||
@ -304,13 +317,14 @@ class Graphics(object):
|
||||
self.current_state = GraphicsState()
|
||||
self.pending_state = None
|
||||
|
||||
def __call__(self, pdf, pdf_system, painter):
|
||||
def __call__(self, pdf_system, painter):
|
||||
# Apply the currently pending state to the PDF
|
||||
if self.pending_state is None:
|
||||
return
|
||||
|
||||
pdf_state = self.current_state
|
||||
ps = self.pending_state
|
||||
pdf = self.pdf
|
||||
|
||||
if (ps.transform != pdf_state.transform or ps.clip != pdf_state.clip):
|
||||
pdf.restore_stack()
|
||||
@ -321,11 +335,11 @@ class Graphics(object):
|
||||
pdf.transform(ps.transform)
|
||||
|
||||
if (pdf_state.opacity != ps.opacity or pdf_state.stroke != ps.stroke):
|
||||
self.apply_stroke(ps, pdf, pdf_system, painter)
|
||||
self.apply_stroke(ps, pdf_system, painter)
|
||||
|
||||
if (pdf_state.opacity != ps.opacity or pdf_state.fill != ps.fill or
|
||||
pdf_state.brush_origin != ps.brush_origin):
|
||||
self.apply_fill(ps, pdf, pdf_system, painter)
|
||||
self.apply_fill(ps, pdf_system, painter)
|
||||
|
||||
if (pdf_state.clip != ps.clip):
|
||||
p = convert_path(ps.clip)
|
||||
@ -336,25 +350,28 @@ class Graphics(object):
|
||||
self.current_state = self.pending_state
|
||||
self.pending_state = None
|
||||
|
||||
def convert_brush(self, brush, brush_origin, global_opacity, pdf,
|
||||
def convert_brush(self, brush, brush_origin, global_opacity,
|
||||
pdf_system, qt_system):
|
||||
# Convert a QBrush to PDF operators
|
||||
style = brush.style()
|
||||
pdf = self.pdf
|
||||
|
||||
pattern = color = None
|
||||
pattern = color = pat = 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())
|
||||
self.brushobj = None
|
||||
|
||||
if style <= Qt.DiagCrossPattern:
|
||||
opacity = global_opacity * vals[-1]
|
||||
color = vals[:3]
|
||||
|
||||
if style > Qt.SolidPattern:
|
||||
pattern = pdf.add_pattern(QtPattern(style, matrix))
|
||||
pat = QtPattern(style, matrix)
|
||||
pattern = pdf.add_pattern(pat)
|
||||
|
||||
if opacity < 1e-4 or style == Qt.NoBrush:
|
||||
do_fill = False
|
||||
@ -370,15 +387,17 @@ class Graphics(object):
|
||||
if opacity < 1e-4 or style == Qt.NoBrush:
|
||||
do_fill = False
|
||||
|
||||
self.brushobj = Brush(brush_origin, pat, color)
|
||||
# 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_system, painter):
|
||||
# TODO: Handle pens with non solid brushes by setting the colorspace
|
||||
# for stroking to a pattern
|
||||
# TODO: Support miter limit by using QPainterPathStroker
|
||||
pen = state.stroke
|
||||
self.pending_state.do_stroke = True
|
||||
pdf = self.pdf
|
||||
if pen.style() == Qt.NoPen:
|
||||
self.pending_state.do_stroke = False
|
||||
|
||||
@ -417,10 +436,41 @@ class Graphics(object):
|
||||
if vals[-1] < 1e-5 or b.style() == Qt.NoBrush:
|
||||
self.pending_state.do_stroke = False
|
||||
|
||||
def apply_fill(self, state, pdf, pdf_system, painter):
|
||||
def apply_fill(self, state, pdf_system, painter):
|
||||
self.pending_state.do_fill = True
|
||||
color, opacity, pattern, self.pending_state.do_fill = self.convert_brush(
|
||||
state.fill, state.brush_origin, state.opacity, pdf, pdf_system,
|
||||
state.fill, state.brush_origin, state.opacity, pdf_system,
|
||||
painter.transform())
|
||||
pdf.apply_fill(color, pattern, opacity)
|
||||
self.pdf.apply_fill(color, pattern, opacity)
|
||||
self.last_fill = self.brushobj
|
||||
|
||||
def __enter__(self):
|
||||
self.pdf.save_stack()
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.pdf.restore_stack()
|
||||
|
||||
def resolve_fill(self, rect, pdf_system, qt_system):
|
||||
'''
|
||||
Qt's paint system does not update brushOrigin when using
|
||||
TexturePatterns and it also uses TexturePatterns to emulate gradients,
|
||||
leading to brokenness. So this method allows the paint engine to update
|
||||
the brush origin before painting an object. While not perfect, this is
|
||||
better than nothing.
|
||||
'''
|
||||
if not self.current_state.do_fill:
|
||||
return
|
||||
|
||||
if isinstance(self.last_fill.brush, TexturePattern):
|
||||
tl = rect.topLeft()
|
||||
if tl == self.last_fill.origin:
|
||||
return
|
||||
|
||||
matrix = (QTransform.fromTranslate(tl.x(), tl.y())
|
||||
* pdf_system * qt_system.inverted()[0])
|
||||
|
||||
pat = TexturePattern(None, matrix, self.pdf, clone=self.last_fill.brush)
|
||||
pattern = self.pdf.add_pattern(pat)
|
||||
self.pdf.apply_fill(self.last_fill.color, pattern)
|
||||
|
||||
|
||||
|
@ -83,21 +83,20 @@ def run(dev, func):
|
||||
raise SystemExit(1)
|
||||
|
||||
def brush(p, xmax, ymax):
|
||||
x = y = 0
|
||||
w = xmax/3 + 10
|
||||
g = QLinearGradient(QPointF(0, 0), QPointF(0, w))
|
||||
g.setSpread(g.RepeatSpread)
|
||||
g.setColorAt(0, QColor('#00f'))
|
||||
g.setColorAt(1, QColor('#fff'))
|
||||
p.fillRect(x, y, w, w, QBrush(g))
|
||||
p.drawRect(x, y, w, w)
|
||||
x = xmax/3
|
||||
y = 0
|
||||
w = xmax/2
|
||||
pix = QPixmap(I('console.png'))
|
||||
p.fillRect(x, y, w, w, QBrush(pix))
|
||||
|
||||
p.fillRect(0, y+xmax/1.9, w, w, QBrush(pix))
|
||||
|
||||
def main():
|
||||
app = QApplication([])
|
||||
app
|
||||
tdir = gettempdir()
|
||||
pdf = os.path.join(tdir, 'painter.pdf')
|
||||
func = brush
|
||||
func = full
|
||||
dpi = 100
|
||||
with open(pdf, 'wb') as f:
|
||||
dev = PdfDevice(f, xdpi=dpi, ydpi=dpi, compress=False)
|
||||
|
Loading…
x
Reference in New Issue
Block a user