mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
PDF engine: Get rid of the unneccessary reportlab
This commit is contained in:
parent
a3c5ee351f
commit
e3121fe618
11
src/calibre/ebooks/pdf/render/__init__.py
Normal file
11
src/calibre/ebooks/pdf/render/__init__.py
Normal 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'
|
||||
|
||||
|
||||
|
@ -15,20 +15,14 @@ from future_builtins import map
|
||||
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
||||
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.ebooks.pdf.render.serialize import inch, A4, PDFStream, Path
|
||||
|
||||
XDPI = 1200
|
||||
YDPI = 1200
|
||||
|
||||
Point = namedtuple('Point', 'x y')
|
||||
|
||||
def set_transform(transform, func):
|
||||
func(transform.m11(), transform.m12(), transform.m21(), transform.m22(), transform.dx(), transform.dy())
|
||||
Color = namedtuple('Color', 'red green blue opacity')
|
||||
|
||||
class GraphicsState(object): # {{{
|
||||
|
||||
@ -51,30 +45,24 @@ class GraphicsState(object): # {{{
|
||||
if flags & QPaintEngine.DirtyBrush:
|
||||
brush = state.brush()
|
||||
color = brush.color()
|
||||
alpha = color.alphaF()
|
||||
if alpha == 1.0: alpha = None
|
||||
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)
|
||||
self.ops['do_fill'] = 0 if (color.alpha() == 0 or brush.style() == Qt.NoBrush) else 1
|
||||
self.ops['fill_color'] = Color(*color.getRgbF())
|
||||
|
||||
if flags & QPaintEngine.DirtyPen:
|
||||
pen = state.pen()
|
||||
brush = pen.brush()
|
||||
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() ==
|
||||
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],
|
||||
Qt.DashDotDotLine:[3, 2, 1, 2, 1, 2]}.get(pen.style(), [])
|
||||
self.ops['dash'] = ps
|
||||
self.ops['line_width'] = pen.widthF()
|
||||
self.ops['stroke_color'] = Color(color.red(), color.green(),
|
||||
color.blue(), alpha=alpha)
|
||||
self.ops['line_cap'] = {Qt.FlatCap:0, Qt.RoundCap:1,
|
||||
Qt.SquareCap:2}.get(pen.capStyle(), 0)
|
||||
self.ops['line_join'] = {Qt.MiterJoin:0, Qt.RoundJoin:1,
|
||||
Qt.BevelJoin:2}.get(pen.joinStyle(), 0)
|
||||
self.ops['stroke_color'] = Color(*color.getRgbF())
|
||||
self.ops['line_cap'] = {Qt.FlatCap:'flat', Qt.RoundCap:'round',
|
||||
Qt.SquareCap:'square'}.get(pen.capStyle(), 'flat')
|
||||
self.ops['line_join'] = {Qt.MiterJoin:'miter', Qt.RoundJoin:'round',
|
||||
Qt.BevelJoin:'bevel'}.get(pen.joinStyle(), 'miter')
|
||||
|
||||
if flags & QPaintEngine.DirtyClipPath:
|
||||
self.ops['clip'] = (state.clipOperation(), state.clipPath())
|
||||
@ -87,14 +75,14 @@ class GraphicsState(object): # {{{
|
||||
# TODO: Add support for opacity
|
||||
|
||||
def __call__(self, engine):
|
||||
canvas = engine.canvas
|
||||
pdf = engine.pdf
|
||||
ops = self.ops
|
||||
current_transform = ops.get('transform', None)
|
||||
srn = self.stack_reset_needed
|
||||
|
||||
if srn:
|
||||
canvas.restoreState()
|
||||
canvas.saveState()
|
||||
pdf.restore_stack()
|
||||
pdf.save_stack()
|
||||
# Since we have reset the stack we need to re-apply all previous
|
||||
# operations
|
||||
ops = engine.graphics_state.ops.copy()
|
||||
@ -125,40 +113,40 @@ class GraphicsState(object): # {{{
|
||||
ops['clip'] = (Qt.NoClip, None)
|
||||
path = ops['clip'][1]
|
||||
if path is not None:
|
||||
engine.set_clip(path)
|
||||
engine.add_clip(path)
|
||||
elif prev_clip_path is not None:
|
||||
# Re-apply the previous clip path since no clipping operation was
|
||||
# specified
|
||||
engine.set_clip(prev_clip_path)
|
||||
engine.add_clip(prev_clip_path)
|
||||
ops['clip'] = (Qt.ReplaceClip, prev_clip_path)
|
||||
|
||||
# Apply transform
|
||||
if current_transform is not None:
|
||||
engine.qt_system = current_transform
|
||||
set_transform(current_transform, canvas.transform)
|
||||
pdf.transform(current_transform)
|
||||
|
||||
if 'fill_color' in ops:
|
||||
canvas.setFillColor(ops['fill_color'])
|
||||
if 'stroke_color' in ops:
|
||||
canvas.setStrokeColor(ops['stroke_color'])
|
||||
# if 'fill_color' in ops:
|
||||
# canvas.setFillColor(ops['fill_color'])
|
||||
# if 'stroke_color' in ops:
|
||||
# canvas.setStrokeColor(ops['stroke_color'])
|
||||
for x in ('fill', 'stroke'):
|
||||
x = 'do_'+x
|
||||
if x in ops:
|
||||
setattr(canvas, x, ops[x])
|
||||
setattr(engine, x, ops[x])
|
||||
if 'dash' in ops:
|
||||
canvas.setDash(ops['dash'])
|
||||
pdf.set_dash(ops['dash'])
|
||||
if 'line_width' in ops:
|
||||
canvas.setLineWidth(ops['line_width'])
|
||||
pdf.set_line_width(ops['line_width'])
|
||||
if 'line_cap' in ops:
|
||||
canvas.setLineCap(ops['line_cap'])
|
||||
pdf.set_line_cap(ops['line_cap'])
|
||||
if 'line_join' in ops:
|
||||
canvas.setLineJoin(ops['line_join'])
|
||||
pdf.set_line_join(ops['line_join'])
|
||||
|
||||
if not srn:
|
||||
# Add the operations from the previous state object that were not
|
||||
# updated in this state object. This is needed to allow stack
|
||||
# resetting to work.
|
||||
ops = canvas.graphics_state.ops.copy()
|
||||
ops = engine.graphics_state.ops.copy()
|
||||
ops.update(self.ops)
|
||||
self.ops = ops
|
||||
|
||||
@ -197,8 +185,11 @@ class PdfEngine(QPaintEngine):
|
||||
self.graphics_state = GraphicsState()
|
||||
|
||||
def init_page(self):
|
||||
set_transform(self.pdf_system, self.canvas.transform)
|
||||
self.canvas.saveState()
|
||||
self.pdf.transform(self.pdf_system)
|
||||
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
|
||||
def features(self):
|
||||
@ -208,9 +199,9 @@ class PdfEngine(QPaintEngine):
|
||||
|
||||
def begin(self, device):
|
||||
try:
|
||||
self.canvas = Canvas(self.file_object,
|
||||
pageCompression=0 if DEBUG else 1,
|
||||
pagesize=(self.page_width, self.page_height))
|
||||
self.pdf = PDFStream(self.file_object, (self.page_width,
|
||||
self.page_height),
|
||||
compress=0 if DEBUG else 1)
|
||||
self.init_page()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
@ -218,20 +209,20 @@ class PdfEngine(QPaintEngine):
|
||||
return True
|
||||
|
||||
def end_page(self, start_new=True):
|
||||
self.canvas.restoreState()
|
||||
self.canvas.showPage()
|
||||
self.pdf.restore_stack()
|
||||
self.pdf.end_page()
|
||||
if start_new:
|
||||
self.init_page()
|
||||
|
||||
def end(self):
|
||||
try:
|
||||
self.end_page(start_new=False)
|
||||
self.canvas.save()
|
||||
self.pdf.end()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
finally:
|
||||
self.canvas = self.file_object = None
|
||||
self.pdf = self.file_object = None
|
||||
return True
|
||||
|
||||
def type(self):
|
||||
@ -248,41 +239,36 @@ class PdfEngine(QPaintEngine):
|
||||
self.graphics_state = state(self)
|
||||
|
||||
def convert_path(self, path):
|
||||
p = self.canvas.beginPath()
|
||||
path = path.simplified()
|
||||
p = Path()
|
||||
i = 0
|
||||
while i < path.elementCount():
|
||||
elem = path.elementAt(i)
|
||||
em = (elem.x, elem.y)
|
||||
i += 1
|
||||
if elem.isMoveTo():
|
||||
p.moveTo(*em)
|
||||
p.move_to(*em)
|
||||
elif elem.isLineTo():
|
||||
p.lineTo(*em)
|
||||
p.line_to(*em)
|
||||
elif elem.isCurveTo():
|
||||
if path.elementCount() > i+1:
|
||||
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
|
||||
p.curveTo(*(c1 + c2 + em))
|
||||
p.curve_to(*(c1 + c2 + em))
|
||||
return p
|
||||
|
||||
def drawPath(self, path):
|
||||
p = self.convert_path(path)
|
||||
old = self.canvas._fillMode
|
||||
self.canvas._fillMode = {Qt.OddEvenFill:FILL_EVEN_ODD,
|
||||
Qt.WindingFill:FILL_NON_ZERO}[path.fillRule()]
|
||||
self.canvas.drawPath(p, stroke=self.do_stroke,
|
||||
fill=self.do_fill)
|
||||
self.canvas._fillMode = old
|
||||
fill_rule = {Qt.OddEvenFill:'evenodd',
|
||||
Qt.WindingFill:'winding'}[path.fillRule()]
|
||||
self.pdf.draw_path(p, stroke=self.do_stroke,
|
||||
fill=self.do_fill, fill_rule=fill_rule)
|
||||
|
||||
def set_clip(self, path):
|
||||
def add_clip(self, path):
|
||||
p = self.convert_path(path)
|
||||
old = self.canvas._fillMode
|
||||
self.canvas._fillMode = {Qt.OddEvenFill:FILL_EVEN_ODD,
|
||||
Qt.WindingFill:FILL_NON_ZERO}[path.fillRule()]
|
||||
self.canvas.clipPath(p, fill=0, stroke=0)
|
||||
self.canvas._fillMode = old
|
||||
fill_rule = {Qt.OddEvenFill:'evenodd',
|
||||
Qt.WindingFill:'winding'}[path.fillRule()]
|
||||
self.pdf.add_clip(p, fill_rule=fill_rule)
|
||||
|
||||
def drawPoints(self, points):
|
||||
for point in points:
|
||||
@ -293,7 +279,7 @@ class PdfEngine(QPaintEngine):
|
||||
def drawRects(self, rects):
|
||||
for rect in rects:
|
||||
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)
|
||||
|
||||
def drawTextItem(self, point, text_item):
|
||||
@ -315,7 +301,7 @@ class PdfEngine(QPaintEngine):
|
||||
return
|
||||
|
||||
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'
|
||||
to.setFont(fontname, sz) # TODO: Embed font
|
||||
stretch = f.stretch()
|
||||
@ -354,25 +340,23 @@ class PdfEngine(QPaintEngine):
|
||||
draw_line('strikeout')
|
||||
|
||||
def drawPolygon(self, points, mode):
|
||||
points = [Point(p.x(), p.y()) for p in points]
|
||||
p = self.canvas.beginPath()
|
||||
p.moveTo(*points[0])
|
||||
if not points: return
|
||||
p = Path()
|
||||
p.move_to(points[0].x(), points[0].y())
|
||||
for point in points[1:]:
|
||||
p.lineTo(*point)
|
||||
p.close()
|
||||
old = self.canvas._fillMode
|
||||
self.canvas._fillMode = {self.OddEvenMode:FILL_EVEN_ODD,
|
||||
self.WindingMode:FILL_NON_ZERO}.get(mode,
|
||||
FILL_EVEN_ODD)
|
||||
self.canvas.drawPath(p, fill=(mode in (self.OddEvenMode,
|
||||
self.WindingMode, self.ConvexMode)))
|
||||
self.canvas._fillMode = old
|
||||
p.line_to(point.x(), point.y())
|
||||
if points[-1] != points[0]:
|
||||
p.line_to(points[0].x(), points[0].y())
|
||||
fill_rule = {self.OddEvenMode:'evenodd',
|
||||
self.WindingMode:'winding'}.get(mode, 'evenodd')
|
||||
self.pdf.draw_path(p, stroke=True, fill_rule=fill_rule,
|
||||
fill=(mode in (self.OddEvenMode, self.WindingMode, self.ConvexMode)))
|
||||
|
||||
def __enter__(self):
|
||||
self.canvas.saveState()
|
||||
self.pdf.save_stack()
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.canvas.restoreState()
|
||||
self.pdf.restore_stack()
|
||||
|
||||
class PdfDevice(QPaintDevice): # {{{
|
||||
|
||||
@ -438,14 +422,13 @@ if __name__ == '__main__':
|
||||
p.drawLine(0, 0, 5000, 0)
|
||||
p.restore()
|
||||
|
||||
|
||||
f = p.font()
|
||||
f.setPointSize(24)
|
||||
f.setFamily('Times New Roman')
|
||||
p.setFont(f)
|
||||
# p.scale(2, 2)
|
||||
p.rotate(45)
|
||||
p.drawText(QPoint(100, 300), 'Some text')
|
||||
# f = p.font()
|
||||
# f.setPointSize(24)
|
||||
# f.setFamily('Times New Roman')
|
||||
# p.setFont(f)
|
||||
# # p.scale(2, 2)
|
||||
# p.rotate(45)
|
||||
# p.drawText(QPoint(100, 300), 'Some text')
|
||||
finally:
|
||||
p.end()
|
||||
|
||||
|
397
src/calibre/ebooks/pdf/render/serialize.py
Normal file
397
src/calibre/ebooks/pdf/render/serialize.py
Normal 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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user