PDF engine: Get rid of the unneccessary reportlab

This commit is contained in:
Kovid Goyal 2012-12-15 16:21:38 +05:30
parent a3c5ee351f
commit e3121fe618
3 changed files with 480 additions and 89 deletions

View File

@ -0,0 +1,11 @@
#!/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'

View File

@ -15,20 +15,14 @@ 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, QPoint, QPainterPath)
from reportlab.lib.units import inch
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen.canvas import FILL_NON_ZERO, FILL_EVEN_ODD, Canvas
from reportlab.lib.colors import Color
from calibre.constants import DEBUG from calibre.constants import DEBUG
from calibre.ebooks.pdf.render.serialize import 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')
def set_transform(transform, func):
func(transform.m11(), transform.m12(), transform.m21(), transform.m22(), transform.dx(), transform.dy())
class GraphicsState(object): # {{{ class GraphicsState(object): # {{{
@ -51,30 +45,24 @@ class GraphicsState(object): # {{{
if flags & QPaintEngine.DirtyBrush: if flags & QPaintEngine.DirtyBrush:
brush = state.brush() brush = state.brush()
color = brush.color() color = brush.color()
alpha = color.alphaF() self.ops['do_fill'] = 0 if (color.alpha() == 0 or brush.style() == Qt.NoBrush) else 1
if alpha == 1.0: alpha = None self.ops['fill_color'] = Color(*color.getRgbF())
self.ops['do_fill'] = 0 if (alpha == 0.0 or brush.style() == Qt.NoBrush) else 1
self.ops['fill_color'] = Color(color.red(), color.green(), color.blue(),
alpha=alpha)
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()
alpha = color.alphaF()
if alpha == 1.0: alpha = None
self.ops['do_stroke'] = 0 if (pen.style() == Qt.NoPen or brush.style() == self.ops['do_stroke'] = 0 if (pen.style() == Qt.NoPen or brush.style() ==
Qt.NoBrush or alpha == 0.0) else 1 Qt.NoBrush or color.alpha() == 0) else 1
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.red(), color.green(), self.ops['stroke_color'] = Color(*color.getRgbF())
color.blue(), alpha=alpha) self.ops['line_cap'] = {Qt.FlatCap:'flat', Qt.RoundCap:'round',
self.ops['line_cap'] = {Qt.FlatCap:0, Qt.RoundCap:1, Qt.SquareCap:'square'}.get(pen.capStyle(), 'flat')
Qt.SquareCap:2}.get(pen.capStyle(), 0) self.ops['line_join'] = {Qt.MiterJoin:'miter', Qt.RoundJoin:'round',
self.ops['line_join'] = {Qt.MiterJoin:0, Qt.RoundJoin:1, Qt.BevelJoin:'bevel'}.get(pen.joinStyle(), 'miter')
Qt.BevelJoin:2}.get(pen.joinStyle(), 0)
if flags & QPaintEngine.DirtyClipPath: if flags & QPaintEngine.DirtyClipPath:
self.ops['clip'] = (state.clipOperation(), state.clipPath()) self.ops['clip'] = (state.clipOperation(), state.clipPath())
@ -87,14 +75,14 @@ class GraphicsState(object): # {{{
# TODO: Add support for opacity # TODO: Add support for opacity
def __call__(self, engine): def __call__(self, engine):
canvas = engine.canvas pdf = engine.pdf
ops = self.ops ops = self.ops
current_transform = ops.get('transform', None) current_transform = ops.get('transform', None)
srn = self.stack_reset_needed srn = self.stack_reset_needed
if srn: if srn:
canvas.restoreState() pdf.restore_stack()
canvas.saveState() pdf.save_stack()
# Since we have reset the stack we need to re-apply all previous # Since we have reset the stack we need to re-apply all previous
# operations # operations
ops = engine.graphics_state.ops.copy() ops = engine.graphics_state.ops.copy()
@ -125,40 +113,40 @@ class GraphicsState(object): # {{{
ops['clip'] = (Qt.NoClip, None) ops['clip'] = (Qt.NoClip, None)
path = ops['clip'][1] path = ops['clip'][1]
if path is not None: if path is not None:
engine.set_clip(path) engine.add_clip(path)
elif prev_clip_path is not None: elif prev_clip_path is not None:
# 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.set_clip(prev_clip_path) engine.add_clip(prev_clip_path)
ops['clip'] = (Qt.ReplaceClip, prev_clip_path) ops['clip'] = (Qt.ReplaceClip, prev_clip_path)
# Apply transform # Apply transform
if current_transform is not None: if current_transform is not None:
engine.qt_system = current_transform engine.qt_system = current_transform
set_transform(current_transform, canvas.transform) pdf.transform(current_transform)
if 'fill_color' in ops: # if 'fill_color' in ops:
canvas.setFillColor(ops['fill_color']) # canvas.setFillColor(ops['fill_color'])
if 'stroke_color' in ops: # if 'stroke_color' in ops:
canvas.setStrokeColor(ops['stroke_color']) # canvas.setStrokeColor(ops['stroke_color'])
for x in ('fill', 'stroke'): for x in ('fill', 'stroke'):
x = 'do_'+x x = 'do_'+x
if x in ops: if x in ops:
setattr(canvas, x, ops[x]) setattr(engine, x, ops[x])
if 'dash' in ops: if 'dash' in ops:
canvas.setDash(ops['dash']) pdf.set_dash(ops['dash'])
if 'line_width' in ops: if 'line_width' in ops:
canvas.setLineWidth(ops['line_width']) pdf.set_line_width(ops['line_width'])
if 'line_cap' in ops: if 'line_cap' in ops:
canvas.setLineCap(ops['line_cap']) pdf.set_line_cap(ops['line_cap'])
if 'line_join' in ops: if 'line_join' in ops:
canvas.setLineJoin(ops['line_join']) pdf.set_line_join(ops['line_join'])
if not srn: if not srn:
# Add the operations from the previous state object that were not # Add the operations from the previous state object that were not
# updated in this state object. This is needed to allow stack # updated in this state object. This is needed to allow stack
# resetting to work. # resetting to work.
ops = canvas.graphics_state.ops.copy() ops = engine.graphics_state.ops.copy()
ops.update(self.ops) ops.update(self.ops)
self.ops = ops self.ops = ops
@ -197,8 +185,11 @@ class PdfEngine(QPaintEngine):
self.graphics_state = GraphicsState() self.graphics_state = GraphicsState()
def init_page(self): def init_page(self):
set_transform(self.pdf_system, self.canvas.transform) self.pdf.transform(self.pdf_system)
self.canvas.saveState() self.pdf.set_rgb_colorspace()
width = self.painter.pen().widthF() if self.isActive() else 0
self.pdf.set_line_width(width)
self.pdf.save_stack()
@property @property
def features(self): def features(self):
@ -208,9 +199,9 @@ class PdfEngine(QPaintEngine):
def begin(self, device): def begin(self, device):
try: try:
self.canvas = Canvas(self.file_object, self.pdf = PDFStream(self.file_object, (self.page_width,
pageCompression=0 if DEBUG else 1, self.page_height),
pagesize=(self.page_width, self.page_height)) compress=0 if DEBUG else 1)
self.init_page() self.init_page()
except: except:
traceback.print_exc() traceback.print_exc()
@ -218,20 +209,20 @@ class PdfEngine(QPaintEngine):
return True return True
def end_page(self, start_new=True): def end_page(self, start_new=True):
self.canvas.restoreState() self.pdf.restore_stack()
self.canvas.showPage() self.pdf.end_page()
if start_new: if start_new:
self.init_page() self.init_page()
def end(self): def end(self):
try: try:
self.end_page(start_new=False) self.end_page(start_new=False)
self.canvas.save() self.pdf.end()
except: except:
traceback.print_exc() traceback.print_exc()
return False return False
finally: finally:
self.canvas = self.file_object = None self.pdf = self.file_object = None
return True return True
def type(self): def type(self):
@ -248,41 +239,36 @@ class PdfEngine(QPaintEngine):
self.graphics_state = state(self) self.graphics_state = state(self)
def convert_path(self, path): def convert_path(self, path):
p = self.canvas.beginPath() p = Path()
path = path.simplified()
i = 0 i = 0
while i < path.elementCount(): while i < path.elementCount():
elem = path.elementAt(i) elem = path.elementAt(i)
em = (elem.x, elem.y) em = (elem.x, elem.y)
i += 1 i += 1
if elem.isMoveTo(): if elem.isMoveTo():
p.moveTo(*em) p.move_to(*em)
elif elem.isLineTo(): elif elem.isLineTo():
p.lineTo(*em) p.line_to(*em)
elif elem.isCurveTo(): elif elem.isCurveTo():
if path.elementCount() > i+1: if path.elementCount() > i+1:
c1, c2 = map(lambda j:( c1, c2 = map(lambda j:(
path.elementAt(j).x, path.elementAt(j)), (i, i+1)) path.elementAt(j).x, path.elementAt(j).y), (i, i+1))
i += 2 i += 2
p.curveTo(*(c1 + c2 + em)) p.curve_to(*(c1 + c2 + em))
return p return p
def drawPath(self, path): def drawPath(self, path):
p = self.convert_path(path) p = self.convert_path(path)
old = self.canvas._fillMode fill_rule = {Qt.OddEvenFill:'evenodd',
self.canvas._fillMode = {Qt.OddEvenFill:FILL_EVEN_ODD, Qt.WindingFill:'winding'}[path.fillRule()]
Qt.WindingFill:FILL_NON_ZERO}[path.fillRule()] self.pdf.draw_path(p, stroke=self.do_stroke,
self.canvas.drawPath(p, stroke=self.do_stroke, fill=self.do_fill, fill_rule=fill_rule)
fill=self.do_fill)
self.canvas._fillMode = old
def set_clip(self, path): def add_clip(self, path):
p = self.convert_path(path) p = self.convert_path(path)
old = self.canvas._fillMode fill_rule = {Qt.OddEvenFill:'evenodd',
self.canvas._fillMode = {Qt.OddEvenFill:FILL_EVEN_ODD, Qt.WindingFill:'winding'}[path.fillRule()]
Qt.WindingFill:FILL_NON_ZERO}[path.fillRule()] self.pdf.add_clip(p, fill_rule=fill_rule)
self.canvas.clipPath(p, fill=0, stroke=0)
self.canvas._fillMode = old
def drawPoints(self, points): def drawPoints(self, points):
for point in points: for point in points:
@ -293,7 +279,7 @@ class PdfEngine(QPaintEngine):
def drawRects(self, rects): def drawRects(self, rects):
for rect in rects: for rect in rects:
bl = rect.topLeft() bl = rect.topLeft()
self.canvas.rect(bl.x(), bl.y(), rect.width(), rect.height(), self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
stroke=self.do_stroke, fill=self.do_fill) stroke=self.do_stroke, fill=self.do_fill)
def drawTextItem(self, point, text_item): def drawTextItem(self, point, text_item):
@ -315,7 +301,7 @@ class PdfEngine(QPaintEngine):
return return
to = self.canvas.beginText() to = self.canvas.beginText()
set_transform(QTransform(1, 0, 0, -1, point.x(), point.y()), to.setTextTransform) # set_transform(QTransform(1, 0, 0, -1, point.x(), point.y()), to.setTextTransform)
fontname = 'Times-Roman' fontname = 'Times-Roman'
to.setFont(fontname, sz) # TODO: Embed font to.setFont(fontname, sz) # TODO: Embed font
stretch = f.stretch() stretch = f.stretch()
@ -354,25 +340,23 @@ class PdfEngine(QPaintEngine):
draw_line('strikeout') draw_line('strikeout')
def drawPolygon(self, points, mode): def drawPolygon(self, points, mode):
points = [Point(p.x(), p.y()) for p in points] if not points: return
p = self.canvas.beginPath() p = Path()
p.moveTo(*points[0]) p.move_to(points[0].x(), points[0].y())
for point in points[1:]: for point in points[1:]:
p.lineTo(*point) p.line_to(point.x(), point.y())
p.close() if points[-1] != points[0]:
old = self.canvas._fillMode p.line_to(points[0].x(), points[0].y())
self.canvas._fillMode = {self.OddEvenMode:FILL_EVEN_ODD, fill_rule = {self.OddEvenMode:'evenodd',
self.WindingMode:FILL_NON_ZERO}.get(mode, self.WindingMode:'winding'}.get(mode, 'evenodd')
FILL_EVEN_ODD) self.pdf.draw_path(p, stroke=True, fill_rule=fill_rule,
self.canvas.drawPath(p, fill=(mode in (self.OddEvenMode, fill=(mode in (self.OddEvenMode, self.WindingMode, self.ConvexMode)))
self.WindingMode, self.ConvexMode)))
self.canvas._fillMode = old
def __enter__(self): def __enter__(self):
self.canvas.saveState() self.pdf.save_stack()
def __exit__(self, *args): def __exit__(self, *args):
self.canvas.restoreState() self.pdf.restore_stack()
class PdfDevice(QPaintDevice): # {{{ class PdfDevice(QPaintDevice): # {{{
@ -438,14 +422,13 @@ if __name__ == '__main__':
p.drawLine(0, 0, 5000, 0) p.drawLine(0, 0, 5000, 0)
p.restore() p.restore()
# f = p.font()
f = p.font() # f.setPointSize(24)
f.setPointSize(24) # f.setFamily('Times New Roman')
f.setFamily('Times New Roman') # p.setFont(f)
p.setFont(f) # # p.scale(2, 2)
# p.scale(2, 2) # p.rotate(45)
p.rotate(45) # p.drawText(QPoint(100, 300), 'Some text')
p.drawText(QPoint(100, 300), 'Some text')
finally: finally:
p.end() p.end()

