Added printing requirements

This commit is contained in:
Tom Price
2014-12-07 17:32:24 +00:00
parent c76d12d877
commit 7ba11a2db9
571 changed files with 143368 additions and 6 deletions

View File

@@ -0,0 +1,5 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/__init__.py
__version__=''' $Id$ '''
__doc__='''Defines a high-level Canvas interface for creating PDF files'''

1837
reportlab/pdfgen/canvas.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,127 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/pathobject.py
__version__=''' $Id$ '''
__doc__="""
PDFPathObject is an efficient way to draw paths on a Canvas. Do not
instantiate directly, obtain one from the Canvas instead.
Progress Reports:
8.83, 2000-01-13, gmcm: created from pdfgen.py
"""
from reportlab.pdfgen import pdfgeom
from reportlab.lib.rl_accel import fp_str
class PDFPathObject:
"""Represents a graphic path. There are certain 'modes' to PDF
drawing, and making a separate object to expose Path operations
ensures they are completed with no run-time overhead. Ask
the Canvas for a PDFPath with getNewPathObject(); moveto/lineto/
curveto wherever you want; add whole shapes; and then add it back
into the canvas with one of the relevant operators.
Path objects are probably not long, so we pack onto one line
the code argument allows a canvas to get the operatiosn appended directly so
avoiding the final getCode
"""
def __init__(self,code=None):
self._code = (code,[])[code is None]
self._code_append = self._init_code_append
def _init_code_append(self,c):
assert c.endswith(' m') or c.endswith(' re'), 'path must start with a moveto or rect'
code_append = self._code.append
code_append('n')
code_append(c)
self._code_append = code_append
def getCode(self):
"pack onto one line; used internally"
return ' '.join(self._code)
def moveTo(self, x, y):
self._code_append('%s m' % fp_str(x,y))
def lineTo(self, x, y):
self._code_append('%s l' % fp_str(x,y))
def curveTo(self, x1, y1, x2, y2, x3, y3):
self._code_append('%s c' % fp_str(x1, y1, x2, y2, x3, y3))
def arc(self, x1,y1, x2,y2, startAng=0, extent=90):
"""Contributed to piddlePDF by Robert Kern, 28/7/99.
Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
starting at startAng degrees and covering extent degrees. Angles
start with 0 to the right (+x) and increase counter-clockwise.
These should have x1<x2 and y1<y2.
The algorithm is an elliptical generalization of the formulae in
Jim Fitzsimmon's TeX tutorial <URL: http://www.tinaja.com/bezarc1.pdf>."""
self._curves(pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent))
def arcTo(self, x1,y1, x2,y2, startAng=0, extent=90):
"""Like arc, but draws a line from the current point to
the start if the start is not the current point."""
self._curves(pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent),'lineTo')
def rect(self, x, y, width, height):
"""Adds a rectangle to the path"""
self._code_append('%s re' % fp_str((x, y, width, height)))
def ellipse(self, x, y, width, height):
"""adds an ellipse to the path"""
self._curves(pdfgeom.bezierArc(x, y, x + width,y + height, 0, 360))
def _curves(self,curves,initial='moveTo'):
getattr(self,initial)(*curves[0][:2])
for curve in curves:
self.curveTo(*curve[2:])
def circle(self, x_cen, y_cen, r):
"""adds a circle to the path"""
x1 = x_cen - r
y1 = y_cen - r
width = height = 2*r
self.ellipse(x1, y1, width, height)
def roundRect(self, x, y, width, height, radius):
"""Draws a rectangle with rounded corners. The corners are
approximately quadrants of a circle, with the given radius."""
#use a precomputed set of factors for the bezier approximation
#to a circle. There are six relevant points on the x axis and y axis.
#sketch them and it should all make sense!
t = 0.4472 * radius
x0 = x
x1 = x0 + t
x2 = x0 + radius
x3 = x0 + width - radius
x4 = x0 + width - t
x5 = x0 + width
y0 = y
y1 = y0 + t
y2 = y0 + radius
y3 = y0 + height - radius
y4 = y0 + height - t
y5 = y0 + height
self.moveTo(x2, y0)
self.lineTo(x3, y0) #bottom row
self.curveTo(x4, y0, x5, y1, x5, y2) #bottom right
self.lineTo(x5, y3) #right edge
self.curveTo(x5, y4, x4, y5, x3, y5) #top right
self.lineTo(x2, y5) #top row
self.curveTo(x1, y5, x0, y4, x0, y3) #top left
self.lineTo(x0, y2) #left edge
self.curveTo(x0, y1, x1, y0, x2, y0) #bottom left
self.close()
def close(self):
"draws a line back to where it started"
self._code_append('h')

