mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Fix clipping, basic text output (no font embedding) and better error handling
This commit is contained in:
parent
2f254bf0e0
commit
5eca5a7b5a
152
src/calibre/ebooks/pdf/render/common.py
Normal file
152
src/calibre/ebooks/pdf/render/common.py
Normal file
@ -0,0 +1,152 @@
|
||||
#!/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
|
||||
from io import BytesIO
|
||||
|
||||
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)
|
||||
# }}}
|
||||
|
||||
# Basic PDF datatypes {{{
|
||||
|
||||
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['Filter'] = 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'))
|
||||
# }}}
|
||||
|
@ -11,13 +11,14 @@ import sys, traceback
|
||||
from math import sqrt
|
||||
from collections import namedtuple
|
||||
from future_builtins import map
|
||||
from functools import wraps
|
||||
|
||||
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
||||
QTransform, QPainterPath)
|
||||
QTransform, QPainterPath, QFontMetricsF)
|
||||
|
||||
from calibre.constants import DEBUG
|
||||
from calibre.ebooks.pdf.render.serialize import (Color, inch, A4, PDFStream,
|
||||
Path)
|
||||
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path, Text)
|
||||
from calibre.ebooks.pdf.render.common import inch, A4
|
||||
|
||||
XDPI = 1200
|
||||
YDPI = 1200
|
||||
@ -25,6 +26,17 @@ YDPI = 1200
|
||||
Point = namedtuple('Point', 'x y')
|
||||
ColorState = namedtuple('ColorState', 'color opacity do')
|
||||
|
||||
def store_error(func):
|
||||
|
||||
@wraps(func)
|
||||
def errh(self, *args, **kwargs):
|
||||
try:
|
||||
func(self, *args, **kwargs)
|
||||
except:
|
||||
self.errors.append(traceback.format_exc())
|
||||
|
||||
return errh
|
||||
|
||||
class GraphicsState(object): # {{{
|
||||
|
||||
def __init__(self):
|
||||
@ -156,8 +168,9 @@ class GraphicsState(object): # {{{
|
||||
|
||||
# Now apply the new operations
|
||||
for op, val in ops.iteritems():
|
||||
self.apply(op, val, engine, pdf)
|
||||
self.current_state[op] = val
|
||||
if op != 'clip':
|
||||
self.apply(op, val, engine, pdf)
|
||||
self.current_state[op] = val
|
||||
|
||||
def apply(self, op, val, engine, pdf):
|
||||
getattr(self, 'apply_'+op)(val, engine, pdf)
|
||||
@ -219,8 +232,9 @@ class PdfEngine(QPaintEngine):
|
||||
self.do_stroke = True
|
||||
self.do_fill = False
|
||||
self.scale = sqrt(sy**2 + sx**2)
|
||||
self.yscale = sy
|
||||
self.xscale, self.yscale = sx, sy
|
||||
self.graphics_state = GraphicsState()
|
||||
self.errors = []
|
||||
|
||||
def init_page(self):
|
||||
self.pdf.transform(self.pdf_system)
|
||||
@ -246,7 +260,7 @@ class PdfEngine(QPaintEngine):
|
||||
compress=not DEBUG)
|
||||
self.init_page()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.errors.append(traceback.format_exc())
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -261,7 +275,7 @@ class PdfEngine(QPaintEngine):
|
||||
self.end_page(start_new=False)
|
||||
self.pdf.end()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.errors.append(traceback.format_exc())
|
||||
return False
|
||||
finally:
|
||||
self.pdf = self.file_object = None
|
||||
@ -270,12 +284,15 @@ class PdfEngine(QPaintEngine):
|
||||
def type(self):
|
||||
return QPaintEngine.Pdf
|
||||
|
||||
@store_error
|
||||
def drawPixmap(self, rect, pixmap, source_rect):
|
||||
print ('TODO: drawPixmap() currently unimplemented')
|
||||
|
||||
@store_error
|
||||
def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor):
|
||||
print ('TODO: drawImage() currently unimplemented')
|
||||
|
||||
@store_error
|
||||
def updateState(self, state):
|
||||
self.graphics_state.read(state)
|
||||
self.graphics_state(self)
|
||||
@ -299,6 +316,7 @@ class PdfEngine(QPaintEngine):
|
||||
p.curve_to(*(c1 + c2 + em))
|
||||
return p
|
||||
|
||||
@store_error
|
||||
def drawPath(self, path):
|
||||
p = self.convert_path(path)
|
||||
fill_rule = {Qt.OddEvenFill:'evenodd',
|
||||
@ -312,6 +330,7 @@ class PdfEngine(QPaintEngine):
|
||||
Qt.WindingFill:'winding'}[path.fillRule()]
|
||||
self.pdf.add_clip(p, fill_rule=fill_rule)
|
||||
|
||||
@store_error
|
||||
def drawPoints(self, points):
|
||||
p = Path()
|
||||
for point in points:
|
||||
@ -319,14 +338,16 @@ class PdfEngine(QPaintEngine):
|
||||
p.line_to(point.x(), point.y() + 0.001)
|
||||
self.pdf.draw_path(p, stroke=self.do_stroke, fill=False)
|
||||
|
||||
@store_error
|
||||
def drawRects(self, rects):
|
||||
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)
|
||||
|
||||
@store_error
|
||||
def drawTextItem(self, point, text_item):
|
||||
# super(PdfEngine, self).drawTextItem(point, text_item)
|
||||
# super(PdfEngine, self).drawTextItem(point+QPoint(0, 300), text_item)
|
||||
f = text_item.font()
|
||||
px, pt = f.pixelSize(), f.pointSizeF()
|
||||
if px == -1:
|
||||
@ -343,37 +364,46 @@ class PdfEngine(QPaintEngine):
|
||||
self.do_fill, self.do_stroke = f, s
|
||||
return
|
||||
|
||||
to = self.canvas.beginText()
|
||||
# set_transform(QTransform(1, 0, 0, -1, point.x(), point.y()), to.setTextTransform)
|
||||
fontname = 'Times-Roman'
|
||||
to.setFont(fontname, sz) # TODO: Embed font
|
||||
to = Text()
|
||||
to.size = sz
|
||||
to.set_transform(1, 0, 0, -1, point.x(), point.y())
|
||||
stretch = f.stretch()
|
||||
if stretch != 100:
|
||||
to.setHorizontalScale(stretch)
|
||||
to.horizontal_scale = stretch
|
||||
ws = f.wordSpacing()
|
||||
if ws != 0:
|
||||
to.setWordSpacing(self.map_dx(ws))
|
||||
to.word_spacing = ws
|
||||
spacing = f.letterSpacing()
|
||||
st = f.letterSpacingType()
|
||||
if st == f.AbsoluteSpacing and spacing != 0:
|
||||
to.setCharSpace(spacing)
|
||||
# TODO: Handle percentage letter spacing
|
||||
to.char_space = spacing/self.scale
|
||||
if st == f.PercentageSpacing and spacing not in {100, 0}:
|
||||
# TODO: Implement this with the TJ operator
|
||||
avg_char_width = QFontMetricsF(f).averageCharWidth()
|
||||
to.char_space = (spacing - 100) * avg_char_width / 100
|
||||
text = type(u'')(text_item.text())
|
||||
to.textOut(text)
|
||||
# TODO: handle colors
|
||||
self.canvas.drawText(to)
|
||||
to.text = text
|
||||
with self:
|
||||
self.graphics_state.apply_fill(self.graphics_state.current_state['stroke'],
|
||||
self, self.pdf)
|
||||
self.pdf.draw_text(to)
|
||||
|
||||
def draw_line(kind='underline'):
|
||||
tw = self.canvas.stringWidth(text, fontname, sz)
|
||||
p = self.canvas.beginPath()
|
||||
m = QFontMetricsF(f)
|
||||
tw = m.width(text)
|
||||
p = Path()
|
||||
if kind == 'underline':
|
||||
dy = -text_item.descent()
|
||||
dy = m.underlinePos()
|
||||
elif kind == 'overline':
|
||||
dy = text_item.ascent()
|
||||
dy = -m.overlinePos()
|
||||
elif kind == 'strikeout':
|
||||
dy = text_item.ascent()/2
|
||||
p.moveTo(point.x, point.y+dy)
|
||||
p.lineTo(point.x+tw, point.y+dy)
|
||||
dy = -m.strikeOutPos()
|
||||
p.move_to(point.x(), point.y()+dy)
|
||||
p.line_to(point.x()+tw, point.y()+dy)
|
||||
with self:
|
||||
self.graphics_state.apply_line_width(m.lineWidth(),
|
||||
self, self.pdf)
|
||||
self.pdf.draw_path(p, stroke=True, fill=False)
|
||||
|
||||
if f.underline():
|
||||
draw_line()
|
||||
@ -382,6 +412,7 @@ class PdfEngine(QPaintEngine):
|
||||
if f.strikeOut():
|
||||
draw_line('strikeout')
|
||||
|
||||
@store_error
|
||||
def drawPolygon(self, points, mode):
|
||||
if not points: return
|
||||
p = Path()
|
||||
@ -397,8 +428,10 @@ class PdfEngine(QPaintEngine):
|
||||
|
||||
def __enter__(self):
|
||||
self.pdf.save_stack()
|
||||
self.saved_ps = (self.do_stroke, self.do_fill)
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.do_stroke, self.do_fill = self.saved_ps
|
||||
self.pdf.restore_stack()
|
||||
|
||||
class PdfDevice(QPaintDevice): # {{{
|
||||
@ -470,13 +503,18 @@ 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.setUnderline(True)
|
||||
f.setFamily('Times New Roman')
|
||||
p.setFont(f)
|
||||
# p.scale(2, 2)
|
||||
p.rotate(45)
|
||||
p.setPen(QColor(0, 255, 0))
|
||||
p.drawText(QPoint(100, 300), 'Some text')
|
||||
finally:
|
||||
p.end()
|
||||
if dev.engine.errors:
|
||||
for err in dev.engine.errors: print (err)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
35
src/calibre/ebooks/pdf/render/fonts.py
Normal file
35
src/calibre/ebooks/pdf/render/fonts.py
Normal file
@ -0,0 +1,35 @@
|
||||
#!/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'
|
||||
|
||||
from calibre.ebooks.pdf.render.common import (
|
||||
Dictionary, Name)
|
||||
|
||||
STANDARD_FONTS = {
|
||||
'Times-Roman', 'Helvetica', 'Courier', 'Symbol', 'Times-Bold',
|
||||
'Helvetica-Bold', 'Courier-Bold', 'ZapfDingbats', 'Times-Italic',
|
||||
'Helvetica-Oblique', 'Courier-Oblique', 'Times-BoldItalic',
|
||||
'Helvetica-BoldOblique', 'Courier-BoldOblique', }
|
||||
|
||||
class FontManager(object):
|
||||
|
||||
def __init__(self, objects):
|
||||
self.objects = objects
|
||||
self.std_map = {}
|
||||
|
||||
def add_standard_font(self, name):
|
||||
if name not in STANDARD_FONTS:
|
||||
raise ValueError('%s is not a standard font'%name)
|
||||
if name not in self.std_map:
|
||||
self.std_map[name] = self.objects.add(Dictionary({
|
||||
'Type':Name('Font'),
|
||||
'Subtype':Name('Type1'),
|
||||
'BaseFont':Name(name)
|
||||
}))
|
||||
return self.std_map[name]
|
||||
|
@ -7,156 +7,19 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import codecs, zlib, hashlib
|
||||
from io import BytesIO
|
||||
import hashlib
|
||||
from future_builtins import map
|
||||
from collections import namedtuple
|
||||
|
||||
from calibre.constants import (__appname__, __version__)
|
||||
from calibre.ebooks.pdf.render.common import (
|
||||
Reference, EOL, serialize, Stream, Dictionary, String, Name, Array)
|
||||
from calibre.ebooks.pdf.render.fonts import FontManager
|
||||
|
||||
PDFVER = b'%PDF-1.6'
|
||||
EOL = b'\n'
|
||||
|
||||
Color = namedtuple('Color', 'red green blue opacity')
|
||||
|
||||
# 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)
|
||||
# }}}
|
||||
|
||||
# Basic PDF datatypes {{{
|
||||
|
||||
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['Filter'] = 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):
|
||||
@ -222,6 +85,7 @@ class Page(Stream):
|
||||
'Parent': parentref,
|
||||
})
|
||||
self.opacities = {}
|
||||
self.fonts = {}
|
||||
|
||||
def set_opacity(self, opref):
|
||||
if opref not in self.opacities:
|
||||
@ -230,6 +94,11 @@ class Page(Stream):
|
||||
serialize(Name(name), self)
|
||||
self.write(b' gs ')
|
||||
|
||||
def add_font(self, fontref):
|
||||
if fontref not in self.fonts:
|
||||
self.fonts[fontref] = 'F%d'%len(self.fonts)
|
||||
return self.fonts[fontref]
|
||||
|
||||
def add_resources(self):
|
||||
r = Dictionary()
|
||||
if self.opacities:
|
||||
@ -237,6 +106,11 @@ class Page(Stream):
|
||||
for opref, name in self.opacities.iteritems():
|
||||
extgs[name] = opref
|
||||
r['ExtGState'] = extgs
|
||||
if self.fonts:
|
||||
fonts = Dictionary()
|
||||
for ref, name in self.fonts.iteritems():
|
||||
fonts[name] = ref
|
||||
r['Font'] = fonts
|
||||
if r:
|
||||
self.page_dict['Resources'] = r
|
||||
|
||||
@ -263,6 +137,44 @@ class Path(object):
|
||||
def curve_to(self, x1, y1, x2, y2, x, y):
|
||||
self.ops.append((x1, y1, x2, y2, x, y, 'c'))
|
||||
|
||||
class Text(object):
|
||||
|
||||
def __init__(self):
|
||||
self.transform = self.default_transform = [1, 0, 0, 1, 0, 0]
|
||||
self.font_name = 'Times-Roman'
|
||||
self.font_path = None
|
||||
self.horizontal_scale = self.default_horizontal_scale = 100
|
||||
self.word_spacing = self.default_word_spacing = 0
|
||||
self.char_space = self.default_char_space = 0
|
||||
self.size = 12
|
||||
self.text = ''
|
||||
|
||||
def set_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
|
||||
self.transform = vals
|
||||
|
||||
def pdf_serialize(self, stream, font_name):
|
||||
if not self.text: return
|
||||
stream.write_line('BT ')
|
||||
serialize(Name(font_name), stream)
|
||||
stream.write(' %g Tf '%self.size)
|
||||
stream.write(' '.join(map(type(u''), self.transform)) + ' Tm ')
|
||||
if self.horizontal_scale != self.default_horizontal_scale:
|
||||
stream.write('%g Tz '%self.horizontal_scale)
|
||||
if self.word_spacing != self.default_word_spacing:
|
||||
stream.write('%g Tw '%self.word_spacing)
|
||||
if self.char_space != self.default_char_space:
|
||||
stream.write('%g Tc '%self.char_space)
|
||||
stream.write_line()
|
||||
serialize(String(self.text), stream)
|
||||
stream.write(' Tj ')
|
||||
stream.write_line('ET')
|
||||
|
||||
|
||||
class Catalog(Dictionary):
|
||||
|
||||
def __init__(self, pagetree):
|
||||
@ -325,6 +237,7 @@ class PDFStream(object):
|
||||
self.info = Dictionary({'Creator':String(creator),
|
||||
'Producer':String(creator)})
|
||||
self.stroke_opacities, self.fill_opacities = {}, {}
|
||||
self.font_manager = FontManager(self.objects)
|
||||
|
||||
@property
|
||||
def page_tree(self):
|
||||
@ -377,8 +290,9 @@ class PDFStream(object):
|
||||
|
||||
def add_clip(self, path, fill_rule='winding'):
|
||||
if not path.ops: return
|
||||
self.write_path(path)
|
||||
op = 'W' if fill_rule == 'winding' else 'W*'
|
||||
self.current_page.write(op + ' ' + 'n')
|
||||
self.current_page.write_line(op + ' ' + 'n')
|
||||
|
||||
def set_dash(self, array, phase=0):
|
||||
array = Array(array)
|
||||
@ -421,6 +335,14 @@ class PDFStream(object):
|
||||
self.page_tree.obj.add_page(pageref)
|
||||
self.current_page = Page(self.page_tree, compress=self.compress)
|
||||
|
||||
def draw_text(self, text_object):
|
||||
if text_object.font_path is None:
|
||||
fontref = self.font_manager.add_standard_font(text_object.font_name)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
name = self.current_page.add_font(fontref)
|
||||
text_object.pdf_serialize(self.current_page, name)
|
||||
|
||||
def end(self):
|
||||
if self.current_page.getvalue():
|
||||
self.end_page()
|
||||
|
Loading…
x
Reference in New Issue
Block a user