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)
|
raise RuntimeError('Failed to load qt_hack with err: %s'%err)
|
||||||
|
|
||||||
def apply_graphics_state(self):
|
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
|
@property
|
||||||
def do_fill(self):
|
def do_fill(self):
|
||||||
@ -117,6 +121,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
self.page_height), compress=self.compress,
|
self.page_height), compress=self.compress,
|
||||||
mark_links=self.mark_links,
|
mark_links=self.mark_links,
|
||||||
debug=self.debug)
|
debug=self.debug)
|
||||||
|
self.graphics.begin(self.pdf)
|
||||||
except:
|
except:
|
||||||
self.errors(traceback.format_exc())
|
self.errors(traceback.format_exc())
|
||||||
self.errors_occurred = True
|
self.errors_occurred = True
|
||||||
@ -155,7 +160,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
brush = QBrush(pixmap)
|
brush = QBrush(pixmap)
|
||||||
bl = rect.topLeft()
|
bl = rect.topLeft()
|
||||||
color, opacity, pattern, do_fill = self.graphics.convert_brush(
|
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.painter().transform())
|
||||||
self.pdf.save_stack()
|
self.pdf.save_stack()
|
||||||
self.pdf.apply_fill(color, pattern)
|
self.pdf.apply_fill(color, pattern)
|
||||||
@ -211,10 +216,12 @@ class PdfEngine(QPaintEngine):
|
|||||||
@store_error
|
@store_error
|
||||||
def drawRects(self, rects):
|
def drawRects(self, rects):
|
||||||
self.apply_graphics_state()
|
self.apply_graphics_state()
|
||||||
for rect in rects:
|
with self.graphics:
|
||||||
bl = rect.topLeft()
|
for rect in rects:
|
||||||
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
self.resolve_fill(rect)
|
||||||
stroke=self.do_stroke, fill=self.do_fill)
|
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):
|
def create_sfnt(self, text_item):
|
||||||
get_table = partial(self.qt_hack.get_sfnt_table, 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'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QBrush, QPen, Qt, QPointF, QTransform, QPainterPath, QPaintEngine, QImage)
|
QBrush, QPen, Qt, QPointF, QTransform, QPainterPath, QPaintEngine, QImage)
|
||||||
@ -41,6 +42,8 @@ def convert_path(path): # {{{
|
|||||||
return p
|
return p
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
Brush = namedtuple('Brush', 'origin brush color')
|
||||||
|
|
||||||
class TilingPattern(Stream):
|
class TilingPattern(Stream):
|
||||||
|
|
||||||
def __init__(self, cache_key, matrix, w=8, h=8, paint_type=2, compress=False):
|
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):
|
class TexturePattern(TilingPattern):
|
||||||
|
|
||||||
def __init__(self, pixmap, matrix, pdf):
|
def __init__(self, pixmap, matrix, pdf, clone=None):
|
||||||
image = pixmap.toImage()
|
if clone is None:
|
||||||
cache_key = pixmap.cacheKey()
|
image = pixmap.toImage()
|
||||||
imgref = pdf.add_image(image, cache_key)
|
cache_key = pixmap.cacheKey()
|
||||||
paint_type = (2 if image.format() in {QImage.Format_MonoLSB,
|
imgref = pdf.add_image(image, cache_key)
|
||||||
QImage.Format_Mono} else 1)
|
paint_type = (2 if image.format() in {QImage.Format_MonoLSB,
|
||||||
super(TexturePattern, self).__init__(
|
QImage.Format_Mono} else 1)
|
||||||
cache_key, matrix, w=image.width(), h=image.height(),
|
super(TexturePattern, self).__init__(
|
||||||
paint_type=paint_type)
|
cache_key, matrix, w=image.width(), h=image.height(),
|
||||||
m = (self.w, 0, 0, -self.h, 0, self.h)
|
paint_type=paint_type)
|
||||||
self.resources['XObject'] = Dictionary({'Texture':imgref})
|
m = (self.w, 0, 0, -self.h, 0, self.h)
|
||||||
self.write_line('%s cm /Texture Do'%(' '.join(map(fmtnum, m))))
|
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):
|
class GraphicsState(object):
|
||||||
|
|
||||||
@ -275,6 +285,9 @@ class Graphics(object):
|
|||||||
self.current_state = GraphicsState()
|
self.current_state = GraphicsState()
|
||||||
self.pending_state = None
|
self.pending_state = None
|
||||||
|
|
||||||
|
def begin(self, pdf):
|
||||||
|
self.pdf = pdf
|
||||||
|
|
||||||
def update_state(self, state, painter):
|
def update_state(self, state, painter):
|
||||||
flags = state.state()
|
flags = state.state()
|
||||||
if self.pending_state is None:
|
if self.pending_state is None:
|
||||||
@ -304,13 +317,14 @@ class Graphics(object):
|
|||||||
self.current_state = GraphicsState()
|
self.current_state = GraphicsState()
|
||||||
self.pending_state = None
|
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
|
# Apply the currently pending state to the PDF
|
||||||
if self.pending_state is None:
|
if self.pending_state is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
pdf_state = self.current_state
|
pdf_state = self.current_state
|
||||||
ps = self.pending_state
|
ps = self.pending_state
|
||||||
|
pdf = self.pdf
|
||||||
|
|
||||||
if (ps.transform != pdf_state.transform or ps.clip != pdf_state.clip):
|
if (ps.transform != pdf_state.transform or ps.clip != pdf_state.clip):
|
||||||
pdf.restore_stack()
|
pdf.restore_stack()
|
||||||
@ -321,11 +335,11 @@ class Graphics(object):
|
|||||||
pdf.transform(ps.transform)
|
pdf.transform(ps.transform)
|
||||||
|
|
||||||
if (pdf_state.opacity != ps.opacity or pdf_state.stroke != ps.stroke):
|
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
|
if (pdf_state.opacity != ps.opacity or pdf_state.fill != ps.fill or
|
||||||
pdf_state.brush_origin != ps.brush_origin):
|
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):
|
if (pdf_state.clip != ps.clip):
|
||||||
p = convert_path(ps.clip)
|
p = convert_path(ps.clip)
|
||||||
@ -336,25 +350,28 @@ 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,
|
def convert_brush(self, brush, brush_origin, global_opacity,
|
||||||
pdf_system, qt_system):
|
pdf_system, qt_system):
|
||||||
# Convert a QBrush to PDF operators
|
# Convert a QBrush to PDF operators
|
||||||
style = brush.style()
|
style = brush.style()
|
||||||
|
pdf = self.pdf
|
||||||
|
|
||||||
pattern = color = None
|
pattern = color = pat = None
|
||||||
opacity = 1.0
|
opacity = 1.0
|
||||||
do_fill = True
|
do_fill = True
|
||||||
|
|
||||||
matrix = (QTransform.fromTranslate(brush_origin.x(), brush_origin.y())
|
matrix = (QTransform.fromTranslate(brush_origin.x(), brush_origin.y())
|
||||||
* pdf_system * qt_system.inverted()[0])
|
* pdf_system * qt_system.inverted()[0])
|
||||||
vals = list(brush.color().getRgbF())
|
vals = list(brush.color().getRgbF())
|
||||||
|
self.brushobj = None
|
||||||
|
|
||||||
if style <= Qt.DiagCrossPattern:
|
if style <= Qt.DiagCrossPattern:
|
||||||
opacity = global_opacity * vals[-1]
|
opacity = global_opacity * vals[-1]
|
||||||
color = vals[:3]
|
color = vals[:3]
|
||||||
|
|
||||||
if style > Qt.SolidPattern:
|
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:
|
if opacity < 1e-4 or style == Qt.NoBrush:
|
||||||
do_fill = False
|
do_fill = False
|
||||||
@ -370,15 +387,17 @@ class Graphics(object):
|
|||||||
if opacity < 1e-4 or style == Qt.NoBrush:
|
if opacity < 1e-4 or style == Qt.NoBrush:
|
||||||
do_fill = False
|
do_fill = False
|
||||||
|
|
||||||
|
self.brushobj = Brush(brush_origin, pat, color)
|
||||||
# TODO: Add support for gradient fills
|
# TODO: Add support for gradient fills
|
||||||
return color, opacity, pattern, do_fill
|
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
|
# TODO: Handle pens with non solid brushes by setting the colorspace
|
||||||
# for stroking to a pattern
|
# for stroking to a pattern
|
||||||
# TODO: Support miter limit by using QPainterPathStroker
|
# TODO: Support miter limit by using QPainterPathStroker
|
||||||
pen = state.stroke
|
pen = state.stroke
|
||||||
self.pending_state.do_stroke = True
|
self.pending_state.do_stroke = True
|
||||||
|
pdf = self.pdf
|
||||||
if pen.style() == Qt.NoPen:
|
if pen.style() == Qt.NoPen:
|
||||||
self.pending_state.do_stroke = False
|
self.pending_state.do_stroke = False
|
||||||
|
|
||||||
@ -417,10 +436,41 @@ class Graphics(object):
|
|||||||
if vals[-1] < 1e-5 or b.style() == Qt.NoBrush:
|
if vals[-1] < 1e-5 or b.style() == Qt.NoBrush:
|
||||||
self.pending_state.do_stroke = False
|
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
|
self.pending_state.do_fill = True
|
||||||
color, opacity, pattern, self.pending_state.do_fill = self.convert_brush(
|
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())
|
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)
|
raise SystemExit(1)
|
||||||
|
|
||||||
def brush(p, xmax, ymax):
|
def brush(p, xmax, ymax):
|
||||||
x = y = 0
|
x = xmax/3
|
||||||
w = xmax/3 + 10
|
y = 0
|
||||||
g = QLinearGradient(QPointF(0, 0), QPointF(0, w))
|
w = xmax/2
|
||||||
g.setSpread(g.RepeatSpread)
|
pix = QPixmap(I('console.png'))
|
||||||
g.setColorAt(0, QColor('#00f'))
|
p.fillRect(x, y, w, w, QBrush(pix))
|
||||||
g.setColorAt(1, QColor('#fff'))
|
|
||||||
p.fillRect(x, y, w, w, QBrush(g))
|
p.fillRect(0, y+xmax/1.9, w, w, QBrush(pix))
|
||||||
p.drawRect(x, y, w, w)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
app
|
app
|
||||||
tdir = gettempdir()
|
tdir = gettempdir()
|
||||||
pdf = os.path.join(tdir, 'painter.pdf')
|
pdf = os.path.join(tdir, 'painter.pdf')
|
||||||
func = brush
|
func = full
|
||||||
dpi = 100
|
dpi = 100
|
||||||
with open(pdf, 'wb') as f:
|
with open(pdf, 'wb') as f:
|
||||||
dev = PdfDevice(f, xdpi=dpi, ydpi=dpi, compress=False)
|
dev = PdfDevice(f, xdpi=dpi, ydpi=dpi, compress=False)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user