View File

@@ -0,0 +1,77 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/pdfgeom.py
__version__=''' $Id$ '''
__doc__="""
This module includes any mathematical methods needed for PIDDLE.
It should have no dependencies beyond the Python library.
So far, just Robert Kern's bezierArc.
"""
from math import sin, cos, pi, ceil
def bezierArc(x1,y1, x2,y2, startAng=0, extent=90):
"""bezierArc(x1,y1, x2,y2, startAng=0, extent=90) --> List of Bezier
curve control points.
(x1, y1) and (x2, y2) are the corners of the enclosing rectangle. The
coordinate system has coordinates that increase to the right and down.
Angles, measured in degress, start with 0 to the right (the positive X
axis) and increase counter-clockwise. The arc extends from startAng
to startAng+extent. I.e. startAng=0 and extent=180 yields an openside-down
semi-circle.
The resulting coordinates are of the form (x1,y1, x2,y2, x3,y3, x4,y4)
such that the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and
(x3, y3) as their respective Bezier control points."""
x1,y1, x2,y2 = min(x1,x2), max(y1,y2), max(x1,x2), min(y1,y2)
if abs(extent) <= 90:
arcList = [startAng]
fragAngle = float(extent)
Nfrag = 1
else:
arcList = []
Nfrag = int(ceil(abs(extent)/90.))
fragAngle = float(extent) / Nfrag
x_cen = (x1+x2)/2.
y_cen = (y1+y2)/2.
rx = (x2-x1)/2.
ry = (y2-y1)/2.
halfAng = fragAngle * pi / 360.
kappa = abs(4. / 3. * (1. - cos(halfAng)) / sin(halfAng))
if fragAngle < 0:
sign = -1
else:
sign = 1
pointList = []
for i in range(Nfrag):
theta0 = (startAng + i*fragAngle) * pi / 180.
theta1 = (startAng + (i+1)*fragAngle) *pi / 180.
if fragAngle > 0:
pointList.append((x_cen + rx * cos(theta0),
y_cen - ry * sin(theta0),
x_cen + rx * (cos(theta0) - kappa * sin(theta0)),
y_cen - ry * (sin(theta0) + kappa * cos(theta0)),
x_cen + rx * (cos(theta1) + kappa * sin(theta1)),
y_cen - ry * (sin(theta1) - kappa * cos(theta1)),
x_cen + rx * cos(theta1),
y_cen - ry * sin(theta1)))
else:
pointList.append((x_cen + rx * cos(theta0),
y_cen - ry * sin(theta0),
x_cen + rx * (cos(theta0) + kappa * sin(theta0)),
y_cen - ry * (sin(theta0) - kappa * cos(theta0)),
x_cen + rx * (cos(theta1) - kappa * sin(theta1)),
y_cen - ry * (sin(theta1) + kappa * cos(theta1)),
x_cen + rx * cos(theta1),
y_cen - ry * sin(theta1)))
return pointList

View File

