mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add support for opacity and alpha blending
This commit is contained in:
parent
e3121fe618
commit
fbcec65b71
@ -13,29 +13,58 @@ from collections import namedtuple
|
|||||||
from future_builtins import map
|
from future_builtins import map
|
||||||
|
|
||||||
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
||||||
QTransform, QPoint, QPainterPath)
|
QTransform, QPainterPath)
|
||||||
|
|
||||||
from calibre.constants import DEBUG
|
from calibre.constants import DEBUG
|
||||||
from calibre.ebooks.pdf.render.serialize import inch, A4, PDFStream, Path
|
from calibre.ebooks.pdf.render.serialize import (Color, inch, A4, PDFStream,
|
||||||
|
Path)
|
||||||
|
|
||||||
XDPI = 1200
|
XDPI = 1200
|
||||||
YDPI = 1200
|
YDPI = 1200
|
||||||
|
|
||||||
Point = namedtuple('Point', 'x y')
|
Point = namedtuple('Point', 'x y')
|
||||||
Color = namedtuple('Color', 'red green blue opacity')
|
ColorState = namedtuple('ColorState', 'color opacity do')
|
||||||
|
|
||||||
class GraphicsState(object): # {{{
|
class GraphicsState(object): # {{{
|
||||||
|
|
||||||
def __init__(self, state=None):
|
def __init__(self):
|
||||||
self.ops = {}
|
self.ops = {}
|
||||||
if state is not None:
|
self.current_state = self.initial_state = {
|
||||||
self.read_state(state)
|
'fill': ColorState(Color(0., 0., 0., 1.), 1.0, False),
|
||||||
|
'transform': QTransform(),
|
||||||
|
'dash': [],
|
||||||
|
'line_width': 0,
|
||||||
|
'stroke': ColorState(Color(0., 0., 0., 1.), 1.0, True),
|
||||||
|
'line_cap': 'flat',
|
||||||
|
'line_join': 'miter',
|
||||||
|
'clip': (Qt.NoClip, QPainterPath()),
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
def reset(self):
|
||||||
def stack_reset_needed(self):
|
self.current_state = self.initial_state
|
||||||
return 'transform' in self.ops or 'clip' in self.ops
|
|
||||||
|
|
||||||
def read_state(self, state):
|
def update_color_state(self, which, color=None, opacity=None,
|
||||||
|
brush_style=None, pen_style=None):
|
||||||
|
current = self.ops.get(which, self.current_state[which])
|
||||||
|
n = ColorState(*current)
|
||||||
|
if color is not None:
|
||||||
|
n = n._replace(color=Color(*color.getRgbF()))
|
||||||
|
if opacity is not None:
|
||||||
|
n = n._replace(opacity=opacity)
|
||||||
|
if opacity is not None:
|
||||||
|
opacity *= n.color.opacity
|
||||||
|
if brush_style is not None:
|
||||||
|
if which == 'fill':
|
||||||
|
do = (False if opacity == 0.0 or brush_style == Qt.NoBrush else
|
||||||
|
True)
|
||||||
|
else:
|
||||||
|
do = (False if opacity == 0.0 or brush_style == Qt.NoBrush or
|
||||||
|
pen_style == Qt.NoPen else True)
|
||||||
|
n = n._replace(do=do)
|
||||||
|
self.ops[which] = n
|
||||||
|
|
||||||
|
def read(self, state):
|
||||||
|
self.ops = {}
|
||||||
flags = state.state()
|
flags = state.state()
|
||||||
|
|
||||||
if flags & QPaintEngine.DirtyTransform:
|
if flags & QPaintEngine.DirtyTransform:
|
||||||
@ -45,25 +74,28 @@ class GraphicsState(object): # {{{
|
|||||||
if flags & QPaintEngine.DirtyBrush:
|
if flags & QPaintEngine.DirtyBrush:
|
||||||
brush = state.brush()
|
brush = state.brush()
|
||||||
color = brush.color()
|
color = brush.color()
|
||||||
self.ops['do_fill'] = 0 if (color.alpha() == 0 or brush.style() == Qt.NoBrush) else 1
|
self.update_color_state('fill', color=color,
|
||||||
self.ops['fill_color'] = Color(*color.getRgbF())
|
brush_style=brush.style())
|
||||||
|
|
||||||
if flags & QPaintEngine.DirtyPen:
|
if flags & QPaintEngine.DirtyPen:
|
||||||
pen = state.pen()
|
pen = state.pen()
|
||||||
brush = pen.brush()
|
brush = pen.brush()
|
||||||
color = pen.color()
|
color = pen.color()
|
||||||
self.ops['do_stroke'] = 0 if (pen.style() == Qt.NoPen or brush.style() ==
|
self.update_color_state('stroke', color, brush_style=brush.style(),
|
||||||
Qt.NoBrush or color.alpha() == 0) else 1
|
pen_style=pen.style())
|
||||||
ps = {Qt.DashLine:[3], Qt.DotLine:[1,2], Qt.DashDotLine:[3,2,1,2],
|
ps = {Qt.DashLine:[3], Qt.DotLine:[1,2], Qt.DashDotLine:[3,2,1,2],
|
||||||
Qt.DashDotDotLine:[3, 2, 1, 2, 1, 2]}.get(pen.style(), [])
|
Qt.DashDotDotLine:[3, 2, 1, 2, 1, 2]}.get(pen.style(), [])
|
||||||
self.ops['dash'] = ps
|
self.ops['dash'] = ps
|
||||||
self.ops['line_width'] = pen.widthF()
|
self.ops['line_width'] = pen.widthF()
|
||||||
self.ops['stroke_color'] = Color(*color.getRgbF())
|
|
||||||
self.ops['line_cap'] = {Qt.FlatCap:'flat', Qt.RoundCap:'round',
|
self.ops['line_cap'] = {Qt.FlatCap:'flat', Qt.RoundCap:'round',
|
||||||
Qt.SquareCap:'square'}.get(pen.capStyle(), 'flat')
|
Qt.SquareCap:'square'}.get(pen.capStyle(), 'flat')
|
||||||
self.ops['line_join'] = {Qt.MiterJoin:'miter', Qt.RoundJoin:'round',
|
self.ops['line_join'] = {Qt.MiterJoin:'miter', Qt.RoundJoin:'round',
|
||||||
Qt.BevelJoin:'bevel'}.get(pen.joinStyle(), 'miter')
|
Qt.BevelJoin:'bevel'}.get(pen.joinStyle(), 'miter')
|
||||||
|
|
||||||
|
if flags & QPaintEngine.DirtyOpacity:
|
||||||
|
self.update_color_state('fill', opacity=state.opacity())
|
||||||
|
self.update_color_state('stroke', opacity=state.opacity())
|
||||||
|
|
||||||
if flags & QPaintEngine.DirtyClipPath:
|
if flags & QPaintEngine.DirtyClipPath:
|
||||||
self.ops['clip'] = (state.clipOperation(), state.clipPath())
|
self.ops['clip'] = (state.clipOperation(), state.clipPath())
|
||||||
elif flags & QPaintEngine.DirtyClipRegion:
|
elif flags & QPaintEngine.DirtyClipRegion:
|
||||||
@ -72,85 +104,91 @@ class GraphicsState(object): # {{{
|
|||||||
path.addRect(rect)
|
path.addRect(rect)
|
||||||
self.ops['clip'] = (state.clipOperation(), path)
|
self.ops['clip'] = (state.clipOperation(), path)
|
||||||
|
|
||||||
# TODO: Add support for opacity
|
|
||||||
|
|
||||||
def __call__(self, engine):
|
def __call__(self, engine):
|
||||||
pdf = engine.pdf
|
pdf = engine.pdf
|
||||||
ops = self.ops
|
ops = self.ops
|
||||||
current_transform = ops.get('transform', None)
|
current_transform = self.current_state['transform']
|
||||||
srn = self.stack_reset_needed
|
transform_changed = 'transform' in ops and ops['transform'] != current_transform
|
||||||
|
reset_stack = transform_changed or 'clip' in ops
|
||||||
|
|
||||||
if srn:
|
if reset_stack:
|
||||||
pdf.restore_stack()
|
pdf.restore_stack()
|
||||||
pdf.save_stack()
|
pdf.save_stack()
|
||||||
# Since we have reset the stack we need to re-apply all previous
|
|
||||||
# operations
|
|
||||||
ops = engine.graphics_state.ops.copy()
|
|
||||||
ops.pop('clip', None) # Prev clip is handled separately
|
|
||||||
ops.update(self.ops)
|
|
||||||
self.ops = ops
|
|
||||||
|
|
||||||
# We apply clip before transform as the clip may have to be merged with
|
# We apply clip before transform as the clip may have to be merged with
|
||||||
# the previous clip path so it is easiest to work with clips that are
|
# the previous clip path so it is easiest to work with clips that are
|
||||||
# pre-transformed
|
# pre-transformed
|
||||||
prev_clip_path = engine.graphics_state.ops.get('clip', (None, None))[1]
|
prev_op, prev_clip_path = self.current_state['clip']
|
||||||
if 'clip' in ops:
|
if 'clip' in ops:
|
||||||
op, path = ops['clip']
|
op, path = ops['clip']
|
||||||
if current_transform is not None and path is not None:
|
self.current_state['clip'] = (op, path)
|
||||||
|
transform = ops.get('transform', QTransform())
|
||||||
|
if not transform.isIdentity() and path is not None:
|
||||||
# Pre transform the clip path
|
# Pre transform the clip path
|
||||||
path = current_transform.map(path)
|
path = current_transform.map(path)
|
||||||
ops['clip'] = (op, path)
|
self.current_state['clip'] = (op, path)
|
||||||
|
|
||||||
if op == Qt.ReplaceClip:
|
if op == Qt.ReplaceClip:
|
||||||
pass
|
pass
|
||||||
elif op == Qt.IntersectClip:
|
elif op == Qt.IntersectClip:
|
||||||
if prev_clip_path is not None:
|
if prev_op != Qt.NoClip:
|
||||||
ops['clip'] = (op, path.intersected(prev_clip_path))
|
self.current_state['clip'] = (op, path.intersected(prev_clip_path))
|
||||||
elif op == Qt.UniteClip:
|
elif op == Qt.UniteClip:
|
||||||
if prev_clip_path is not None:
|
if prev_clip_path is not None:
|
||||||
path.addPath(prev_clip_path)
|
path.addPath(prev_clip_path)
|
||||||
else:
|
else:
|
||||||
ops['clip'] = (Qt.NoClip, None)
|
self.current_state['clip'] = (Qt.NoClip, QPainterPath())
|
||||||
path = ops['clip'][1]
|
op, path = self.current_state['clip']
|
||||||
if path is not None:
|
if op != Qt.NoClip:
|
||||||
engine.add_clip(path)
|
engine.add_clip(path)
|
||||||
elif prev_clip_path is not None:
|
elif reset_stack and prev_op != Qt.NoClip:
|
||||||
# Re-apply the previous clip path since no clipping operation was
|
# Re-apply the previous clip path since no clipping operation was
|
||||||
# specified
|
# specified
|
||||||
engine.add_clip(prev_clip_path)
|
engine.add_clip(prev_clip_path)
|
||||||
ops['clip'] = (Qt.ReplaceClip, prev_clip_path)
|
|
||||||
|
|
||||||
# Apply transform
|
if reset_stack:
|
||||||
if current_transform is not None:
|
# Since we have reset the stack we need to re-apply all previous
|
||||||
engine.qt_system = current_transform
|
# operations, that are different from the default value (clip is
|
||||||
pdf.transform(current_transform)
|
# handled separately).
|
||||||
|
for op in set(self.current_state) - (set(ops)|{'clip'}):
|
||||||
|
if self.current_state[op] != self.initial_state[op]:
|
||||||
|
self.apply(op, self.current_state[op], engine, pdf)
|
||||||
|
|
||||||
# if 'fill_color' in ops:
|
# Now apply the new operations
|
||||||
# canvas.setFillColor(ops['fill_color'])
|
for op, val in ops.iteritems():
|
||||||
# if 'stroke_color' in ops:
|
self.apply(op, val, engine, pdf)
|
||||||
# canvas.setStrokeColor(ops['stroke_color'])
|
self.current_state[op] = val
|
||||||
for x in ('fill', 'stroke'):
|
|
||||||
x = 'do_'+x
|
|
||||||
if x in ops:
|
|
||||||
setattr(engine, x, ops[x])
|
|
||||||
if 'dash' in ops:
|
|
||||||
pdf.set_dash(ops['dash'])
|
|
||||||
if 'line_width' in ops:
|
|
||||||
pdf.set_line_width(ops['line_width'])
|
|
||||||
if 'line_cap' in ops:
|
|
||||||
pdf.set_line_cap(ops['line_cap'])
|
|
||||||
if 'line_join' in ops:
|
|
||||||
pdf.set_line_join(ops['line_join'])
|
|
||||||
|
|
||||||
if not srn:
|
def apply(self, op, val, engine, pdf):
|
||||||
# Add the operations from the previous state object that were not
|
getattr(self, 'apply_'+op)(val, engine, pdf)
|
||||||
# updated in this state object. This is needed to allow stack
|
|
||||||
# resetting to work.
|
def apply_transform(self, val, engine, pdf):
|
||||||
ops = engine.graphics_state.ops.copy()
|
engine.qt_system = val
|
||||||
ops.update(self.ops)
|
pdf.transform(val)
|
||||||
self.ops = ops
|
|
||||||
|
def apply_stroke(self, val, engine, pdf):
|
||||||
|
self.apply_color_state('stroke', val, engine, pdf)
|
||||||
|
|
||||||
|
def apply_fill(self, val, engine, pdf):
|
||||||
|
self.apply_color_state('fill', val, engine, pdf)
|
||||||
|
|
||||||
|
def apply_color_state(self, which, val, engine, pdf):
|
||||||
|
color = val.color._replace(opacity=val.opacity*val.color.opacity)
|
||||||
|
getattr(pdf, 'set_%s_color'%which)(color)
|
||||||
|
setattr(engine, 'do_%s'%which, val.do)
|
||||||
|
|
||||||
|
def apply_dash(self, val, engine, pdf):
|
||||||
|
pdf.set_dash(val)
|
||||||
|
|
||||||
|
def apply_line_width(self, val, engine, pdf):
|
||||||
|
pdf.set_line_width(val)
|
||||||
|
|
||||||
|
def apply_line_cap(self, val, engine, pdf):
|
||||||
|
pdf.set_line_cap(val)
|
||||||
|
|
||||||
|
def apply_line_join(self, val, engine, pdf):
|
||||||
|
pdf.set_line_join(val)
|
||||||
|
|
||||||
return self
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class PdfEngine(QPaintEngine):
|
class PdfEngine(QPaintEngine):
|
||||||
@ -178,8 +216,8 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
|
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
|
||||||
self.qt_system = QTransform()
|
self.qt_system = QTransform()
|
||||||
self.do_stroke = 1
|
self.do_stroke = True
|
||||||
self.do_fill = 0
|
self.do_fill = False
|
||||||
self.scale = sqrt(sy**2 + sx**2)
|
self.scale = sqrt(sy**2 + sx**2)
|
||||||
self.yscale = sy
|
self.yscale = sy
|
||||||
self.graphics_state = GraphicsState()
|
self.graphics_state = GraphicsState()
|
||||||
@ -189,12 +227,16 @@ class PdfEngine(QPaintEngine):
|
|||||||
self.pdf.set_rgb_colorspace()
|
self.pdf.set_rgb_colorspace()
|
||||||
width = self.painter.pen().widthF() if self.isActive() else 0
|
width = self.painter.pen().widthF() if self.isActive() else 0
|
||||||
self.pdf.set_line_width(width)
|
self.pdf.set_line_width(width)
|
||||||
|
self.do_stroke = True
|
||||||
|
self.do_fill = False
|
||||||
|
self.graphics_state.reset()
|
||||||
self.pdf.save_stack()
|
self.pdf.save_stack()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def features(self):
|
def features(self):
|
||||||
return (QPaintEngine.Antialiasing | QPaintEngine.PainterPaths |
|
return (QPaintEngine.Antialiasing | QPaintEngine.AlphaBlend |
|
||||||
QPaintEngine.PaintOutsidePaintEvent | QPaintEngine.PorterDuff |
|
QPaintEngine.ConstantOpacity | QPaintEngine.PainterPaths |
|
||||||
|
QPaintEngine.PaintOutsidePaintEvent |
|
||||||
QPaintEngine.PrimitiveTransform)
|
QPaintEngine.PrimitiveTransform)
|
||||||
|
|
||||||
def begin(self, device):
|
def begin(self, device):
|
||||||
@ -226,7 +268,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def type(self):
|
def type(self):
|
||||||
return QPaintEngine.User
|
return QPaintEngine.Pdf
|
||||||
|
|
||||||
def drawPixmap(self, rect, pixmap, source_rect):
|
def drawPixmap(self, rect, pixmap, source_rect):
|
||||||
pass # TODO: Implement me
|
pass # TODO: Implement me
|
||||||
@ -235,8 +277,8 @@ class PdfEngine(QPaintEngine):
|
|||||||
pass # TODO: Implement me
|
pass # TODO: Implement me
|
||||||
|
|
||||||
def updateState(self, state):
|
def updateState(self, state):
|
||||||
state = GraphicsState(state)
|
self.graphics_state.read(state)
|
||||||
self.graphics_state = state(self)
|
self.graphics_state(self)
|
||||||
|
|
||||||
def convert_path(self, path):
|
def convert_path(self, path):
|
||||||
p = Path()
|
p = Path()
|
||||||
@ -295,7 +337,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
if not q.isIdentity() and q.type() > q.TxShear:
|
if not q.isIdentity() and q.type() > q.TxShear:
|
||||||
# We cant map this transform to a PDF text transform operator
|
# We cant map this transform to a PDF text transform operator
|
||||||
f, s = self.do_fill, self.do_stroke
|
f, s = self.do_fill, self.do_stroke
|
||||||
self.do_fill, self.do_stroke = 1, 0
|
self.do_fill, self.do_stroke = True, False
|
||||||
super(PdfEngine, self).drawTextItem(point, text_item)
|
super(PdfEngine, self).drawTextItem(point, text_item)
|
||||||
self.do_fill, self.do_stroke = f, s
|
self.do_fill, self.do_stroke = f, s
|
||||||
return
|
return
|
||||||
@ -395,7 +437,8 @@ class PdfDevice(QPaintDevice): # {{{
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
QPainterPath, QPoint
|
from PyQt4.Qt import (QBrush, QColor, QPoint)
|
||||||
|
QBrush, QColor, QPoint
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
p = QPainter()
|
p = QPainter()
|
||||||
with open('/tmp/painter.pdf', 'wb') as f:
|
with open('/tmp/painter.pdf', 'wb') as f:
|
||||||
@ -411,6 +454,10 @@ if __name__ == '__main__':
|
|||||||
p.drawPath(pp)
|
p.drawPath(pp)
|
||||||
p.save()
|
p.save()
|
||||||
for i in xrange(3):
|
for i in xrange(3):
|
||||||
|
col = [0, 0, 0, 200]
|
||||||
|
col[i] = 255
|
||||||
|
p.setOpacity(0.3)
|
||||||
|
p.setBrush(QBrush(QColor(*col)))
|
||||||
p.drawRect(0, 0, xmax/10, xmax/10)
|
p.drawRect(0, 0, xmax/10, xmax/10)
|
||||||
p.translate(xmax/10, xmax/10)
|
p.translate(xmax/10, xmax/10)
|
||||||
p.scale(1, 1.5)
|
p.scale(1, 1.5)
|
||||||
|
@ -10,12 +10,15 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import codecs, zlib, hashlib
|
import codecs, zlib, hashlib
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from future_builtins import map
|
from future_builtins import map
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
from calibre.constants import (__appname__, __version__)
|
from calibre.constants import (__appname__, __version__)
|
||||||
|
|
||||||
PDFVER = b'%PDF-1.6'
|
PDFVER = b'%PDF-1.6'
|
||||||
EOL = b'\n'
|
EOL = b'\n'
|
||||||
|
|
||||||
|
Color = namedtuple('Color', 'red green blue opacity')
|
||||||
|
|
||||||
# Sizes {{{
|
# Sizes {{{
|
||||||
inch = 72.0
|
inch = 72.0
|
||||||
cm = inch / 2.54
|
cm = inch / 2.54
|
||||||
@ -46,6 +49,8 @@ B1 = (_BH*4, _BW*2)
|
|||||||
B0 = (_BW*4, _BH*4)
|
B0 = (_BW*4, _BH*4)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
# Basic PDF datatypes {{{
|
||||||
|
|
||||||
def serialize(o, stream):
|
def serialize(o, stream):
|
||||||
if hasattr(o, 'pdf_serialize'):
|
if hasattr(o, 'pdf_serialize'):
|
||||||
o.pdf_serialize(stream)
|
o.pdf_serialize(stream)
|
||||||
@ -150,6 +155,7 @@ class Reference(object):
|
|||||||
def pdf_serialize(self, stream):
|
def pdf_serialize(self, stream):
|
||||||
raw = '%d 0 R'%self.num
|
raw = '%d 0 R'%self.num
|
||||||
stream.write(raw.encode('ascii'))
|
stream.write(raw.encode('ascii'))
|
||||||
|
# }}}
|
||||||
|
|
||||||
class IndirectObjects(object):
|
class IndirectObjects(object):
|
||||||
|
|
||||||
@ -215,11 +221,30 @@ class Page(Stream):
|
|||||||
'Type': Name('Page'),
|
'Type': Name('Page'),
|
||||||
'Parent': parentref,
|
'Parent': parentref,
|
||||||
})
|
})
|
||||||
|
self.opacities = {}
|
||||||
|
|
||||||
|
def set_opacity(self, opref):
|
||||||
|
if opref not in self.opacities:
|
||||||
|
self.opacities[opref] = 'Opa%d'%len(self.opacities)
|
||||||
|
name = self.opacities[opref]
|
||||||
|
serialize(Name(name), self)
|
||||||
|
self.write(b' gs ')
|
||||||
|
|
||||||
|
def add_resources(self):
|
||||||
|
r = Dictionary()
|
||||||
|
if self.opacities:
|
||||||
|
extgs = Dictionary()
|
||||||
|
for opref, name in self.opacities.iteritems():
|
||||||
|
extgs[name] = opref
|
||||||
|
r['ExtGState'] = extgs
|
||||||
|
if r:
|
||||||
|
self.page_dict['Resources'] = r
|
||||||
|
|
||||||
def end(self, objects, stream):
|
def end(self, objects, stream):
|
||||||
contents = objects.add(self)
|
contents = objects.add(self)
|
||||||
objects.commit(contents, stream)
|
objects.commit(contents, stream)
|
||||||
self.page_dict['Contents'] = contents
|
self.page_dict['Contents'] = contents
|
||||||
|
self.add_resources()
|
||||||
ret = objects.add(self.page_dict)
|
ret = objects.add(self.page_dict)
|
||||||
objects.commit(ret, stream)
|
objects.commit(ret, stream)
|
||||||
return ret
|
return ret
|
||||||
@ -299,6 +324,7 @@ class PDFStream(object):
|
|||||||
self.current_page = Page(self.page_tree, compress=self.compress)
|
self.current_page = Page(self.page_tree, compress=self.compress)
|
||||||
self.info = Dictionary({'Creator':String(creator),
|
self.info = Dictionary({'Creator':String(creator),
|
||||||
'Producer':String(creator)})
|
'Producer':String(creator)})
|
||||||
|
self.stroke_opacities, self.fill_opacities = {}, {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def page_tree(self):
|
def page_tree(self):
|
||||||
@ -374,6 +400,22 @@ class PDFStream(object):
|
|||||||
serialize({'miter':0, 'round':1, 'bevel':2}[style], self.current_page)
|
serialize({'miter':0, 'round':1, 'bevel':2}[style], self.current_page)
|
||||||
self.current_page.write_line(' j')
|
self.current_page.write_line(' j')
|
||||||
|
|
||||||
|
def set_stroke_color(self, color):
|
||||||
|
opacity = color.opacity
|
||||||
|
if opacity not in self.stroke_opacities:
|
||||||
|
op = Dictionary({'Type':Name('ExtGState'), 'CA': opacity})
|
||||||
|
self.stroke_opacities[opacity] = self.objects.add(op)
|
||||||
|
self.current_page.set_opacity(self.stroke_opacities[opacity])
|
||||||
|
self.current_page.write_line(' '.join(map(type(u''), color[:3])) + ' SC')
|
||||||
|
|
||||||
|
def set_fill_color(self, color):
|
||||||
|
opacity = color.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(type(u''), 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)
|
||||||
self.page_tree.obj.add_page(pageref)
|
self.page_tree.obj.add_page(pageref)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user