mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-22 07:52:15 +00:00
Added printing requirements
This commit is contained in:
5
reportlab/pdfgen/__init__.py
Normal file
5
reportlab/pdfgen/__init__.py
Normal 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
1837
reportlab/pdfgen/canvas.py
Normal file
File diff suppressed because it is too large
Load Diff
127
reportlab/pdfgen/pathobject.py
Normal file
127
reportlab/pdfgen/pathobject.py
Normal 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')
|
||||
77
reportlab/pdfgen/pdfgeom.py
Normal file
77
reportlab/pdfgen/pdfgeom.py
Normal 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
|
||||
217
reportlab/pdfgen/pdfimages.py
Normal file
217
reportlab/pdfgen/pdfimages.py
Normal 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))
|
||||
465
reportlab/pdfgen/textobject.py
Normal file
465
reportlab/pdfgen/textobject.py
Normal 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']
|
||||
Reference in New Issue
Block a user