@@ -0,0 +1,217 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/pdfimages.py
__version__=''' $Id$ '''
__doc__="""
Image functionality sliced out of canvas.py for generalization
"""
import os
import reportlab
from reportlab import rl_config
from reportlab.pdfbase import pdfutils
from reportlab.pdfbase import pdfdoc
from reportlab.lib.utils import import_zlib, haveImages, getBytesIO, isStr
from reportlab.lib.rl_accel import fp_str, asciiBase85Encode
from reportlab.lib.boxstuff import aspectRatioFix
class PDFImage:
"""Wrapper around different "image sources". You can make images
from a PIL Image object, a filename (in which case it uses PIL),
an image we previously cached (optimisation, hardly used these
days) or a JPEG (which PDF supports natively)."""
def __init__(self, image, x,y, width=None, height=None, caching=0):
self.image = image
self.x = x
self.y = y
self.width = width
self.height = height
self.filename = None
self.imageCaching = caching
# the following facts need to be determined,
# whatever the source. Declare what they are
# here for clarity.
self.colorSpace = 'DeviceRGB'
self.bitsPerComponent = 8
self.filters = []
self.source = None # JPEG or PIL, set later
self.getImageData()
def jpg_imagedata(self):
#directly process JPEG files
#open file, needs some error handling!!
fp = open(self.image, 'rb')
try:
result = self._jpg_imagedata(fp)
finally:
fp.close()
return result
def _jpg_imagedata(self,imageFile):
info = pdfutils.readJPEGInfo(imageFile)
self.source = 'JPEG'
imgwidth, imgheight = info[0], info[1]
if info[2] == 1:
colorSpace = 'DeviceGray'
elif info[2] == 3:
colorSpace = 'DeviceRGB'
else: #maybe should generate an error, is this right for CMYK?
colorSpace = 'DeviceCMYK'
imageFile.seek(0) #reset file pointer
imagedata = []
#imagedata.append('BI /Width %d /Height /BitsPerComponent 8 /ColorSpace /%s /Filter [/Filter [ /ASCII85Decode /DCTDecode] ID' % (info[0], info[1], colorSpace))
imagedata.append('BI /W %d /H %d /BPC 8 /CS /%s /F [%s/DCT] ID' % (imgwidth, imgheight, colorSpace, rl_config.useA85 and '/A85 ' or ''))
#write in blocks of (??) 60 characters per line to a list
data = imageFile.read()
if rl_config.useA85:
data = asciiBase85Encode(data)
pdfutils._chunker(data,imagedata)
imagedata.append('EI')
return (imagedata, imgwidth, imgheight)
def cache_imagedata(self):
image = self.image
if not pdfutils.cachedImageExists(image):
zlib = import_zlib()
if not zlib: return
if not haveImages: return
pdfutils.cacheImageFile(image)
#now we have one cached, slurp it in
cachedname = os.path.splitext(image)[0] + (rl_config.useA85 and '.a85' or '.bin')
imagedata = open(cachedname,'rb').readlines()
#trim off newlines...
imagedata = list(map(str.strip, imagedata))
return imagedata
def PIL_imagedata(self):
image = self.image
if image.format=='JPEG':
fp=image.fp
fp.seek(0)
return self._jpg_imagedata(fp)
self.source = 'PIL'
zlib = import_zlib()
if not zlib: return
bpc = 8
# Use the colorSpace in the image
if image.mode == 'CMYK':
myimage = image
colorSpace = 'DeviceCMYK'
bpp = 4
elif image.mode == '1':
myimage = image
colorSpace = 'DeviceGray'
bpp = 1
bpc = 1
elif image.mode == 'L':
myimage = image
colorSpace = 'DeviceGray'
bpp = 1
else:
myimage = image.convert('RGB')
colorSpace = 'RGB'
bpp = 3
imgwidth, imgheight = myimage.size
# this describes what is in the image itself
# *NB* according to the spec you can only use the short form in inline images
imagedata=['BI /W %d /H %d /BPC %d /CS /%s /F [%s/Fl] ID' % (imgwidth, imgheight, bpc, colorSpace, rl_config.useA85 and '/A85 ' or '')]
#use a flate filter and, optionally, Ascii Base 85 to compress
raw = (myimage.tobytes if hasattr(myimage,'tobytes') else myimage.tostring)()
rowstride = (imgwidth*bpc*bpp+7)>>3
assert len(raw) == rowstride*imgheight, "Wrong amount of data for image"
data = zlib.compress(raw) #this bit is very fast...
if rl_config.useA85:
data = asciiBase85Encode(data) #...sadly this may not be
#append in blocks of 60 characters
pdfutils._chunker(data,imagedata)
imagedata.append('EI')
return (imagedata, imgwidth, imgheight)
def non_jpg_imagedata(self,image):
if not self.imageCaching:
imagedata = pdfutils.cacheImageFile(image,returnInMemory=1)
else:
imagedata = self.cache_imagedata()
words = imagedata[1].split()
imgwidth = int(words[1])
imgheight = int(words[3])
return imagedata, imgwidth, imgheight
def getImageData(self,preserveAspectRatio=False):
"Gets data, height, width - whatever type of image"
image = self.image
if isStr(image):
self.filename = image
if os.path.splitext(image)[1] in ['.jpg', '.JPG', '.jpeg', '.JPEG']:
try:
imagedata, imgwidth, imgheight = self.jpg_imagedata()
except:
imagedata, imgwidth, imgheight = self.non_jpg_imagedata(image) #try for normal kind of image
else:
imagedata, imgwidth, imgheight = self.non_jpg_imagedata(image)
else:
import sys
if sys.platform[0:4] == 'java':
#jython, PIL not available
imagedata, imgwidth, imgheight = self.JAVA_imagedata()
else:
imagedata, imgwidth, imgheight = self.PIL_imagedata()
self.imageData = imagedata
self.imgwidth = imgwidth
self.imgheight = imgheight
self.width = self.width or imgwidth
self.height = self.height or imgheight
def drawInlineImage(self, canvas, preserveAspectRatio=False,anchor='sw'):
"""Draw an Image into the specified rectangle. If width and
height are omitted, they are calculated from the image size.
Also allow file names as well as images. This allows a
caching mechanism"""
width = self.width
height = self.height
if width<1e-6 or height<1e-6: return False
x,y,self.width,self.height, scaled = aspectRatioFix(preserveAspectRatio,anchor,self.x,self.y,width,height,self.imgwidth,self.imgheight)
# this says where and how big to draw it
if not canvas.bottomup: y = y+height
canvas._code.append('q %s 0 0 %s cm' % (fp_str(self.width), fp_str(self.height, x, y)))
# self._code.extend(imagedata) if >=python-1.5.2
for line in self.imageData:
canvas._code.append(line)
canvas._code.append('Q')
return True
def format(self, document):
"""Allow it to be used within pdfdoc framework. This only
defines how it is stored, not how it is drawn later."""
dict = pdfdoc.PDFDictionary()
dict['Type'] = '/XObject'
dict['Subtype'] = '/Image'
dict['Width'] = self.width
dict['Height'] = self.height
dict['BitsPerComponent'] = 8
dict['ColorSpace'] = pdfdoc.PDFName(self.colorSpace)
content = '\n'.join(self.imageData[3:-1]) + '\n'
strm = pdfdoc.PDFStream(dictionary=dict, content=content)
return strm.format(document)
if __name__=='__main__':
srcfile = os.path.join(
os.path.dirname(reportlab.__file__),
'test',
'pythonpowered.gif'
)
assert os.path.isfile(srcfile), 'image not found'
pdfdoc.LongFormat = 1
img = PDFImage(srcfile, 100, 100)
import pprint
doc = pdfdoc.PDFDocument()
print('source=',img.source)
print(img.format(doc))