View File

@ -0,0 +1,397 @@
#!/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 codecs, zlib, hashlib
from io import BytesIO
from future_builtins import map
from calibre.constants import (__appname__, __version__)
PDFVER = b'%PDF-1.6'
EOL = b'\n'
# Sizes {{{
inch = 72.0
cm = inch / 2.54
mm = cm * 0.1
pica = 12.0
_W, _H = (21*cm, 29.7*cm)
A6 = (_W*.5, _H*.5)
A5 = (_H*.5, _W)
A4 = (_W, _H)
A3 = (_H, _W*2)
A2 = (_W*2, _H*2)
A1 = (_H*2, _W*4)
A0 = (_W*4, _H*4)
LETTER = (8.5*inch, 11*inch)
LEGAL = (8.5*inch, 14*inch)
ELEVENSEVENTEEN = (11*inch, 17*inch)
_BW, _BH = (25*cm, 35.3*cm)
B6 = (_BW*.5, _BH*.5)
B5 = (_BH*.5, _BW)
B4 = (_BW, _BH)
B3 = (_BH*2, _BW)
B2 = (_BW*2, _BH*2)
B1 = (_BH*4, _BW*2)
B0 = (_BW*4, _BH*4)
# }}}
def serialize(o, stream):
if hasattr(o, 'pdf_serialize'):
o.pdf_serialize(stream)
elif isinstance(o, bool):
stream.write(b'true' if o else b'false')
elif isinstance(o, (int, float)):
stream.write(type(u'')(o).encode('ascii'))
elif o is None:
stream.write(b'null')
else:
raise ValueError('Unknown object: %r'%o)
class Name(unicode):
def pdf_serialize(self, stream):
raw = self.encode('ascii')
if len(raw) > 126:
raise ValueError('Name too long: %r'%self)
buf = [x if 33 < ord(x) < 126 and x != b'#' else b'#'+hex(ord(x)) for x
in raw]
stream.write(b'/'+b''.join(buf))
class String(unicode):
def pdf_serialize(self, stream):
s = self.replace('\\', '\\\\').replace('(', r'\(').replace(')', r'\)')
try:
raw = s.encode('latin1')
if raw.startswith(codecs.BOM_UTF16_BE):
raise UnicodeEncodeError('')
except UnicodeEncodeError:
raw = codecs.BOM_UTF16_BE + s.encode('utf-16-be')
stream.write(b'('+raw+b')')
class Dictionary(dict):
def pdf_serialize(self, stream):
stream.write(b'<<' + EOL)
for k, v in self.iteritems():
serialize(Name(k), stream)
stream.write(b' ')
serialize(v, stream)
stream.write(EOL)
stream.write(b'>>' + EOL)
class InlineDictionary(Dictionary):
def pdf_serialize(self, stream):
stream.write(b'<< ')
for k, v in self.iteritems():
serialize(Name(k), stream)
stream.write(b' ')
serialize(v, stream)
stream.write(b' ')
stream.write(b'>>')
class Array(list):
def pdf_serialize(self, stream):
stream.write(b'[')
for i, o in enumerate(self):
if i != 0:
stream.write(b' ')
serialize(o, stream)
stream.write(b']')
class Stream(BytesIO):
def __init__(self, compress=False):
BytesIO.__init__(self)
self.compress = compress
def pdf_serialize(self, stream):
raw = self.getvalue()
dl = len(raw)
filters = Array()
if self.compress:
filters.append(Name('FlateDecode'))
raw = zlib.compress(raw)
d = InlineDictionary({'Length':len(raw), 'DL':dl})
if filters:
d['Filters'] = filters
serialize(d, stream)
stream.write(EOL+b'stream'+EOL)
stream.write(raw)
stream.write(EOL+b'endstream'+EOL)
def write_line(self, raw=b''):
self.write(raw if isinstance(raw, bytes) else raw.encode('ascii'))
self.write(EOL)
def write(self, raw):
super(Stream, self).write(raw if isinstance(raw, bytes) else
raw.encode('ascii'))
class Reference(object):
def __init__(self, num, obj):
self.num, self.obj = num, obj
def pdf_serialize(self, stream):
raw = '%d 0 R'%self.num
stream.write(raw.encode('ascii'))
class IndirectObjects(object):
def __init__(self):
self._list = []
self._map = {}
self._offsets = []
def __len__(self):
return len(self._list)
def add(self, o):
self._list.append(o)
ref = Reference(len(self._list), o)
self._map[id(o)] = ref
self._offsets.append(None)
return ref
def commit(self, ref, stream):
self.write_obj(stream, ref.num, ref.obj)
def write_obj(self, stream, num, obj):
stream.write(EOL)
self._offsets[num-1] = stream.tell()
stream.write('%d 0 obj'%num)
stream.write(EOL)
serialize(obj, stream)
if stream.last_char != EOL:
stream.write(EOL)
stream.write('endobj')
stream.write(EOL)
def __getitem__(self, o):
try:
return self._map[id(self._list[o] if isinstance(o, int) else o)]
except (KeyError, IndexError):
raise KeyError('The object %r was not found'%o)
def pdf_serialize(self, stream):
for i, obj in enumerate(self._list):
offset = self._offsets[i]
if offset is None:
self.write_obj(stream, i+1, obj)
def write_xref(self, stream):
self.xref_offset = stream.tell()
stream.write(b'xref'+EOL)
stream.write('0 %d'%(1+len(self._offsets)))
stream.write(EOL)
stream.write('%010d 65535 f '%0)
stream.write(EOL)
for offset in self._offsets:
line = '%010d 00000 n '%offset
stream.write(line.encode('ascii') + EOL)
return self.xref_offset
class Page(Stream):
def __init__(self, parentref, *args, **kwargs):
super(Page, self).__init__(*args, **kwargs)
self.page_dict = Dictionary({
'Type': Name('Page'),
'Parent': parentref,
})
def end(self, objects, stream):
contents = objects.add(self)
objects.commit(contents, stream)
self.page_dict['Contents'] = contents
ret = objects.add(self.page_dict)
objects.commit(ret, stream)
return ret
class Path(object):
def __init__(self):
self.ops = []
def move_to(self, x, y):
self.ops.append((x, y, 'm'))
def line_to(self, x, y):
self.ops.append((x, y, 'l'))
def curve_to(self, x1, y1, x2, y2, x, y):
self.ops.append((x1, y1, x2, y2, x, y, 'c'))
class Catalog(Dictionary):
def __init__(self, pagetree):
super(Catalog, self).__init__({'Type':Name('Catalog'),
'Pages': pagetree})
class PageTree(Dictionary):
def __init__(self, page_size):
super(PageTree, self).__init__({'Type':Name('Pages'),
'MediaBox':Array([0, 0, page_size[0], page_size[1]]),
'Kids':Array(), 'Count':0,
})
def add_page(self, pageref):
self['Kids'].append(pageref)
self['Count'] += 1
class HashingStream(object):
def __init__(self, f):
self.f = f
self.tell = f.tell
self.hashobj = hashlib.sha256()
self.last_char = b''
def write(self, raw):
raw = raw if isinstance(raw, bytes) else raw.encode('ascii')
self.f.write(raw)
self.hashobj.update(raw)
if raw:
self.last_char = raw[-1]
class PDFStream(object):
PATH_OPS = {
# stroke fill fill-rule
( False, False, 'winding') : 'n',
( False, False, 'evenodd') : 'n',
( False, True, 'winding') : 'f',
( False, True, 'evenodd') : 'f*',
( True, False, 'winding') : 'S',
( True, False, 'evenodd') : 'S',
( True, True, 'winding') : 'B',
( True, True, 'evenodd') : 'B*',
}
def __init__(self, stream, page_size, compress=False):
self.stream = HashingStream(stream)
self.compress = compress
self.write_line(PDFVER)
self.write_line(b'%íì¦"')
creator = ('%s %s [http://calibre-ebook.com]'%(__appname__,
__version__))
self.write_line('%% Created by %s'%creator)
self.objects = IndirectObjects()
self.objects.add(PageTree(page_size))
self.objects.add(Catalog(self.page_tree))
self.current_page = Page(self.page_tree, compress=self.compress)
self.info = Dictionary({'Creator':String(creator),
'Producer':String(creator)})
@property
def page_tree(self):
return self.objects[0]
@property
def catalog(self):
return self.objects[1]
def write_line(self, byts=b''):
byts = byts if isinstance(byts, bytes) else byts.encode('ascii')
self.stream.write(byts + EOL)
def transform(self, *args):
if len(args) == 1:
m = args[0]
vals = [m.m11(), m.m12(), m.m21(), m.m22(), m.dx(), m.dy()]
else:
vals = args
cm = ' '.join(map(type(u''), vals))
self.current_page.write_line(cm + ' cm')
def set_rgb_colorspace(self):
self.current_page.write_line('/DeviceRGB CS /DeviceRGB cs')
def save_stack(self):
self.current_page.write_line('q')
def restore_stack(self):
self.current_page.write_line('Q')
def reset_stack(self):
self.current_page.write_line('Q q')
def draw_rect(self, x, y, width, height, stroke=True, fill=False):
self.current_page.write('%g %g %g %g re '%(x, y, width, height))
self.current_page.write_line(self.PATH_OPS[(stroke, fill, 'winding')])
def write_path(self, path):
for i, op in enumerate(path.ops):
if i != 0:
self.current_page.write_line()
for x in op:
self.current_page.write(type(u'')(x) + ' ')
def draw_path(self, path, stroke=True, fill=False, fill_rule='winding'):
if not path.ops: return
self.write_path(path)
self.current_page.write_line(self.PATH_OPS[(stroke, fill, fill_rule)])
def add_clip(self, path, fill_rule='winding'):
if not path.ops: return
op = 'W' if fill_rule == 'winding' else 'W*'
self.current_page.write(op + ' ' + 'n')
def set_dash(self, array, phase=0):
array = Array(array)
serialize(array, self.current_page)
self.current_page.write(b' ')
serialize(phase, self.current_page)
self.current_page.write_line(' d')
def set_line_width(self, width):
serialize(width, self.current_page)
self.current_page.write_line(' w')
def set_line_cap(self, style):
serialize({'flat':0, 'round':1, 'square':2}.get(style),
self.current_page)
self.current_page.write_line(' J')
def set_line_join(self, style):
serialize({'miter':0, 'round':1, 'bevel':2}[style], self.current_page)
self.current_page.write_line(' j')
def end_page(self):
pageref = self.current_page.end(self.objects, self.stream)
self.page_tree.obj.add_page(pageref)
self.current_page = Page(self.page_tree, compress=self.compress)
def end(self):
if self.current_page.getvalue():
self.end_page()
inforef = self.objects.add(self.info)
self.objects.pdf_serialize(self.stream)
self.write_line()
startxref = self.objects.write_xref(self.stream)
file_id = String(self.stream.hashobj.hexdigest().decode('ascii'))
self.write_line('trailer')
trailer = Dictionary({'Root':self.catalog, 'Size':len(self.objects)+1,
'ID':Array([file_id, file_id]), 'Info':inforef})
serialize(trailer, self.stream)
self.write_line('startxref')
self.write_line('%d'%startxref)
self.stream.write('%%EOF')