View File

@@ -0,0 +1,465 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/textobject.py
__version__=''' $Id$ '''
__doc__="""
PDFTextObject is an efficient way to add text to a Canvas. Do not
instantiate directly, obtain one from the Canvas instead.
Progress Reports:
8.83, 2000-01-13, gmcm: created from pdfgen.py
"""
import string
from types import *
from reportlab.lib.colors import Color, CMYKColor, CMYKColorSep, toColor, black, white, _CMYK_black, _CMYK_white
from reportlab.lib.utils import isBytes, isStr, asUnicode
from reportlab.lib.rl_accel import fp_str
from reportlab.pdfbase import pdfmetrics
from reportlab.rl_config import rtlSupport
log2vis = None
if rtlSupport:
try:
from pyfribidi2 import log2vis, ON as DIR_ON, LTR as DIR_LTR, RTL as DIR_RTL
directionsMap = dict(LTR=DIR_LTR,RTL=DIR_RTL)
except:
import warnings
warnings.warn('pyfribidi is not installed - RTL not supported')
class _PDFColorSetter:
'''Abstracts the color setting operations; used in Canvas and Textobject
asseumes we have a _code object'''
def _checkSeparation(self,cmyk):
if isinstance(cmyk,CMYKColorSep):
name,sname = self._doc.addColor(cmyk)
if name not in self._colorsUsed:
self._colorsUsed[name] = sname
return name
#if this is set to a callable(color) --> color it can be used to check color setting
#see eg _enforceCMYK/_enforceRGB
_enforceColorSpace = None
def setFillColorCMYK(self, c, m, y, k, alpha=None):
"""set the fill color useing negative color values
(cyan, magenta, yellow and darkness value).
Takes 4 arguments between 0.0 and 1.0"""
self.setFillColor((c,m,y,k),alpha=alpha)
def setStrokeColorCMYK(self, c, m, y, k, alpha=None):
"""set the stroke color useing negative color values
(cyan, magenta, yellow and darkness value).
Takes 4 arguments between 0.0 and 1.0"""
self.setStrokeColor((c,m,y,k),alpha=alpha)
def setFillColorRGB(self, r, g, b, alpha=None):
"""Set the fill color using positive color description
(Red,Green,Blue). Takes 3 arguments between 0.0 and 1.0"""
self.setFillColor((r,g,b),alpha=alpha)
def setStrokeColorRGB(self, r, g, b, alpha=None):
"""Set the stroke color using positive color description
(Red,Green,Blue). Takes 3 arguments between 0.0 and 1.0"""
self.setStrokeColor((r,g,b),alpha=alpha)
def setFillColor(self, aColor, alpha=None):
"""Takes a color object, allowing colors to be referred to by name"""
if self._enforceColorSpace:
aColor = self._enforceColorSpace(aColor)
if isinstance(aColor, CMYKColor):
d = aColor.density
c,m,y,k = (d*aColor.cyan, d*aColor.magenta, d*aColor.yellow, d*aColor.black)
self._fillColorObj = aColor
name = self._checkSeparation(aColor)
if name:
self._code.append('/%s cs %s scn' % (name,fp_str(d)))
else:
self._code.append('%s k' % fp_str(c, m, y, k))
elif isinstance(aColor, Color):
rgb = (aColor.red, aColor.green, aColor.blue)
self._fillColorObj = aColor
self._code.append('%s rg' % fp_str(rgb) )
elif isinstance(aColor,(tuple,list)):
l = len(aColor)
if l==3:
self._fillColorObj = aColor
self._code.append('%s rg' % fp_str(aColor) )
elif l==4:
self._fillColorObj = aColor
self._code.append('%s k' % fp_str(aColor))
else:
raise ValueError('Unknown color %r' % aColor)
elif isStr(aColor):
self.setFillColor(toColor(aColor))
else:
raise ValueError('Unknown color %r' % aColor)
if alpha is not None:
self.setFillAlpha(alpha)
elif getattr(aColor, 'alpha', None) is not None:
self.setFillAlpha(aColor.alpha)
def setStrokeColor(self, aColor, alpha=None):
"""Takes a color object, allowing colors to be referred to by name"""
if self._enforceColorSpace:
aColor = self._enforceColorSpace(aColor)
if isinstance(aColor, CMYKColor):
d = aColor.density
c,m,y,k = (d*aColor.cyan, d*aColor.magenta, d*aColor.yellow, d*aColor.black)
self._strokeColorObj = aColor
name = self._checkSeparation(aColor)
if name:
self._code.append('/%s CS %s SCN' % (name,fp_str(d)))
else:
self._code.append('%s K' % fp_str(c, m, y, k))
elif isinstance(aColor, Color):
rgb = (aColor.red, aColor.green, aColor.blue)
self._strokeColorObj = aColor
self._code.append('%s RG' % fp_str(rgb) )
elif isinstance(aColor,(tuple,list)):
l = len(aColor)
if l==3:
self._strokeColorObj = aColor
self._code.append('%s RG' % fp_str(aColor) )
elif l==4:
self._strokeColorObj = aColor
self._code.append('%s K' % fp_str(aColor))
else:
raise ValueError('Unknown color %r' % aColor)
elif isStr(aColor):
self.setStrokeColor(toColor(aColor))
else:
raise ValueError('Unknown color %r' % aColor)
if alpha is not None:
self.setStrokeAlpha(alpha)
elif getattr(aColor, 'alpha', None) is not None:
self.setStrokeAlpha(aColor.alpha)
def setFillGray(self, gray, alpha=None):
"""Sets the gray level; 0.0=black, 1.0=white"""
self._fillColorObj = (gray, gray, gray)
self._code.append('%s g' % fp_str(gray))
if alpha is not None:
self.setFillAlpha(alpha)
def setStrokeGray(self, gray, alpha=None):
"""Sets the gray level; 0.0=black, 1.0=white"""
self._strokeColorObj = (gray, gray, gray)
self._code.append('%s G' % fp_str(gray))
if alpha is not None:
self.setFillAlpha(alpha)
def setStrokeAlpha(self,a):
if not (isinstance(a,(float,int)) and 0<=a<=1):
raise ValueError('setStrokeAlpha invalid value %r' % a)
getattr(self,'_setStrokeAlpha',lambda x: None)(a)
def setFillAlpha(self,a):
if not (isinstance(a,(float,int)) and 0<=a<=1):
raise ValueError('setFillAlpha invalid value %r' % a)
getattr(self,'_setFillAlpha',lambda x: None)(a)
def setStrokeOverprint(self,a):
getattr(self,'_setStrokeOverprint',lambda x: None)(a)
def setFillOverprint(self,a):
getattr(self,'_setFillOverprint',lambda x: None)(a)
def setOverprintMask(self,a):
getattr(self,'_setOverprintMask',lambda x: None)(a)
class PDFTextObject(_PDFColorSetter):
"""PDF logically separates text and graphics drawing; text
operations need to be bracketed between BT (Begin text) and
ET operators. This class ensures text operations are
properly encapusalted. Ask the canvas for a text object
with beginText(x, y). Do not construct one directly.
Do not use multiple text objects in parallel; PDF is
not multi-threaded!
It keeps track of x and y coordinates relative to its origin."""
def __init__(self, canvas, x=0,y=0, direction=None):
self._code = ['BT'] #no point in [] then append RGB
self._canvas = canvas #canvas sets this so it has access to size info
self._fontname = self._canvas._fontname
self._fontsize = self._canvas._fontsize
self._leading = self._canvas._leading
self._doc = self._canvas._doc
self._colorsUsed = self._canvas._colorsUsed
self._enforceColorSpace = getattr(canvas,'_enforceColorSpace',None)
font = pdfmetrics.getFont(self._fontname)
self._curSubset = -1
self.direction = direction
self.setTextOrigin(x, y)
self._textRenderMode = 0
self._clipping = 0
def getCode(self):
"pack onto one line; used internally"
self._code.append('ET')
if self._clipping:
self._code.append('%d Tr' % (self._textRenderMode^4))
return ' '.join(self._code)
def setTextOrigin(self, x, y):
if self._canvas.bottomup:
self._code.append('1 0 0 1 %s Tm' % fp_str(x, y)) #bottom up
else:
self._code.append('1 0 0 -1 %s Tm' % fp_str(x, y)) #top down
# The current cursor position is at the text origin
self._x0 = self._x = x
self._y0 = self._y = y
def setTextTransform(self, a, b, c, d, e, f):
"Like setTextOrigin, but does rotation, scaling etc."
if not self._canvas.bottomup:
c = -c #reverse bottom row of the 2D Transform
d = -d
self._code.append('%s Tm' % fp_str(a, b, c, d, e, f))
# The current cursor position is at the text origin Note that
# we aren't keeping track of all the transform on these
# coordinates: they are relative to the rotations/sheers
# defined in the matrix.
self._x0 = self._x = e
self._y0 = self._y = f
def moveCursor(self, dx, dy):
"""Starts a new line at an offset dx,dy from the start of the
current line. This does not move the cursor relative to the
current position, and it changes the current offset of every
future line drawn (i.e. if you next do a textLine() call, it
will move the cursor to a position one line lower than the
position specificied in this call. """
# Check if we have a previous move cursor call, and combine
# them if possible.
if self._code and self._code[-1][-3:]==' Td':
L = self._code[-1].split()
if len(L)==3:
del self._code[-1]
else:
self._code[-1] = ''.join(L[:-4])
# Work out the last movement
lastDx = float(L[-3])
lastDy = float(L[-2])
# Combine the two movement
dx += lastDx
dy -= lastDy
# We will soon add the movement to the line origin, so if
# we've already done this for lastDx, lastDy, remove it
# first (so it will be right when added back again).
self._x0 -= lastDx
self._y0 -= lastDy
# Output the move text cursor call.
self._code.append('%s Td' % fp_str(dx, -dy))
# Keep track of the new line offsets and the cursor position
self._x0 += dx
self._y0 += dy
self._x = self._x0
self._y = self._y0
def setXPos(self, dx):
"""Starts a new line dx away from the start of the
current line - NOT from the current point! So if
you call it in mid-sentence, watch out."""
self.moveCursor(dx,0)
def getCursor(self):
"""Returns current text position relative to the last origin."""
return (self._x, self._y)
def getStartOfLine(self):
"""Returns a tuple giving the text position of the start of the
current line."""
return (self._x0, self._y0)
def getX(self):
"""Returns current x position relative to the last origin."""
return self._x
def getY(self):
"""Returns current y position relative to the last origin."""
return self._y
def _setFont(self, psfontname, size):
"""Sets the font and fontSize
Raises a readable exception if an illegal font
is supplied. Font names are case-sensitive! Keeps track
of font anme and size for metrics."""
self._fontname = psfontname
self._fontsize = size
font = pdfmetrics.getFont(self._fontname)
if font._dynamicFont:
self._curSubset = -1
else:
pdffontname = self._canvas._doc.getInternalFontName(psfontname)
self._code.append('%s %s Tf' % (pdffontname, fp_str(size)))
def setFont(self, psfontname, size, leading = None):
"""Sets the font. If leading not specified, defaults to 1.2 x
font size. Raises a readable exception if an illegal font
is supplied. Font names are case-sensitive! Keeps track
of font anme and size for metrics."""
self._fontname = psfontname
self._fontsize = size
if leading is None:
leading = size * 1.2
self._leading = leading
font = pdfmetrics.getFont(self._fontname)
if font._dynamicFont:
self._curSubset = -1
else:
pdffontname = self._canvas._doc.getInternalFontName(psfontname)
self._code.append('%s %s Tf %s TL' % (pdffontname, fp_str(size), fp_str(leading)))
def setCharSpace(self, charSpace):
"""Adjusts inter-character spacing"""
self._charSpace = charSpace
self._code.append('%s Tc' % fp_str(charSpace))
def setWordSpace(self, wordSpace):
"""Adjust inter-word spacing. This can be used
to flush-justify text - you get the width of the
words, and add some space between them."""
self._wordSpace = wordSpace
self._code.append('%s Tw' % fp_str(wordSpace))
def setHorizScale(self, horizScale):
"Stretches text out horizontally"
self._horizScale = 100 + horizScale
self._code.append('%s Tz' % fp_str(horizScale))
def setLeading(self, leading):
"How far to move down at the end of a line."
self._leading = leading
self._code.append('%s TL' % fp_str(leading))
def setTextRenderMode(self, mode):
"""Set the text rendering mode.
0 = Fill text
1 = Stroke text
2 = Fill then stroke
3 = Invisible
4 = Fill text and add to clipping path
5 = Stroke text and add to clipping path
6 = Fill then stroke and add to clipping path
7 = Add to clipping path
after we start clipping we mustn't change the mode back until after the ET
"""
assert mode in (0,1,2,3,4,5,6,7), "mode must be in (0,1,2,3,4,5,6,7)"
if (mode & 4)!=self._clipping:
mode |= 4
self._clipping = mode & 4
if self._textRenderMode!=mode:
self._textRenderMode = mode
self._code.append('%d Tr' % mode)
def setRise(self, rise):
"Move text baseline up or down to allow superscript/subscripts"
self._rise = rise
self._y = self._y - rise # + ? _textLineMatrix?
self._code.append('%s Ts' % fp_str(rise))
def _formatText(self, text):
"Generates PDF text output operator(s)"
if log2vis and self.direction in ('LTR','RTL'):
# Use pyfribidi to write the text in the correct visual order.
text = log2vis(text, directionsMap.get(self.direction.upper(),DIR_ON))
canv = self._canvas
font = pdfmetrics.getFont(self._fontname)
R = []
if font._dynamicFont:
#it's a truetype font and should be utf8. If an error is raised,
for subset, t in font.splitString(text, canv._doc):
if subset!=self._curSubset:
pdffontname = font.getSubsetInternalName(subset, canv._doc)
R.append("%s %s Tf %s TL" % (pdffontname, fp_str(self._fontsize), fp_str(self._leading)))
self._curSubset = subset
R.append("(%s) Tj" % canv._escape(t))
elif font._multiByte:
#all the fonts should really work like this - let them know more about PDF...
R.append("%s %s Tf %s TL" % (
canv._doc.getInternalFontName(font.fontName),
fp_str(self._fontsize),
fp_str(self._leading)
))
R.append("(%s) Tj" % font.formatForPdf(text))
else:
#convert to T1 coding
fc = font
if isBytes(text):
try:
text = text.decode('utf8')
except UnicodeDecodeError as e:
i,j = e.args[2:4]
raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],text[max(i-10,0):i],text[i:j],text[j:j+10]),)))
for f, t in pdfmetrics.unicode2T1(text,[font]+font.substitutionFonts):
if f!=fc:
R.append("%s %s Tf %s TL" % (canv._doc.getInternalFontName(f.fontName), fp_str(self._fontsize), fp_str(self._leading)))
fc = f
R.append("(%s) Tj" % canv._escape(t))
if font!=fc:
R.append("%s %s Tf %s TL" % (canv._doc.getInternalFontName(self._fontname), fp_str(self._fontsize), fp_str(self._leading)))
return ' '.join(R)
def _textOut(self, text, TStar=0):
"prints string at current point, ignores text cursor"
self._code.append('%s%s' % (self._formatText(text), (TStar and ' T*' or '')))
def textOut(self, text):
"""prints string at current point, text cursor moves across."""
self._x = self._x + self._canvas.stringWidth(text, self._fontname, self._fontsize)
self._code.append(self._formatText(text))
def textLine(self, text=''):
"""prints string at current point, text cursor moves down.
Can work with no argument to simply move the cursor down."""
# Update the coordinates of the cursor
self._x = self._x0
if self._canvas.bottomup:
self._y = self._y - self._leading
else:
self._y = self._y + self._leading
# Update the location of the start of the line
# self._x0 is unchanged
self._y0 = self._y
# Output the text followed by a PDF newline command
self._code.append('%s T*' % self._formatText(text))
def textLines(self, stuff, trim=1):
"""prints multi-line or newlined strings, moving down. One
comon use is to quote a multi-line block in your Python code;
since this may be indented, by default it trims whitespace
off each line and from the beginning; set trim=0 to preserve
whitespace."""
if isStr(stuff):
lines = asUnicode(stuff).strip().split(u'\n')
if trim==1:
lines = [s.strip() for s in lines]
elif isinstance(stuff,(tuple,list)):
lines = stuff
else:
assert 1==0, "argument to textlines must be string,, list or tuple"
# Output each line one at a time. This used to be a long-hand
# copy of the textLine code, now called as a method.
for line in lines:
self.textLine(line)
def __nonzero__(self):
'PDFTextObject is true if it has something done after the init'
return self._code != ['BT']