--- a/reportlab/demos/stdfonts/stdfonts.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/demos/stdfonts/stdfonts.py Wed Apr 05 15:18:32 2006 +0000
@@ -27,13 +27,12 @@
for enc in ['MacRoman', 'WinAnsi']:
canv = canvas.Canvas(
'StandardFonts_%s.pdf' % enc,
- encoding=enc
)
canv.setPageCompression(0)
for faceName in pdfmetrics.standardFonts:
if faceName in ['Symbol', 'ZapfDingbats']:
- encLabel = 'StandardEncoding'
+ encLabel = faceName+'Encoding'
else:
encLabel = enc + 'Encoding'
@@ -51,18 +50,14 @@
#for dingbats, we need to use another font for the numbers.
#do two parallel text objects.
- if faceName == 'ZapfDingbats':
- labelfont = 'Helvetica'
- else:
- labelfont = faceName
for byt in range(32, 256):
col, row = divmod(byt - 32, 32)
x = 72 + (66*col)
y = 720 - (18*row)
- canv.setFont(labelfont, 14)
+ canv.setFont('Helvetica', 14)
canv.drawString(x, y, label_formatter % byt)
canv.setFont(fontName, 14)
- canv.drawString(x + 44, y , chr(byt))
+ canv.drawString(x+44, y, chr(byt).decode(encLabel,'ignore').encode('utf8'))
canv.showPage()
canv.save()
--- a/reportlab/docs/userguide/ch1_intro.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/docs/userguide/ch1_intro.py Wed Apr 05 15:18:32 2006 +0000
@@ -239,7 +239,7 @@
list("""To verify,
start the Python interpreter (command line) and type $from PIL import Image$, followed by
-$import _imaging$ or (if you have a more modern copy of PIL) $import PIL._imaging$. If you see no error messages, all is well.""")
+$import _imaging$. If you see no error messages, all is well.""")
disc("""Now you are ready to install reportlab itself.""")
--- a/reportlab/docs/userguide/ch2a_fonts.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/docs/userguide/ch2a_fonts.py Wed Apr 05 15:18:32 2006 +0000
@@ -229,7 +229,7 @@
pdfmetrics.registerFont(pdfmetrics.Font('MacHelvWithEuro', 'Helvetica-Oblique', 'MacWithEuro'))
c.setFont('MacHelvWithEuro', 12)
-c.drawString(125, 575, 'Hacked MacRoman with Euro: Character 219 = "\333"') # oct(219)=0333
+c.drawString(125, 575, 'Hacked MacRoman with Euro: Character 219 = "\\xe2\\x82\\xac"') # utf8 for Euro
""")
heading2("Asian Font Support")
--- a/reportlab/docs/userguide/ch5_paragraphs.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/docs/userguide/ch5_paragraphs.py Wed Apr 05 15:18:32 2006 +0000
@@ -288,8 +288,8 @@
the paragraph, with its x origin determined by the $bulletIndent$
attribute of the style, and in the font given in the
$bulletFontName$ attribute. For genuine bullets, a good
-idea is to select the Symbol font in the style, and
-use a character such as $\\267)$:""")
+idea is to select the Times-Roman font in the style, and
+use a character such as $\\xe2\\x80\\xa2)$:""")
t=apply(Table,getAttrs(_bulletAttrMap))
t.setStyle([
@@ -306,7 +306,7 @@
overrides the implied bullet style and ^bulletText^ specified in the ^Paragraph^
creation.
""")
-parabox("""<bullet>\267</bullet>this is a bullet point. Spam
+parabox("""<bullet>\xe2\x80\xa2</bullet>this is a bullet point. Spam
spam spam spam spam spam spam spam spam spam spam spam
spam spam spam spam spam spam spam spam spam spam """,
styleSheet['Bullet'],
@@ -316,4 +316,4 @@
except that a sequence tag is used. It is also possible
to put a multi-character string in the bullet; with a deep
indent and bold bullet font, you can make a compact
-definition list.""")
\ No newline at end of file
+definition list.""")
--- a/reportlab/graphics/renderPDF.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/graphics/renderPDF.py Wed Apr 05 15:18:32 2006 +0000
@@ -148,10 +148,10 @@
def drawString(self, stringObj):
if self._fill:
S = self._tracker.getState()
- text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text
+ text_anchor, x, y, text, enc = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text, stringObj.encoding
if not text_anchor in ['start','inherited']:
font, font_size = S['fontName'], S['fontSize']
- textLen = stringWidth(text, font,font_size)
+ textLen = stringWidth(text, font, font_size, enc)
if text_anchor=='end':
x = x-textLen
elif text_anchor=='middle':
--- a/reportlab/graphics/renderPM.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/graphics/renderPM.py Wed Apr 05 15:18:32 2006 +0000
@@ -10,7 +10,7 @@
from reportlab.graphics.shapes import *
from reportlab.graphics.renderbase import StateTracker, getStateDelta, renderScaledDrawing
-from reportlab.pdfbase.pdfmetrics import getFont
+from reportlab.pdfbase.pdfmetrics import getFont, unicode2T1
from math import sin, cos, pi, ceil
from reportlab.lib.utils import getStringIO, open_and_read
from reportlab import rl_config
@@ -151,20 +151,51 @@
self.drawPolyLine(polygon,_doClose=1)
def drawString(self, stringObj):
- fill = self._canvas.fillColor
+ canv = self._canvas
+ fill = canv.fillColor
if fill is not None:
S = self._tracker.getState()
- text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text
+ text_anchor = S['textAnchor']
+ fontName = S['fontName']
+ fontSize = S['fontSize']
+ font = getFont(fontName)
+ text = stringObj.text
+ x = stringObj.x
+ y = stringObj.y
if not text_anchor in ['start','inherited']:
- font, font_size = S['fontName'], S['fontSize']
- textLen = stringWidth(text, font,font_size)
+ textLen = stringWidth(text, fontName,fontSize)
if text_anchor=='end':
x = x-textLen
elif text_anchor=='middle':
x = x - textLen/2
else:
raise ValueError, 'bad value for textAnchor '+str(text_anchor)
- self._canvas.drawString(x,y,text)
+ if getattr(font,'_dynamicFont',None):
+ if isinstance(text,unicode): text = text.encode('utf8')
+ canv.drawString(x,y,text)
+ else:
+ fc = font
+ if not isinstance(text,unicode):
+ try:
+ text = text.decode('utf8')
+ except UnicodeDecodeError,e:
+ i,j = e.args[2:4]
+ raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],text[i-10:i],text[i:j],text[j:j+10]),)))
+
+ FT = unicode2T1(text,[font]+font.substitutionFonts)
+ n = len(FT)
+ nm1 = n-1
+ wscale = 0.001*fontSize
+ for i in xrange(n):
+ f, t = FT[i]
+ if f!=fc:
+ canv.setFont(f.fontName,fontSize)
+ fc = f
+ canv.drawString(x,y,t)
+ if i!=nm1:
+ x += wscale*sum(map(f.widths.__getitem__,map(ord,t)))
+ if font!=fc:
+ canv.setFont(fontName,fontSize)
def drawPath(self, path):
c = self._canvas
--- a/reportlab/graphics/shapes.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/graphics/shapes.py Wed Apr 05 15:18:32 2006 +0000
@@ -1216,6 +1216,7 @@
fontSize = AttrMapValue(isNumber),
fillColor = AttrMapValue(isColorOrNone),
textAnchor = AttrMapValue(isTextAnchor),
+ encoding = AttrMapValue(isString),
)
def __init__(self, x, y, text, **kw):
@@ -1227,9 +1228,10 @@
self.fontSize = STATE_DEFAULTS['fontSize']
self.fillColor = STATE_DEFAULTS['fillColor']
self.setProperties(kw)
+ self.encoding = 'cp1252' #matches only fonts we have!
def getEast(self):
- return self.x + stringWidth(self.text,self.fontName,self.fontSize)
+ return self.x + stringWidth(self.text,self.fontName,self.fontSize, self.encoding)
def copy(self):
new = self.__class__(self.x, self.y, self.text)
@@ -1238,7 +1240,7 @@
def getBounds(self):
# assumes constant drop of 0.2*size to baseline
- w = stringWidth(self.text,self.fontName,self.fontSize)
+ w = stringWidth(self.text,self.fontName,self.fontSize, self.encoding)
if self.textAnchor == 'start':
x = self.x
elif self.textAnchor == 'middle':
--- a/reportlab/graphics/testshapes.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/graphics/testshapes.py Wed Apr 05 15:18:32 2006 +0000
@@ -74,6 +74,7 @@
D = Drawing(400, 200)
D.add(Rect(50, 50, 300, 100, fillColor=colors.yellow))
D.add(String(180,100, 'Hello World', fillColor=colors.red))
+ D.add(String(180,86, 'Some special characters \xc2\xa2\xc2\xa9\xc2\xae\xc2\xa3\xca\xa5\xd0\x96\xd6\x83\xd7\x90\xd9\x82\xe0\xa6\x95\xce\xb1\xce\xb2\xce\xb3', fillColor=colors.red))
return D
@@ -399,9 +400,10 @@
pdfmetrics.registerFont(ttfonts.TTFont("Rina", "rina.ttf"))
_FONTS[1] = 'LuxiSerif'
_FONTS[2] = 'Rina'
- F = ['Times-Roman','LuxiSerif', 'Rina']
+ F = ['Times-Roman','Courier','Helvetica','LuxiSerif', 'Rina']
if sys.platform=='win32':
for name, ttf in [('Adventurer Light SF','Advlit.ttf'),('ArialMS','ARIAL.TTF'),
+ ('Arial Unicode MS', 'ARIALUNI.TTF'),
('Book Antiqua','BKANT.TTF'),
('Century Gothic','GOTHIC.TTF'),
('Comic Sans MS', 'COMIC.TTF'),
@@ -432,7 +434,7 @@
maxx = 0
for fontName in F:
y -= th
- text = fontName+": I should be totally horizontal and enclosed in a box"
+ text = fontName+": I should be totally horizontal and enclosed in a box and end in alphabetagamma \xc2\xa2\xc2\xa9\xc2\xae\xc2\xa3\xca\xa5\xd0\x96\xd6\x83\xd7\x90\xd9\x82\xe0\xa6\x95\xce\xb1\xce\xb2\xce\xb3"
textWidth = stringWidth(text, fontName, fontSize)
maxx = max(maxx,textWidth+20)
D.add(
@@ -468,7 +470,6 @@
##
##
## return D
-
def getAllFunctionDrawingNames(doTTF=1):
"Get a list of drawing function names from somewhere."
@@ -485,10 +486,10 @@
return funcNames
def _evalFuncDrawing(name, D, l=None, g=None):
- #try:
- d = eval(name + '()', g or globals(), l or locals())
- #except:
- # d = getFailedDrawing(name)
+ try:
+ d = eval(name + '()', g or globals(), l or locals())
+ except:
+ d = getFailedDrawing(name)
D.append((d, eval(name + '.__doc__'), name[3:]))
def getAllTestDrawings(doTTF=1):
--- a/reportlab/lib/rparsexml.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/lib/rparsexml.py Wed Apr 05 15:18:32 2006 +0000
@@ -48,31 +48,22 @@
try:
#raise ImportError, "dummy error"
simpleparse = 0
- import pyRXP
- if pyRXP.version>='0.5':
- def warnCB(s):
- print s
- pyRXP_parser = pyRXP.Parser(
- ErrorOnValidityErrors=1,
- NoNoDTDWarning=1,
- ExpandCharacterEntities=0,
- ExpandGeneralEntities=0,
- warnCB = warnCB,
- srcName='string input')
- def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None):
- pyRXP_parser.eoCB = eoCB
- p = pyRXP_parser.parse(xmlText)
- return oneOutermostTag and p or ('',None,[p],None)
- else:
- def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None):
- '''eoCB is the entity open callback'''
- def warnCB(s):
- print s
- flags = 0x0157e1ff | pyRXP.PARSER_FLAGS['ErrorOnValidityErrors']
- for k in ('ExpandCharacterEntities','ExpandGeneralEntities'):
- flags = flags & (~pyRXP.PARSER_FLAGS[k])
- p = pyRXP.parse(xmlText,srcName='string input',flags=flags,warnCB=warnCB,eoCB=eoCB)
- return oneOutermostTag and p or ('',None,[p],None)
+ import pyRXPU
+ def warnCB(s):
+ print s
+ pyRXP_parser = pyRXPU.Parser(
+ ErrorOnValidityErrors=1,
+ NoNoDTDWarning=1,
+ ExpandCharacterEntities=1,
+ ExpandGeneralEntities=1,
+ warnCB = warnCB,
+ srcName='string input',
+ ReturnUTF8 = 1,
+ )
+ def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None):
+ pyRXP_parser.eoCB = eoCB
+ p = pyRXP_parser.parse(xmlText)
+ return oneOutermostTag and p or ('',None,[p],None)
except ImportError:
simpleparse = 1
--- a/reportlab/lib/styles.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/lib/styles.py Wed Apr 05 15:18:32 2006 +0000
@@ -79,7 +79,8 @@
'bulletFontSize':10,
'bulletIndent':0,
'textColor': black,
- 'backColor':None
+ 'backColor':None,
+ 'wordWrap':None,
}
class LineStyle(PropertySet):
--- a/reportlab/lib/validators.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/lib/validators.py Wed Apr 05 15:18:32 2006 +0000
@@ -7,7 +7,7 @@
used in an attribute map.
"""
-import string, sys,re
+import string, sys
from types import *
_SequenceTypes = (ListType,TupleType)
_NumberTypes = (FloatType,IntType)
@@ -64,7 +64,7 @@
class _isString(Validator):
def test(self,x):
- return type(x) is StringType
+ return type(x) in (StringType, UnicodeType)
class _isNumber(Validator):
def test(self,x):
@@ -156,7 +156,7 @@
class _isValidChildOrNone(_isValidChild):
def test(self,x):
- return x is None or _isValidChild.test(self,x)
+ return _isValidChild.test(self,x) or x is None
class _isCallable(Validator):
def test(self, x):
@@ -252,7 +252,6 @@
text = str(x)
return (self._pattern.match(text) <> None)
-
class DerivedValue:
"""This is used for magic values which work themselves out.
An example would be an "inherit" property, so that one can have
@@ -271,7 +270,6 @@
a correct stack of parent nodes."""
return None
-
class Inherit(DerivedValue):
def __repr__(self):
return "inherit"
--- a/reportlab/pdfbase/_cidfontdata.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfbase/_cidfontdata.py Wed Apr 05 15:18:32 2006 +0000
@@ -126,6 +126,28 @@
encodings_kor
)
+defaultUnicodeEncodings = {
+ #we ddefine a default Unicode encoding for each face name;
+ #this should be the most commonly used horizontal unicode encoding;
+ #also define a 3-letter language code.
+ 'HeiseiMin-W3': ('jpn','UniJIS-UCS2-H'),
+ 'HeiseiKakuGo-W5': ('jpn','UniJIS-UCS2-H'),
+ 'STSong-Light': ('chs', 'UniGB-UCS2-H'),
+ 'MSung-Light': ('cht', 'UniGB-UCS2-H'),
+ 'MHei-Medium': ('cht', 'UniGB-UCS2-H'),
+ 'HYSMyeongJo-Medium': ('kor', 'UniKS-UCS2-H'),
+ 'HYGothic-Medium': ('kor','UniKS-UCS2-H'),
+ }
+
+typeFaces_chs = ['STSong-Light'] # to do
+typeFaces_cht = ['MSung-Light', 'MHei-Medium'] # to do
+typeFaces_jpn = ['HeiseiMin-W3', 'HeiseiKakuGo-W5']
+typeFaces_kor = ['HYSMyeongJo-Medium','HYGothic-Medium']
+
+
+#declare separately those used for unicode
+unicode_encodings = [enc for enc in allowedEncodings if 'UCS2' in enc]
+
CIDFontInfo = {}
#statically describe the fonts in Adobe's Japanese Language Packs
@@ -449,4 +471,4 @@
## out.append(word)
## else:
## out.append(word + ',')
-## return eval(string.join(out, ''))
\ No newline at end of file
+## return eval(string.join(out, ''))
--- a/reportlab/pdfbase/cidfonts.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfbase/cidfonts.py Wed Apr 05 15:18:32 2006 +0000
@@ -17,9 +17,10 @@
import reportlab
from reportlab.pdfbase import pdfmetrics
-from reportlab.pdfbase._cidfontdata import allowedTypeFaces, allowedEncodings, CIDFontInfo
+from reportlab.pdfbase._cidfontdata import allowedTypeFaces, allowedEncodings, CIDFontInfo, defaultUnicodeEncodings
from reportlab.pdfgen.canvas import Canvas
from reportlab.pdfbase import pdfdoc
+from reportlab.pdfbase.pdfutils import _escape
from reportlab.rl_config import CMapSearchPath
@@ -28,6 +29,7 @@
for dirname in CMapSearchPath:
cmapfile = dirname + os.sep + name
if os.path.isfile(cmapfile):
+ #print "found", cmapfile
return cmapfile
raise IOError, 'CMAP file for encodings "%s" not found!' % name
@@ -204,6 +206,16 @@
finished = time.clock()
#print 'loaded %s in %0.4f seconds' % (self.name, finished - started)
+ def getData(self):
+ """Simple persistence helper. Return a dict with all that matters."""
+ return {
+ 'mapFileHash': self._mapFileHash,
+ 'codeSpaceRanges': self._codeSpaceRanges,
+ 'notDefRanges': self._notDefRanges,
+ 'cmap': self._cmap,
+
+ }
+
class CIDTypeFace(pdfmetrics.TypeFace):
"""Multi-byte type face.
@@ -292,7 +304,16 @@
self.isVertical = (self.encodingName[-1] == 'V')
- def stringWidth(self, text, size):
+ #no substitutes initially
+ self.substitutionFonts = []
+
+ def formatForPdf(self, text):
+ encoded = _escape(text)
+ #print 'encoded CIDFont:', encoded
+ return encoded
+
+ def stringWidth(self, text, size, encoding=None):
+ """This presumes non-Unicode input. UnicodeCIDFont wraps it for that context"""
cidlist = self.encoding.translate(text)
if self.isVertical:
#this part is "not checked!" but seems to work.
@@ -325,6 +346,88 @@
doc.fontMapping[self.name] = '/' + internalName
+class UnicodeCIDFont(CIDFont):
+ """Wraps up CIDFont to hide explicit encoding choice;
+ encodes text for output as UTF16.
+
+ lang should be one of 'jpn',chs','cht','kor' for now.
+ if vertical is set, it will select a different widths array
+ and possibly glyphs for some punctuation marks.
+
+ halfWidth is only for Japanese.
+
+
+ >>> dodgy = UnicodeCIDFont('nonexistent')
+ Traceback (most recent call last):
+ ...
+ KeyError: "don't know anything about CID font nonexistent"
+ >>> heisei = UnicodeCIDFont('HeiseiMin-W3')
+ >>> heisei.name
+ 'HeiseiMin-W3'
+ >>> heisei.language
+ 'jpn'
+ >>> heisei.encoding.name
+ 'UniJIS-UCS2-H'
+ >>> #This is how PDF data gets encoded.
+ >>> print heisei.formatForPdf('hello')
+ \\377\\376h\\000e\\000l\\000l\\000o\\000
+ >>> tokyo = u'\u6771\u4AEC'
+ >>> print heisei.formatForPdf(tokyo)
+ \\377\\376qg\\354J
+
+ """
+
+ def __init__(self, face, isVertical=False, isHalfWidth=False):
+ #pass
+ try:
+ lang, defaultEncoding = defaultUnicodeEncodings[face]
+ except KeyError:
+ raise KeyError("don't know anything about CID font %s" % face)
+
+ #we know the languages now.
+ self.language = lang
+
+ #rebuilt encoding string. They follow rules which work
+ #for the 7 fonts provided.
+ enc = defaultEncoding[:-1]
+ if isHalfWidth:
+ enc = enc + 'HW-'
+ if isVertical:
+ enc = enc + 'V'
+ else:
+ enc = enc + 'H'
+
+ #now we can do the more general case
+ CIDFont.__init__(self, face, enc)
+ #self.encName = 'utf_16_le'
+ #it's simpler for unicode, just use the face name
+ self.name = self.fontName = face
+ self.vertical = isVertical
+ self.isHalfWidth = isHalfWidth
+
+
+ def formatForPdf(self, text):
+ #these ones should be encoded asUTF16 minus the BOM
+ from codecs import utf_16_be_encode
+ #print 'formatting %s: %s' % (type(text), repr(text))
+ if type(text) is not unicode:
+ text = text.decode('utf8')
+ utfText = utf_16_be_encode(text)[0]
+ encoded = _escape(utfText)
+ #print ' encoded:',encoded
+ return encoded
+ #
+ #result = _escape(encoded)
+ #print ' -> %s' % repr(result)
+ #return result
+
+
+ def stringWidth(self, text, size, encoding=None):
+ "Just ensure we do width test on characters, not bytes..."
+ if type(text) is type(''):
+ text = text.decode('utf8')
+ return CIDFont.stringWidth(self, text, size, encoding)
+
def precalculate(cmapdir):
# crunches through all, making 'fastmap' files
@@ -392,7 +495,9 @@
## print 'constructed all encodings in %0.2f seconds' % (finished - started)
if __name__=='__main__':
- test()
+ import doctest, cidfonts
+ doctest.testmod(cidfonts)
+ #test()
--- a/reportlab/pdfbase/pdfdoc.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfbase/pdfdoc.py Wed Apr 05 15:18:32 2006 +0000
@@ -15,7 +15,7 @@
classes are made available elsewhere for users to manipulate.
"""
-import string, types, binascii
+import string, types, binascii, codecs
from reportlab.pdfbase import pdfutils
from reportlab.pdfbase.pdfutils import LINEEND # this constant needed in both
from reportlab import rl_config
@@ -136,19 +136,10 @@
encrypt = NoEncryption() # default no encryption
pageCounter = 1
def __init__(self,
- encoding=rl_config.defaultEncoding,
dummyoutline=0,
compression=rl_config.pageCompression,
invariant=rl_config.invariant,
filename=None):
- #self.defaultStreamFilters = [PDFBase85Encode, PDFZCompress] # for testing!
- #self.defaultStreamFilters = [PDFZCompress] # for testing!
- assert encoding in ['MacRomanEncoding',
- 'WinAnsiEncoding',
- 'MacRoman',
- 'WinAnsi'], 'Unsupported encoding %s' % encoding
- if encoding[-8:] <> 'Encoding':
- encoding = encoding + 'Encoding'
# allow None value to be passed in to mean 'give system defaults'
if invariant is None:
@@ -156,7 +147,6 @@
else:
self.invariant = invariant
self.setCompression(compression)
- self.encoding = encoding
# signature for creating PDF ID
import md5
sig = self.signature = md5.new()
@@ -334,8 +324,6 @@
fontnames.sort()
return fontnames
-
-
def format(self):
# register the Catalog/INfo and then format the objects one by one until exhausted
# (possible infinite loop if there is a bug that continually makes new objects/refs...)
@@ -361,7 +349,7 @@
done = None
File = PDFFile() # output collector
while done is None:
- counter = counter+1 # do next object...
+ counter += 1 # do next object...
if numbertoid.has_key(counter):
id = numbertoid[counter]
#printidToOb
@@ -1082,7 +1070,7 @@
self.buildtree = []
self.closedict = {} # dictionary of "closed" destinations in the outline
- def addOutlineEntry(self, destinationname, level=0, title=None, closed=None):
+ def addOutlineEntry(self, destinationname, level=0, title=None, closed=None, asUtf16=False):
"""destinationname of None means "close the tree" """
from types import IntType, TupleType
if destinationname is None and level!=0:
@@ -1116,6 +1104,12 @@
currentlevel = currentlevel-1
if destinationname is None: return
stack[-1].append(destinationname)
+ if asUtf16:
+ if type(title) is str:
+ title = title.decode('utf8')
+ elif type(title) is not unicode:
+ raise ValueError('title must be utf8 string or unicode')
+ title = codecs.BOM_UTF16_BE+title.encode('utf_16_be')
self.destinationnamestotitles[destinationname] = title
if closed: self.closedict[destinationname] = 1
self.currentlevel = level
--- a/reportlab/pdfbase/pdfmetrics.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfbase/pdfmetrics.py Wed Apr 05 15:18:32 2006 +0000
@@ -23,28 +23,58 @@
from types import StringType, ListType, TupleType
from reportlab.pdfbase import _fontdata
from reportlab.lib.logger import warnOnce
-from reportlab.lib.utils import rl_isfile, rl_isdir, open_and_read, open_and_readlines, rl_glob
+from reportlab.lib.utils import rl_isfile, rl_glob, rl_isdir, open_and_read, open_and_readlines
from reportlab.rl_config import defaultEncoding
+import rl_codecs
+rl_codecs.RL_Codecs.register()
standardFonts = _fontdata.standardFonts
standardEncodings = _fontdata.standardEncodings
-_dummyEncoding=' _not an encoding_ '
-# conditional import - try both import techniques, and set a flag
+# AR 20040612 - disabling accelerated stringwidth until I have
+# a slow one which works right for Unicode. Then we can change
+# the accelerated one.
+##_dummyEncoding=' _not an encoding_ '
+## conditional import - try both import techniques, and set a flag
try:
- import _rl_accel
- try:
- _stringWidth = _rl_accel.stringWidth
- _rl_accel.defaultEncoding(_dummyEncoding)
- except:
- _stringWidth = None
+ import _rl_accel
+ try:
+ _stringWidth = _rl_accel.stringWidth
+ #_rl_accel.defaultEncoding(_dummyEncoding)
+ except:
+ _stringWidth = None
except ImportError:
- _stringWidth = None
+ _stringWidth = None
+_stringWidth = None
_typefaces = {}
_encodings = {}
_fonts = {}
+def unicode2T1(utext,fonts):
+ '''return a list of (font,string) pairs representing the unicode text'''
+ #print 'unicode2t1(%s, %s): %s' % (utext, fonts, type(utext))
+ #if type(utext)
+ R = []
+ font, fonts = fonts[0], fonts[1:]
+ enc = font.encName
+ if 'UCS-2' in enc:
+ enc = 'UTF16'
+ while utext:
+ try:
+ R.append((font,utext.encode(enc)))
+ break
+ except UnicodeEncodeError, e:
+ i0, il = e.args[2:4]
+ if i0:
+ R.append((font,utext[:i0].encode(enc)))
+ if fonts:
+ R.extend(unicode2T1(utext[i0:il],fonts))
+ else:
+ R.append((_notdefFont,_notdefChar*(il-i0)))
+ utext = utext[il:]
+ return R
+
class FontError(Exception):
pass
class FontNotFoundError(Exception):
@@ -326,6 +356,7 @@
#for encName in standardEncodings:
# registerEncoding(Encoding(encName))
+standardT1SubstitutionFonts = []
class Font:
"""Represents a font (i.e combination of face and encoding).
@@ -336,13 +367,23 @@
composition)"""
def __init__(self, name, faceName, encName):
self.fontName = name
- self.face = getTypeFace(faceName)
+ face = self.face = getTypeFace(faceName)
self.encoding= getEncoding(encName)
+ self.encName = encName
+ if face.builtIn and face.requiredEncoding is None:
+ _ = standardT1SubstitutionFonts
+ else:
+ _ = []
+ self.substitutionFonts = _
self._calcWidths()
# multi byte fonts do their own stringwidth calculations.
# signal this here.
self._multiByte = 0
+
+
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__.__name__, self.face.name)
def _calcWidths(self):
"""Vector of widths for stringWidth function"""
@@ -365,15 +406,12 @@
self.widths = w
if not _stringWidth:
- def stringWidth(self, text, size):
- """This is the "purist" approach to width. The practical one
- is to use the stringWidth one which may be optimized
- in C."""
- w = 0
- widths = self.widths
- for ch in text:
- w = w + widths[ord(ch)]
- return w * 0.001 * size
+ def stringWidth(self, text, size, encoding='utf8'):
+ """This is the "purist" approach to width. The practical approach
+ is to use the stringWidth function, which may be swapped in for one
+ written in C."""
+ if not isinstance(text,unicode): text = text.decode(encoding)
+ return sum([sum(map(f.widths.__getitem__,map(ord,t))) for f, t in unicode2T1(text,[self]+self.substitutionFonts)])*0.001*size
def _formatWidths(self):
"returns a pretty block in PDF Array format to aid inspection"
@@ -653,6 +691,8 @@
font = Font(fontName, fontName, defaultEncoding)
registerFont(font)
return font
+_notdefFont,_notdefChar = getFont('ZapfDingbats'),chr(110)
+standardT1SubstitutionFonts.extend([getFont('Symbol'),getFont('ZapfDingbats')])
def getAscentDescent(fontName):
font = getFont(fontName)
@@ -673,17 +713,9 @@
reg.sort()
return reg
-def _slowStringWidth(text, fontName, fontSize):
+def _slowStringWidth(text, fontName, fontSize, encoding='utf8'):
"""Define this anyway so it can be tested, but whether it is used or not depends on _rl_accel"""
- font = getFont(fontName)
- return font.stringWidth(text, fontSize)
- #this is faster, but will need more special-casing for multi-byte fonts.
- #wid = getFont(fontName).widths
- #w = 0
- #for ch in text:
- # w = w + wid[ord(ch)]
- #return 0.001 * w * fontSize
-
+ return getFont(fontName).stringWidth(text, fontSize, encoding=encoding)
if _stringWidth:
import new
@@ -744,11 +776,11 @@
# checks all 3 algorithms give same answer, note speed
import time
for fontName in standardFonts[0:1]:
- t0 = time.time()
- for text in texts:
- l1 = _stringWidth(text, fontName, 10)
- t1 = time.time()
- print 'fast stringWidth took %0.4f' % (t1 - t0)
+## t0 = time.time()
+## for text in texts:
+## l1 = stringWidth(text, fontName, 10)
+## t1 = time.time()
+## print 'fast stringWidth took %0.4f' % (t1 - t0)
t0 = time.time()
w = getFont(fontName).widths
@@ -788,7 +820,6 @@
dumpFontData()
-
if __name__=='__main__':
test()
testStringWidthAlgorithms()
--- a/reportlab/pdfbase/ttfonts.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfbase/ttfonts.py Wed Apr 05 15:18:32 2006 +0000
@@ -60,7 +60,7 @@
__version__ = '$Id$'
import string
-from types import StringType
+from types import StringType, UnicodeType
from struct import pack, unpack
from cStringIO import StringIO
from reportlab.pdfbase import pdfmetrics, pdfdoc
@@ -105,7 +105,7 @@
"endcodespacerange",
"%d beginbfchar" % len(subset)
] + map(lambda n, subset=subset: "<%02X> <%04X>" % (n, subset[n]),
- range(len(subset))) + [
+ xrange(len(subset))) + [
"endbfchar",
"endcmap",
"CMapName currentdict /CMap defineresource pop",
@@ -237,7 +237,7 @@
# Read table directory
self.table = {}
self.tables = []
- for n in range(self.numTables):
+ for n in xrange(self.numTables):
record = {}
record['tag'] = self.read_tag()
record['checksum'] = self.read_ulong()
@@ -428,7 +428,7 @@
names = {1:None,2:None,3:None,4:None,6:None}
K = names.keys()
nameCount = len(names)
- for i in range(numRecords):
+ for i in xrange(numRecords):
platformId = self.read_ushort()
encodingId = self.read_ushort()
languageId = self.read_ushort()
@@ -591,7 +591,7 @@
self.skip(2)
cmapTableCount = self.read_ushort()
unicode_cmap_offset = None
- for n in range(cmapTableCount):
+ for n in xrange(cmapTableCount):
platformID = self.read_ushort()
encodingID = self.read_ushort()
offset = self.read_ulong()
@@ -613,18 +613,18 @@
self.skip(2)
segCount = self.read_ushort() / 2
self.skip(6)
- endCount = map(lambda x, self=self: self.read_ushort(), range(segCount))
+ endCount = map(lambda x, self=self: self.read_ushort(), xrange(segCount))
self.skip(2)
- startCount = map(lambda x, self=self: self.read_ushort(), range(segCount))
- idDelta = map(lambda x, self=self: self.read_short(), range(segCount))
+ startCount = map(lambda x, self=self: self.read_ushort(), xrange(segCount))
+ idDelta = map(lambda x, self=self: self.read_short(), xrange(segCount))
idRangeOffset_start = self._pos
- idRangeOffset = map(lambda x, self=self: self.read_ushort(), range(segCount))
+ idRangeOffset = map(lambda x, self=self: self.read_ushort(), xrange(segCount))
# Now it gets tricky.
glyphToChar = {}
charToGlyph = {}
- for n in range(segCount):
- for unichar in range(startCount[n], endCount[n] + 1):
+ for n in xrange(segCount):
+ for unichar in xrange(startCount[n], endCount[n] + 1):
if idRangeOffset[n] == 0:
glyph = (unichar + idDelta[n]) & 0xFFFF
else:
@@ -650,7 +650,7 @@
aw = None
self.charWidths = {}
self.hmetrics = []
- for glyph in range(numberOfHMetrics):
+ for glyph in xrange(numberOfHMetrics):
# advance width and left side bearing. lsb is actually signed
# short, but we don't need it anyway (except for subsetting)
aw, lsb = self.read_ushort(), self.read_ushort()
@@ -661,7 +661,7 @@
if glyphToChar.has_key(glyph):
for char in glyphToChar[glyph]:
self.charWidths[char] = aw
- for glyph in range(numberOfHMetrics, numGlyphs):
+ for glyph in xrange(numberOfHMetrics, numGlyphs):
# the rest of the table only lists advance left side bearings.
# so we reuse aw set by the last iteration of the previous loop
lsb = self.read_ushort()
@@ -674,10 +674,10 @@
self.seek_table('loca')
self.glyphPos = []
if indexToLocFormat == 0:
- for n in range(numGlyphs + 1):
+ for n in xrange(numGlyphs + 1):
self.glyphPos.append(self.read_ushort() << 1)
elif indexToLocFormat == 1:
- for n in range(numGlyphs + 1):
+ for n in xrange(numGlyphs + 1):
self.glyphPos.append(self.read_ulong())
else:
raise TTFError, 'Unknown location table format (%d)' % indexToLocFormat
@@ -781,7 +781,7 @@
# hmtx - Horizontal Metrics
hmtx = []
- for n in range(numGlyphs):
+ for n in xrange(numGlyphs):
originalGlyphIdx = glyphMap[n]
aw, lsb = self.hmetrics[originalGlyphIdx]
if n < numberOfHMetrics:
@@ -795,7 +795,7 @@
offsets = []
glyf = []
pos = 0
- for n in range(numGlyphs):
+ for n in xrange(numGlyphs):
offsets.append(pos)
originalGlyphIdx = glyphMap[n]
glyphPos = self.glyphPos[originalGlyphIdx]
@@ -967,15 +967,14 @@
self._dynamicFont = 1 # We want dynamic subsetting
self.state = {}
- def stringWidth(self, text, size):
+ def stringWidth(self, text, size, encoding='utf-8'):
"Calculate text width"
+ if type(text) is not UnicodeType:
+ text = unicode(text, encoding or 'utf-8') # encoding defaults to utf-8
width = self.face.getCharWidth
- w = 0
- for code in parse_utf8(text):
- w = w + width(code)
- return 0.001 * w * size
+ return 0.001*size*sum([width(ord(u)) for u in text])
- def splitString(self, text, doc):
+ def splitString(self, text, doc, encoding='utf-8'):
"""Splits text into a number of chunks, each of which belongs to a
single subset. Returns a list of tuples (subset, string). Use subset
numbers with getSubsetInternalName. Doc is needed for distinguishing
@@ -985,7 +984,9 @@
curSet = -1
cur = []
results = []
- for code in parse_utf8(text):
+ if type(text) is not UnicodeType:
+ text = unicode(text, encoding or 'utf-8') # encoding defaults to utf-8
+ for code in map(ord,text):
if state.assignments.has_key(code):
n = state.assignments[code]
else:
@@ -1037,7 +1038,7 @@
try: state = self.state[doc]
except KeyError: state = self.state[doc] = TTFont.State()
state.frozen = 1
- for n in range(len(state.subsets)):
+ for n in xrange(len(state.subsets)):
subset = state.subsets[n]
internalName = self.getSubsetInternalName(n, doc)[1:]
baseFontName = "%s+%s" % (SUBSETN(n),self.face.name)
--- a/reportlab/pdfgen/canvas.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfgen/canvas.py Wed Apr 05 15:18:32 2006 +0000
@@ -63,6 +63,17 @@
def _digester(s):
return join(map(lambda x : "%02x" % ord(x), md5.md5(s).digest()), '')
+def _annFormat(D,color,thickness,dashArray):
+ from reportlab.pdfbase.pdfdoc import PDFArray
+ if color:
+ D["C"] = PDFArray([color.red, color.green, color.blue])
+ border = [0,0,0]
+ if thickness:
+ border[2] = thickness
+ if dashArray:
+ border.append(PDFArray(dashArray))
+ D["Border"] = PDFArray(border)
+
class Canvas(textobject._PDFColorSetter):
"""This class is the programmer's interface to the PDF file format. Methods
are (or will be) provided here to do just about everything PDF can do.
@@ -114,7 +125,6 @@
pagesize=None,
bottomup = 1,
pageCompression=None,
- encoding = None,
invariant = None,
verbosity=0):
"""Create a canvas of a given size. etc.
@@ -125,12 +135,10 @@
Most of the attributes are private - we will use set/get methods
as the preferred interface. Default page size is A4."""
if pagesize is None: pagesize = rl_config.defaultPageSize
- if encoding is None: encoding = rl_config.defaultEncoding
if invariant is None: invariant = rl_config.invariant
self._filename = filename
- self._encodingName = encoding
- self._doc = pdfdoc.PDFDocument(encoding,
- compression=pageCompression,
+
+ self._doc = pdfdoc.PDFDocument(compression=pageCompression,
invariant=invariant, filename=filename)
@@ -176,6 +184,7 @@
self._y = 0
self._fontname = 'Times-Roman'
self._fontsize = 12
+
self._dynamicFont = 0
self._textMode = 0 #track if between BT/ET
self._leading = 14.4
@@ -244,7 +253,7 @@
not automatically be seen when the document is viewed."""
self._doc.setAuthor(author)
- def addOutlineEntry(self, title, key, level=0, closed=None):
+ def addOutlineEntry(self, title, key, level=0, closed=None, asUtf16=False):
"""Adds a new entry to the outline at given level. If LEVEL not specified,
entry goes at the top level. If level specified, it must be
no more than 1 greater than the outline level in the last call.
@@ -294,7 +303,7 @@
"""
#to be completed
#self._outlines.append(title)
- self._doc.outline.addOutlineEntry(key, level, title, closed=closed)
+ self._doc.outline.addOutlineEntry(key, level, title, closed=closed, asUtf16=asUtf16)
def setOutlineNames0(self, *nametree): # keep this for now (?)
"""nametree should can be a recursive tree like so
@@ -401,7 +410,7 @@
return result
def bookmarkPage(self, key,
- fitType="Fit",
+ fit="Fit",
left=None,
top=None,
bottom=None,
@@ -453,30 +462,30 @@
if zoom is None:
zoom = "null"
- if fitType == "XYZ":
+ if fit == "XYZ":
dest.xyz(left,top,zoom)
- elif fitType == "Fit":
+ elif fit == "Fit":
dest.fit()
- elif fitType == "FitH":
+ elif fit == "FitH":
dest.fith(top)
- elif fitType == "FitV":
+ elif fit == "FitV":
dest.fitv(left)
- elif fitType == "FitR":
+ elif fit == "FitR":
dest.fitr(left,bottom,right,top)
#Do we need these (version 1.1 / Acrobat 3 versions)?
- elif fitType == "FitB":
+ elif fit == "FitB":
dest.fitb()
- elif fitType == "FitBH":
+ elif fit == "FitBH":
dest.fitbh(top)
- elif fitType == "FitBV":
+ elif fit == "FitBV":
dest.fitbv(left)
else:
- raise "Unknown Fit type %s" % (fitType,)
+ raise "Unknown Fit type %s" % (fit,)
dest.setPage(pageref)
return dest
- def bookmarkHorizontalAbsolute(self, key, yhorizontal):
+ def bookmarkHorizontalAbsolute(self, key, top, left=0, fit='XYZ', **kw):
"""Bind a bookmark (destination) to the current page at a horizontal position.
Note that the yhorizontal of the book mark is with respect to the default
user space (where the origin is at the lower left corner of the page)
@@ -485,12 +494,12 @@
responsible for making sure the bookmark matches an appropriate item on
the page."""
#This method should probably be deprecated since it is just a sub-set of bookmarkPage
- return self.bookmarkPage(key,fitType="FitH",top=yhorizontal)
+ return self.bookmarkPage(key, fit=fit, top=top, left=left, zoom=0)
- def bookmarkHorizontal(self, key, relativeX, relativeY):
+ def bookmarkHorizontal(self, key, relativeX, relativeY, **kw):
"""w.r.t. the current transformation, bookmark this horizontal."""
- (xt, yt) = self.absolutePosition(relativeX,relativeY)
- self.bookmarkHorizontalAbsolute(key, yt)
+ (left, top) = self.absolutePosition(relativeX,relativeY)
+ self.bookmarkHorizontalAbsolute(key, top, left=left, **kw)
#def _inPage0(self): disallowed!
# """declare a page, enable page features"""
@@ -527,9 +536,11 @@
height are omitted, they are calculated from the image size.
Also allow file names as well as images. The size in pixels
of the image is returned."""
+
+ self._currentPageHasImages = 1
from pdfimages import PDFImage
img_obj = PDFImage(image, x,y, width, height)
- if img_obj.drawInlineImage(self): self._currentPageHasImages = 1
+ img_obj.drawInlineImage(self)
return (img_obj.width, img_obj.height)
def drawImage(self, image, x, y, width=None, height=None, mask=None):
@@ -557,6 +568,8 @@
In general you should use drawImage in preference to drawInlineImage
unless you have read the PDF Spec and understand the tradeoffs."""
+ self._currentPageHasImages = 1
+
# first, generate a unique name/signature for the image. If ANYTHING
# is different, even the mask, this should be different.
if type(image) == type(''):
@@ -583,9 +596,6 @@
width = imgObj.width
if height is None:
height = imgObj.height
- if width<1e-6 or height<1e-6: return
-
- self._currentPageHasImages = 1
# scale and draw
self.saveState()
@@ -737,7 +747,8 @@
self._addAnnotation(pdfdoc.InkAnnotation(Rect, contents, InkList, **kw), name, addtopage)
inkAnnotation0 = inkAnnotation #deprecated
- def linkAbsolute(self, contents, destinationname, Rect=None, addtopage=1, name=None, **kw):
+ def linkAbsolute(self, contents, destinationname, Rect=None, addtopage=1, name=None,
+ thickness=0, color=None, dashArray=None, **kw):
"""rectangular link annotation positioned wrt the default user space.
The identified rectangle on the page becomes a "hot link" which
when clicked will send the viewer to the page and position identified
@@ -752,18 +763,21 @@
You may want to use the keyword argument Border='[0 0 0]' to
suppress the visible rectangle around the during viewing link."""
+ return self.linkRect(contents, destinationname, Rect, addtopage, name, relative=0,
+ thickness=thickness, color=color, dashArray=dashArray, **kw)
+
+ def linkRect(self, contents, destinationname, Rect=None, addtopage=1, name=None, relative=0,
+ thickness=0, color=None, dashArray=None, **kw):
+ """rectangular link annotation w.r.t the current user transform.
+ if the transform is skewed/rotated the absolute rectangle will use the max/min x/y
+ """
destination = self._bookmarkReference(destinationname) # permitted to be undefined... must bind later...
- Rect = self._absRect(Rect)
+ Rect = self._absRect(Rect,relative)
kw["Rect"] = Rect
kw["Contents"] = contents
kw["Destination"] = destination
- self._addAnnotation(pdfdoc.LinkAnnotation(**kw), name, addtopage)
-
- def linkRect(self, contents, destinationname, Rect=None, addtopage=1, name=None, **kw):
- """rectangular link annotation w.r.t the current user transform.
- if the transform is skewed/rotated the absolute rectangle will use the max/min x/y
- """
- return self.linkAbsolute(contents, destinationname, Rect, addtopage, name, **kw)
+ _annFormat(kw,color,thickness,dashArray)
+ return self._addAnnotation(pdfdoc.LinkAnnotation(**kw), name, addtopage)
def linkURL(self, url, rect, relative=0, thickness=0, color=None, dashArray=None, kind="URI", **kw):
"""Create a rectangular URL 'hotspot' in the given rectangle.
@@ -798,17 +812,7 @@
raise ValueError("Unknown linkURI kind '%s'" % kind)
ann["A"] = A
-
- # now for formatting stuff.
- if color:
- ann["C"] = PDFArray([color.red, color.green, color.blue])
- border = [0,0,0]
- if thickness:
- border[2] = thickness
- if dashArray:
- border.append(PDFArray(dashArray))
- ann["Border"] = PDFArray(border)
-
+ _annFormat(ann,color,thickness,dashArray)
self._addAnnotation(ann)
def _addAnnotation(self, annotation, name=None, addtopage=1):
@@ -1257,6 +1261,7 @@
leading = size * 1.2
self._leading = leading
font = pdfmetrics.getFont(self._fontname)
+
self._dynamicFont = getattr(font, '_dynamicFont', 0)
if not self._dynamicFont:
pdffontname = self._doc.getInternalFontName(psfontname)
@@ -1268,12 +1273,8 @@
if leading is None: leading = self._leading
self.setFont(self._fontname, size, leading)
- def stringWidth(self, text, fontName, fontSize, encoding=None):
+ def stringWidth(self, text, fontName, fontSize):
"gets width of a string in the given font and size"
- if encoding is not None:
- from reportlab.lib import logger
- logger.warnOnce('encoding argument to Canvas.stringWidth is deprecated and has no effect!')
- #if encoding is None: encoding = self._doc.encoding
return pdfmetrics.stringWidth(text, fontName, fontSize)
# basic graphics modes
@@ -1301,10 +1302,10 @@
def setDash(self, array=[], phase=0):
"""Two notations. pass two numbers, or an array and phase"""
if type(array) == IntType or type(array) == FloatType:
- self._code.append('[%s] %s d' % (array, phase))
+ self._code.append('[%s %s] 0 d' % (array, phase))
elif type(array) == ListType or type(array) == TupleType:
assert phase >= 0, "phase is a length in user space"
- textarray = ' '.join(map(str, array))
+ textarray = ' '.join(map(str, array))
self._code.append('[%s] %s d' % (textarray, phase))
# path stuff - the separate path object builds it
@@ -1439,6 +1440,15 @@
transDict[key] = value
self._pageTransition = transDict
+ def getCurrentPageContent(self):
+ """Return uncompressed contents of current page buffer.
+
+ This is useful in creating test cases and assertions of what
+ got drawn, without necessarily saving pages to disk"""
+ return '\n'.join(self._code)
+
+
+
if _instanceEscapePDF:
import new
Canvas._escape = new.instancemethod(_instanceEscapePDF,None,Canvas)
--- a/reportlab/pdfgen/textobject.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfgen/textobject.py Wed Apr 05 15:18:32 2006 +0000
@@ -232,6 +232,7 @@
self._fontname = psfontname
self._fontsize = size
font = pdfmetrics.getFont(self._fontname)
+
self._dynamicFont = getattr(font, '_dynamicFont', 0)
if self._dynamicFont:
self._curSubset = -1
@@ -250,6 +251,7 @@
leading = size * 1.2
self._leading = leading
font = pdfmetrics.getFont(self._fontname)
+
self._dynamicFont = getattr(font, '_dynamicFont', 0)
if self._dynamicFont:
self._curSubset = -1
@@ -303,31 +305,44 @@
def _formatText(self, text):
"Generates PDF text output operator(s)"
+ canv = self._canvas
+ font = pdfmetrics.getFont(self._fontname)
+ R = []
if self._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))
- results = []
- font = pdfmetrics.getFont(self._fontname)
- try: #assume UTF8
- stuff = font.splitString(text, self._canvas._doc)
- except UnicodeDecodeError:
- #assume latin1 as fallback
- from reportlab.pdfbase.ttfonts import latin1_to_utf8
- from reportlab.lib.logger import warnOnce
- warnOnce('non-utf8 data fed to truetype font, assuming latin-1 data')
- text = latin1_to_utf8(text)
- stuff = font.splitString(text, self._canvas._doc)
- for subset, chunk in stuff:
- if subset != self._curSubset:
- pdffontname = font.getSubsetInternalName(subset, self._canvas._doc)
- results.append("%s %s Tf %s TL" % (pdffontname, fp_str(self._fontsize), fp_str(self._leading)))
- self._curSubset = subset
- chunk = self._canvas._escape(chunk)
- results.append("(%s) Tj" % chunk)
- return string.join(results, ' ')
else:
- text = self._canvas._escape(text)
- return "(%s) Tj" % text
+ #convert to T1 coding
+ fc = font
+ if not isinstance(text,unicode):
+ try:
+ text = text.decode('utf8')
+ except UnicodeDecodeError,e:
+ i,j = e.args[2:4]
+ raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],text[i-10: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"
--- a/reportlab/platypus/doctemplate.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/doctemplate.py Wed Apr 05 15:18:32 2006 +0000
@@ -81,11 +81,13 @@
use NextPageTemplate which creates an ActionFlowable.
'''
def __init__(self,action=()):
+ #must call super init to ensure it has a width and height (of zero),
+ #as in some cases the packer might get called on it...
+ Flowable.__init__(self)
if type(action) not in (ListType, TupleType):
action = (action,)
self.action = tuple(action)
-
def apply(self,doc):
'''
This is called by the doc.build processing to allow the instance to
@@ -130,6 +132,10 @@
def __init__(self,ix,resume=0):
ActionFlowable.__init__(self,('currentFrame',ix,resume))
+class NullActionFlowable(ActionFlowable):
+ def apply(self):
+ pass
+
class _FrameBreak(LCActionFlowable):
'''
A special ActionFlowable that allows setting doc._nextFrameIndex
@@ -570,7 +576,7 @@
if i:
if not getattr(flowables[i],'locChanger',None): i += 1
K = KeepTogether(flowables[:i])
- for f in K._flowables:
+ for f in K._content:
f.keepWithNext = 0
del flowables[:i]
flowables.insert(0,K)
@@ -619,16 +625,17 @@
else:
n = 0
if n:
- if self.frame.add(S[0], self.canv, trySplit=0):
- self._curPageFlowableCount = self._curPageFlowableCount + 1
- self.afterFlowable(S[0])
- else:
- ident = "Splitting error(n==%d) on page %d in\n%s" % (n,self.page,self._fIdent(f,30,self.frame))
- #leave to keep apart from the raise
- raise LayoutError(ident)
- del S[0]
- for f in xrange(n-1):
- flowables.insert(f,S[f]) # put split flowables back on the list
+ if not isinstance(S[0],(PageBreak,SlowPageBreak,ActionFlowable)):
+ if self.frame.add(S[0], self.canv, trySplit=0):
+ self._curPageFlowableCount = self._curPageFlowableCount + 1
+ self.afterFlowable(S[0])
+ else:
+ ident = "Splitting error(n==%d) on page %d in\n%s" % (n,self.page,self._fIdent(f,30,self.frame))
+ #leave to keep apart from the raise
+ raise LayoutError(ident)
+ del S[0]
+ for i,f in enumerate(S):
+ flowables.insert(i,f) # put split flowables back on the list
else:
if hasattr(f,'_postponed'):
ident = "Flowable %s too large on page %d" % (self._fIdent(f,30,self.frame), self.page)
--- a/reportlab/platypus/flowables.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/flowables.py Wed Apr 05 15:18:32 2006 +0000
@@ -76,6 +76,11 @@
self._traceInfo = None
self._showBoundary = None
+ #many flowables handle text and must be processed in the
+ #absence of a canvas. tagging them with their encoding
+ #helps us to get conversions right. Use Python codec names.
+ self.encoding = None
+
def _drawOn(self,canv):
'''ensure canv is set on and then draw'''
@@ -410,7 +415,7 @@
def wrap(self, availWidth, availHeight):
self.width = availWidth
self.height = availHeight
- return (availWidth,availHeight) #step back a point
+ return (availWidth,availHeight-1e-8) #step back a point
def draw(self):
pass
@@ -435,7 +440,7 @@
return (availWidth, availHeight)
return (0, 0)
-def _listWrapOn(F,availWidth,canv,mergeSpace=1,obj=None):
+def _listWrapOn(F,availWidth,canv,mergeSpace=1,obj=None,dims=None):
'''return max width, required height for a list of flowables F'''
W = 0
H = 0
@@ -443,6 +448,7 @@
atTop = 1
for f in F:
w,h = f.wrapOn(canv,availWidth,0xfffffff)
+ if dims is not None: dims.append((w,h))
if w<=_FUZZ or h<=_FUZZ: continue
W = max(W,w)
H += h
@@ -465,13 +471,30 @@
assert not [x for x in V if isinstance(x,LCActionFlowable)],'LCActionFlowables not allowed in sublists'
return V
-class KeepTogether(Flowable):
+class _ContainerSpace: #Abstract some common container like behaviour
+ def getSpaceBefore(self):
+ for c in self._content:
+ if not hasattr(c,'frameAction'):
+ return c.getSpaceBefore()
+ return 0
+
+ def getSpaceAfter(self,content=None):
+ #this needs 2.4
+ #for c in reversed(content or self._content):
+ reverseContent = (content or self._content)[:]
+ reverseContent.reverse()
+ for c in reverseContent:
+ if not hasattr(c,'frameAction'):
+ return c.getSpaceAfter()
+ return 0
+
+class KeepTogether(_ContainerSpace,Flowable):
def __init__(self,flowables,maxHeight=None):
- self._flowables = _flowableSublist(flowables)
+ self._content = _flowableSublist(flowables)
self._maxHeight = maxHeight
def __repr__(self):
- f = self._flowables
+ f = self._content
L = map(repr,f)
import string
L = "\n"+string.join(L, "\n")
@@ -479,17 +502,34 @@
return "KeepTogether(%s,maxHeight=%s) # end KeepTogether" % (L,self._maxHeight)
def wrap(self, aW, aH):
- W,H = _listWrapOn(self._flowables,aW,self.canv)
- self._CPage = (H>aH) and (not self._maxHeight or H<=self._maxHeight)
+ dims = []
+ W,H = _listWrapOn(self._content,aW,self.canv,dims=dims)
+ self._H = H
+ self._H0 = dims and dims[0][1] or 0
+ self._wrapInfo = aW,aH
return W, 0xffffff # force a split
def split(self, aW, aH):
- S = getattr(self,'_CPage',1) and [CondPageBreak(aH+1)] or []
- for f in self._flowables: S.append(f)
+ if getattr(self,'_wrapInfo',None)!=(aW,aH): self.wrap(aW,aH)
+ S = self._content[:]
+ C0 = self._H>aH and (not self._maxHeight or aH>self._maxHeight)
+ C1 = self._H0>aH
+ if C0 or C1:
+ if C0:
+ from doctemplate import FrameBreak
+ A = FrameBreak
+ else:
+ from doctemplate import NullActionFlowable
+ A = NullActionFlowable
+ S.insert(0,A())
return S
- def identity(self):
- return "<KeepTogether at %s%s> containing :%s" % (hex(id(self)),self._frameName(),"\n".join([f.identity() for f in self._flowables]))
+ def identity(self, maxLen=None):
+ msg = "<KeepTogether at %s%s> containing :%s" % (hex(id(self)),self._frameName(),"\n".join([f.identity() for f in self._content]))
+ if maxLen:
+ return msg[0:maxLen]
+ else:
+ return msg
class Macro(Flowable):
"""This is not actually drawn (i.e. it has zero height)
@@ -531,6 +571,12 @@
self.ypad = ypad
self._side = side
+ def getSpaceBefore(self):
+ return max(self.P.getSpaceBefore(),self.I.getSpaceBefore())
+
+ def getSpaceAfter(self):
+ return max(self.P.getSpaceAfter(),self.I.getSpaceAfter())
+
def wrap(self,availWidth,availHeight):
wI, hI = self.I.wrap(availWidth,availHeight)
self.wI = wI
@@ -643,23 +689,7 @@
self.trailer = _flowableSublist(trailer)
self.header = _flowableSublist(header)
-class _Container: #Abstract some common container like behaviour
- def getSpaceBefore(self):
- for c in self._content:
- if not hasattr(c,'frameAction'):
- return c.getSpaceBefore()
- return 0
-
- def getSpaceAfter(self,content=None):
- #this needs 2.4
- #for c in reversed(self._content):
- reverseContent = (content or self._content)[:]
- reverseContent.reverse()
- for c in reverseContent:
- if not hasattr(c,'frameAction'):
- return c.getSpaceAfter()
- return 0
-
+class _Container(_ContainerSpace): #Abstract some common container like behaviour
def drawOn(self, canv, x, y, _sW=0, scale=1.0, content=None, aW=None):
'''we simulate being added to a frame'''
pS = 0
@@ -805,7 +835,10 @@
return self.maxWidth - self._leftExtraIndent - self._rightExtraIndent
def identity(self, maxLen=None):
- return "<%s at %s%s%s> size=%sx%s" % (self.__class__.__name__, hex(id(self)), self._frameName(), self.name and ' name="%s"'%self.name or '', fp_str(self.maxWidth),fp_str(self.maxHeight))
+ return "<%s at %s%s%s> size=%sx%s" % (self.__class__.__name__, hex(id(self)), self._frameName(),
+ getattr(self,'name','') and (' name="%s"'% getattr(self,'name','')) or '',
+ getattr(self,'maxWidth','') and (' maxWidth=%s'%fp_str(getattr(self,'maxWidth',0))) or '',
+ getattr(self,'maxHeight','')and (' maxHeight=%s' % fp_str(getattr(self,'maxHeight')))or '')
def wrap(self,availWidth,availHeight):
from doctemplate import LayoutError
--- a/reportlab/platypus/para.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/para.py Wed Apr 05 15:18:32 2006 +0000
@@ -1275,7 +1275,7 @@
needatleast = state["leading"]
else:
needatleast = self.style1.leading
- if availableHeight < needatleast:
+ if availableHeight<=needatleast:
self.cansplit = 0
#if debug:
# print "CANNOT COMPILE, NEED AT LEAST", needatleast, 'AVAILABLE', availableHeight
--- a/reportlab/platypus/paragraph.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/paragraph.py Wed Apr 05 15:18:32 2006 +0000
@@ -2,18 +2,30 @@
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/paragraph.py
__version__=''' $Id$ '''
-from string import split, strip, join, whitespace, find
+from string import join, whitespace, find
from operator import truth
from types import StringType, ListType
-from reportlab.pdfbase.pdfmetrics import stringWidth
+from reportlab.pdfbase.pdfmetrics import stringWidth, getFont
from reportlab.platypus.paraparser import ParaParser
from reportlab.platypus.flowables import Flowable
from reportlab.lib.colors import Color
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
from reportlab.lib.utils import _className
+from reportlab.lib.textsplit import wordSplit
from copy import deepcopy
from reportlab.lib.abag import ABag
+
+#on UTF8 branch, split and strip must be unicode-safe!
+def split(text, delim=' '):
+ if type(text) is str: text = text.decode('utf8')
+ if type(delim) is str: delim = delim.decode('utf8')
+ return [uword.encode('utf8') for uword in text.split(delim)]
+
+def strip(text):
+ if type(text) is str: text = text.decode('utf8')
+ return text.strip().encode('utf8')
+
class ParaLines(ABag):
"""
class ParaLines contains the broken into lines representation of Paragraphs
@@ -126,9 +138,18 @@
xtraState.underlines.append( (xtraState.underline_x, cur_x, xtraState.underlineColor) )
xtraState.underlineColor = xtraState.textColor
xtraState.underline_x = cur_x
+ if not xtraState.link and f.link:
+ xtraState.link = f.link
+ xtraState.link_x = cur_x
+ elif xtraState.link and f.link is not xtraState.link:
+ spacelen = tx._canvas.stringWidth(' ', tx._fontname, tx._fontsize)
+ xtraState.links.append( (xtraState.link_x, cur_x-spacelen, xtraState.link) )
+ xtraState.link = None
cur_x = cur_x + txtlen
if xtraState.underline:
xtraState.underlines.append( (xtraState.underline_x, cur_x, xtraState.underlineColor) )
+ if xtraState.link:
+ xtraState.links.append( (xtraState.link_x, cur_x, xtraState.link) )
def _leftDrawParaLineX( tx, offset, line, last=0):
setXPos(tx,offset)
@@ -175,7 +196,7 @@
def _sameFrag(f,g):
'returns 1 if two ParaFrags map out the same'
if hasattr(f,'cbDefn') or hasattr(g,'cbDefn'): return 0
- for a in ('fontName', 'fontSize', 'textColor', 'rise', 'underline'):
+ for a in ('fontName', 'fontSize', 'textColor', 'rise', 'underline', 'link'):
if getattr(f,a)!=getattr(g,a): return 0
return 1
@@ -345,7 +366,7 @@
if j==lim:
i=i+1
-def _do_under_lines(i, t_off, tx):
+def _do_under_line(i, t_off, tx):
y = tx.XtraState.cur_y - i*tx.XtraState.style.leading - tx.XtraState.f.fontSize/8.0 # 8.0 factor copied from para.py
text = join(tx.XtraState.lines[i][1])
textlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize)
@@ -364,6 +385,24 @@
xtraState.underline=0
xtraState.underlineColor=None
+def _do_link_line(i, t_off, tx):
+ xs = tx.XtraState
+ leading = xs.style.leading
+ y = xs.cur_y - i*leading - xs.f.fontSize/8.0 # 8.0 factor copied from para.py
+ text = join(xs.lines[i][1])
+ textlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize)
+ tx._canvas.linkRect("", xs.link, (t_off, y, t_off+textlen, y+leading), relative=1)
+
+def _do_link(i, t_off, tx):
+ xs = tx.XtraState
+ leading = xs.style.leading
+ y = xs.cur_y - i*leading - xs.f.fontSize/8.0 # 8.0 factor copied from para.py
+ for x1,x2,link in xs.links:
+ tx._canvas.line(t_off+x1, y, t_off+x2, y)
+ tx._canvas.linkRect("", link, (t_off+x1, y, t_off+x2, y+leading), relative=1)
+ xs.links = []
+ xs.link=None
+
class Paragraph(Flowable):
""" Paragraph(text, style, bulletText=None, caseSensitive=1)
text a string of stuff to go into the paragraph.
@@ -387,11 +426,11 @@
It will also be able to handle any MathML specified Greek characters.
"""
- def __init__(self, text, style, bulletText = None, frags=None, caseSensitive=1):
+ def __init__(self, text, style, bulletText = None, frags=None, caseSensitive=1, encoding='utf8'):
self.caseSensitive = caseSensitive
+ self.encoding = encoding
self._setup(text, style, bulletText, frags, cleanBlockQuotedText)
-
def __repr__(self):
import string
n = self.__class__.__name__
@@ -431,7 +470,12 @@
leftIndent = self.style.leftIndent
first_line_width = availWidth - (leftIndent+self.style.firstLineIndent) - self.style.rightIndent
later_widths = availWidth - leftIndent - self.style.rightIndent
- self.blPara = self.breakLines([first_line_width, later_widths])
+
+ if self.style.wordWrap == 'CJK':
+ #use Asian text wrap algorithm to break characters
+ self.blPara = self.breakLinesCJK([first_line_width, later_widths])
+ else:
+ self.blPara = self.breakLines([first_line_width, later_widths])
self.height = len(self.blPara.lines) * self.style.leading
return (self.width, self.height)
@@ -540,18 +584,19 @@
fontSize = f.fontSize
fontName = f.fontName
words = hasattr(f,'text') and split(f.text, ' ') or f.words
- spaceWidth = stringWidth(' ', fontName, fontSize)
+ spaceWidth = stringWidth(' ', fontName, fontSize, self.encoding)
cLine = []
currentWidth = - spaceWidth # hack to get around extra space for word 1
for word in words:
- wordWidth = stringWidth(word, fontName, fontSize)
+ #this underscores my feeling that Unicode throughout would be easier!
+ wordWidth = stringWidth(word, fontName, fontSize, self.encoding)
newWidth = currentWidth + spaceWidth + wordWidth
- if newWidth<=maxWidth or len(cLine)==0:
+ if newWidth <= maxWidth or len(cLine) == 0:
# fit one more on this line
cLine.append(word)
currentWidth = newWidth
else:
- if currentWidth>self.width: self.width = currentWidth
+ if currentWidth > self.width: self.width = currentWidth
#end of line
lines.append((maxWidth - currentWidth, cLine))
cLine = [word]
@@ -658,6 +703,50 @@
return lines
+
+ def breakLinesCJK(self, width):
+ """Initially, the dumbest possible wrapping algorithm.
+ Cannot handle font variations."""
+
+
+ if type(width) <> ListType: maxWidths = [width]
+ else: maxWidths = width
+ lines = []
+ lineno = 0
+ style = self.style
+ fFontSize = float(style.fontSize)
+
+ #for bullets, work out width and ensure we wrap the right amount onto line one
+ _handleBulletWidth(self.bulletText, style, maxWidths)
+
+ maxWidth = maxWidths[0]
+
+ self.height = 0
+
+ #for now we only handle one fragment. Need to generalize this quickly.
+ if len(self.frags) > 1:
+ raise ValueError('CJK Wordwrap can only handle one fragment per paragraph for now')
+ elif len(self.frags) == 0:
+ return ParaLines(kind=0, fontSize=style.fontSize, fontName=style.fontName,
+ textColor=style.textColor, lines=[])
+
+ f = self.frags[0]
+
+ if hasattr(f,'text'):
+ text = f.text
+ else:
+ text = ''.join(getattr(f,'words',[]))
+
+ from reportlab.lib.textsplit import wordSplit
+ lines = wordSplit(text, maxWidths[0], f.fontName, f.fontSize)
+ #the paragraph drawing routine assumes multiple frags per line, so we need an
+ #extra list like this
+ # [space, [text]]
+ #
+ wrappedLines = [(sp, [line]) for (sp, line) in lines]
+ return f.clone(kind=0, lines=wrappedLines)
+
+
def beginText(self, x, y):
return self.canv.beginText(x, y)
@@ -740,24 +829,27 @@
#now the font for the rest of the paragraph
tx.setFont(f.fontName, f.fontSize, style.leading)
t_off = dpl( tx, offset, lines[0][0], lines[0][1], noJustifyLast and nLines==1)
- if f.underline:
- tx.XtraState=ABag()
- tx.XtraState.cur_y = cur_y
- tx.XtraState.f = f
- tx.XtraState.style = style
- tx.XtraState.lines = lines
- tx.XtraState.underlines=[]
- tx.XtraState.underlineColor=None
+ if f.underline or f.link:
+ xs = tx.XtraState=ABag()
+ xs.cur_y = cur_y
+ xs.f = f
+ xs.style = style
+ xs.lines = lines
+ xs.underlines=[]
+ xs.underlineColor=None
+ xs.links=[]
+ xs.link=f.link
canvas.setStrokeColor(f.textColor)
- _do_under_lines(0, t_off+leftIndent, tx)
+ if f.underline: _do_under_line(0, t_off+leftIndent, tx)
+ if f.link: _do_link_line(0, t_off+leftIndent, tx)
#now the middle of the paragraph, aligned with the left margin which is our origin.
- for i in range(1, nLines):
+ for i in xrange(1, nLines):
t_off = dpl( tx, _offsets[i], lines[i][0], lines[i][1], noJustifyLast and i==lim)
- if f.underline:
- _do_under_lines(i, t_off+leftIndent, tx)
+ if f.underline: _do_under_line(i, t_off+leftIndent, tx)
+ if f.link: _do_link_line(i, t_off+leftIndent, tx)
else:
- for i in range(1, nLines):
+ for i in xrange(1, nLines):
dpl( tx, _offsets[i], lines[i][0], lines[i][1], noJustifyLast and i==lim)
else:
f = lines[0]
@@ -779,16 +871,18 @@
#set up the font etc.
tx = self.beginText(cur_x, cur_y)
- tx.XtraState=ABag()
- tx.XtraState.textColor=None
- tx.XtraState.rise=0
- tx.XtraState.underline=0
- tx.XtraState.underlines=[]
- tx.XtraState.underlineColor=None
+ xs = tx.XtraState=ABag()
+ xs.textColor=None
+ xs.rise=0
+ xs.underline=0
+ xs.underlines=[]
+ xs.underlineColor=None
+ xs.links=[]
+ xs.link=None
tx.setLeading(style.leading)
- tx.XtraState.cur_y = cur_y
- tx.XtraState.f = f
- tx.XtraState.style = style
+ xs.cur_y = cur_y
+ xs.f = f
+ xs.style = style
#f = lines[0].words[0]
#tx._setFont(f.fontName, f.fontSize)
@@ -796,12 +890,14 @@
tx._fontname,tx._fontsize = None, None
t_off = dpl( tx, offset, lines[0], noJustifyLast and nLines==1)
_do_under(0, t_off+leftIndent, tx)
+ _do_link(0, t_off+leftIndent, tx)
#now the middle of the paragraph, aligned with the left margin which is our origin.
for i in range(1, nLines):
f = lines[i]
t_off = dpl( tx, _offsets[i], f, noJustifyLast and i==lim)
_do_under(i, t_off+leftIndent, tx)
+ _do_link(i, t_off+leftIndent, tx)
canvas.drawText(tx)
canvas.restoreState()
--- a/reportlab/platypus/paraparser.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/paraparser.py Wed Apr 05 15:18:32 2006 +0000
@@ -4,7 +4,7 @@
__version__=''' $Id$ '''
import string
import re
-from types import TupleType
+from types import TupleType, UnicodeType, StringType
import sys
import os
import copy
@@ -13,7 +13,6 @@
from reportlab.lib.abag import ABag
from reportlab.lib import xmllib
-_xmllib_newStyle = 1
from reportlab.lib.colors import toColor, white, black, red, Color
from reportlab.lib.fonts import tt2ps, ps2tt
@@ -108,6 +107,18 @@
'backcolor':('backColor',toColor),
'bgcolor':('backColor',toColor),
}
+#things which are valid font attributes
+_linkAttrMap = {'size': ('fontSize', _num),
+ 'face': ('fontName', None),
+ 'name': ('fontName', None),
+ 'fg': ('textColor', toColor),
+ 'color':('textColor', toColor),
+ 'backcolor':('backColor',toColor),
+ 'bgcolor':('backColor',toColor),
+ 'dest': ('link', None),
+ 'destination': ('link', None),
+ 'target': ('link', None),
+ }
def _addAttributeNames(m):
K = m.keys()
@@ -136,264 +147,132 @@
#with additions suggested by Christoph Zwerschke who also suggested the
#numeric entity names that follow.
greeks = {
- 'Alpha': 'A',
- 'Beta': 'B',
- 'Chi': 'C',
- 'Delta': 'D',
- 'Epsilon': 'E',
- 'Eta': 'H',
- 'Gamma': 'G',
- 'Iota': 'I',
- 'Kappa': 'K',
- 'Lambda': 'L',
- 'Mu': 'M',
- 'Nu': 'N',
- 'Omega': 'W',
- 'Omicron': 'O',
- 'Phi': 'F',
- 'Pi': 'P',
- 'Psi': 'Y',
- 'Rho': 'R',
- 'Sigma': 'S',
- 'Tau': 'T',
- 'Theta': 'Q',
- 'Upsilon': 'U',
- 'Xi': 'X',
- 'Zeta': 'Z',
- 'alefsym': '\xc0',
- 'alpha': 'a',
- 'and': '\xd9',
- 'ang': '\xd0',
- 'asymp': '\xbb',
- 'beta': 'b',
- 'bull': '\xb7',
- 'cap': '\xc7',
- 'chi': 'c',
- 'clubs': '\xa7',
- 'cong': '@',
- 'cup': '\xc8',
- 'dArr': '\xdf',
- 'darr': '\xaf',
- 'delta': 'd',
- 'diams': '\xa8',
- 'empty': '\xc6',
- 'epsilon': 'e',
- 'epsiv': 'e',
- 'equiv': '\xba',
- 'eta': 'h',
- 'euro': '\xa0',
- 'exist': '$',
- 'forall': '"',
- 'frasl': '\xa4',
- 'gamma': 'g',
- 'ge': '\xb3',
- 'hArr': '\xdb',
- 'harr': '\xab',
- 'hearts': '\xa9',
- 'hellip': '\xbc',
- 'image': '\xc1',
- 'infin': '\xa5',
- 'int': '\xf2',
- 'iota': 'i',
- 'isin': '\xce',
- 'kappa': 'k',
- 'lArr': '\xdc',
- 'lambda': 'l',
- 'lang': '\xe1',
- 'larr': '\xac',
- 'lceil': '\xe9',
- 'le': '\xa3',
- 'lfloor': '\xeb',
- 'lowast': '*',
- 'loz': '\xe0',
- 'minus': '-',
- 'mu': 'm',
- 'nabla': '\xd1',
- 'ne': '\xb9',
- 'ni': "'",
- 'notin': '\xcf',
- 'nsub': '\xcb',
- 'nu': 'n',
- 'oline': '`',
- 'omega': 'w',
- 'omicron': 'o',
- 'oplus': '\xc5',
- 'or': '\xda',
- 'otimes': '\xc4',
- 'part': '\xb6',
- 'perp': '^',
- 'phi': 'j',
- 'phis': 'f',
- 'pi': 'p',
- 'piv': 'v',
- 'prime': '\xa2',
- 'prod': '\xd5',
- 'prop': '\xb5',
- 'psi': 'y',
- 'rArr': '\xde',
- 'radic': '\xd6',
- 'rang': '\xf1',
- 'rarr': '\xae',
- 'rceil': '\xf9',
- 'real': '\xc2',
- 'rfloor': '\xfb',
- 'rho': 'r',
- 'sdot': '\xd7',
- 'sigma': 's',
- 'sigmaf': 'V',
- 'sigmav': 'V',
- 'sim': '~',
- 'spades': '\xaa',
- 'sub': '\xcc',
- 'sube': '\xcd',
- 'sum': '\xe5',
- 'sup': '\xc9',
- 'supe': '\xca',
- 'tau': 't',
- 'there4': '\\',
- 'theta': 'q',
- 'thetasym': 'J',
- 'thetav': 'J',
- 'trade': '\xe4',
- 'uArr': '\xdd',
- 'uarr': '\xad',
- 'upsih': '\xa1',
- 'upsilon': 'u',
- 'weierp': '\xc3',
- 'xi': 'x',
- 'zeta': 'z',
- }
-
-# mapping of xml character entities to symbol encoding
-symenc = {
- # greek letters
- 913:'A', # Alpha
- 914:'B', # Beta
- 915:'G', # Gamma
- 916:'D', # Delta
- 917:'E', # Epsilon
- 918:'Z', # Zeta
- 919:'H', # Eta
- 920:'Q', # Theta
- 921:'I', # Iota
- 922:'K', # Kappa
- 923:'L', # Lambda
- 924:'M', # Mu
- 925:'N', # Nu
- 926:'X', # Xi
- 927:'O', # Omicron
- 928:'P', # Pi
- 929:'R', # Rho
- 931:'S', # Sigma
- 932:'T', # Tau
- 933:'U', # Upsilon
- 934:'F', # Phi
- 935:'C', # Chi
- 936:'Y', # Psi
- 937:'W', # Omega
- 945:'a', # alpha
- 946:'b', # beta
- 947:'g', # gamma
- 948:'d', # delta
- 949:'e', # epsilon
- 950:'z', # zeta
- 951:'h', # eta
- 952:'q', # theta
- 953:'i', # iota
- 954:'k', # kappa
- 955:'l', # lambda
- 956:'m', # mu
- 957:'n', # nu
- 958:'x', # xi
- 959:'o', # omicron
- 960:'p', # pi
- 961:'r', # rho
- 962:'V', # sigmaf
- 963:'s', # sigma
- 964:'t', # tau
- 965:'u', # upsilon
- 966:'j', # phi
- 967:'c', # chi
- 968:'y', # psi
- 969:'w', # omega
- 977:'J', # thetasym
- 978:'\241', # upsih
- 981:'f', # phis
- 982:'v', # piv
- # mathematical symbols
- 8704:'"', # forall
- 8706:'\266', # part
- 8707:'$', # exist
- 8709:'\306', # empty
- 8711:'\321', # nabla
- 8712:'\316', # isin
- 8713:'\317', # notin
- 8715:'\'', # ni
- 8719:'\325', # prod
- 8721:'\345', # sum
- 8722:'-', # minus
- 8727:'*', # lowast
- 8730:'\326', # radic
- 8733:'\265', # prop
- 8734:'\245', # infin
- 8736:'\320', # ang
- 8869:'\331', # and
- 8870:'\332', # or
- 8745:'\307', # cap
- 8746:'\310', # cup
- 8747:'\362', # int
- 8756:'\\', # there4
- 8764:'~', # sim
- 8773:'@', # cong
- 8776:'\273', #asymp
- 8800:'\271', # ne
- 8801:'\272', # equiv
- 8804:'\243', # le
- 8805:'\263', # ge
- 8834:'\314', # sub
- 8835:'\311', # sup
- 8836:'\313', # nsub
- 8838:'\315', # sube
- 8839:'\312', # supe
- 8853:'\305', # oplus
- 8855:'\304', # otimes
- 8869:'^', # perp
- 8901:'\327', # sdot
- 9674:'\340', # loz
- # technical symbols
- 8968:'\351', # lceil
- 8969:'\371', # rceil
- 8970:'\353', # lfloor
- 8971:'\373', # rfloor
- 9001:'\341', # lang
- 9002:'\361', # rang
- # arrow symbols
- 8592:'\254', # larr
- 8593:'\255', # uarr
- 8594:'\256', # rarr
- 8595:'\257', # darr
- 8596:'\253', # harr
- 8656:'\334', # lArr
- 8657:'\335', # uArr
- 8658:'\336', # rArr
- 8659:'\337', # dArr
- 8660:'\333', # hArr
- # divers symbols
- 8226:'\267', # bull
- 8230:'\274', # hellip
- 8242:'\242', # prime
- 8254:'`', # oline
- 8260:'\244', # frasl
- 8472:'\303', # weierp
- 8465:'\301', # image
- 8476:'\302', # real
- 8482:'\344', # trade
- 8364:'\240', # euro
- 8501:'\300', # alefsym
- 9824:'\252', # spades
- 9827:'\247', # clubs
- 9829:'\251', # hearts
- 9830:'\250' # diams
+ 'alefsym': '\xe2\x84\xb5',
+ 'Alpha': '\xce\x91',
+ 'alpha': '\xce\xb1',
+ 'and': '\xe2\x88\xa7',
+ 'ang': '\xe2\x88\xa0',
+ 'asymp': '\xe2\x89\x88',
+ 'Beta': '\xce\x92',
+ 'beta': '\xce\xb2',
+ 'bull': '\xe2\x80\xa2',
+ 'cap': '\xe2\x88\xa9',
+ 'Chi': '\xce\xa7',
+ 'chi': '\xcf\x87',
+ 'clubs': '\xe2\x99\xa3',
+ 'cong': '\xe2\x89\x85',
+ 'cup': '\xe2\x88\xaa',
+ 'darr': '\xe2\x86\x93',
+ 'dArr': '\xe2\x87\x93',
+ 'delta': '\xce\xb4',
+ 'Delta': '\xe2\x88\x86',
+ 'diams': '\xe2\x99\xa6',
+ 'empty': '\xe2\x88\x85',
+ 'Epsilon': '\xce\x95',
+ 'epsilon': '\xce\xb5',
+ 'epsiv': '\xce\xb5',
+ 'equiv': '\xe2\x89\xa1',
+ 'Eta': '\xce\x97',
+ 'eta': '\xce\xb7',
+ 'euro': '\xe2\x82\xac',
+ 'exist': '\xe2\x88\x83',
+ 'forall': '\xe2\x88\x80',
+ 'frasl': '\xe2\x81\x84',
+ 'Gamma': '\xce\x93',
+ 'gamma': '\xce\xb3',
+ 'ge': '\xe2\x89\xa5',
+ 'harr': '\xe2\x86\x94',
+ 'hArr': '\xe2\x87\x94',
+ 'hearts': '\xe2\x99\xa5',
+ 'hellip': '\xe2\x80\xa6',
+ 'image': '\xe2\x84\x91',
+ 'infin': '\xe2\x88\x9e',
+ 'int': '\xe2\x88\xab',
+ 'Iota': '\xce\x99',
+ 'iota': '\xce\xb9',
+ 'isin': '\xe2\x88\x88',
+ 'Kappa': '\xce\x9a',
+ 'kappa': '\xce\xba',
+ 'Lambda': '\xce\x9b',
+ 'lambda': '\xce\xbb',
+ 'lang': '\xe2\x8c\xa9',
+ 'larr': '\xe2\x86\x90',
+ 'lArr': '\xe2\x87\x90',
+ 'lceil': '\xef\xa3\xae',
+ 'le': '\xe2\x89\xa4',
+ 'lfloor': '\xef\xa3\xb0',
+ 'lowast': '\xe2\x88\x97',
+ 'loz': '\xe2\x97\x8a',
+ 'minus': '\xe2\x88\x92',
+ 'mu': '\xc2\xb5',
+ 'Mu': '\xce\x9c',
+ 'nabla': '\xe2\x88\x87',
+ 'ne': '\xe2\x89\xa0',
+ 'ni': '\xe2\x88\x8b',
+ 'notin': '\xe2\x88\x89',
+ 'nsub': '\xe2\x8a\x84',
+ 'Nu': '\xce\x9d',
+ 'nu': '\xce\xbd',
+ 'oline': '\xef\xa3\xa5',
+ 'omega': '\xcf\x89',
+ 'Omega': '\xe2\x84\xa6',
+ 'Omicron': '\xce\x9f',
+ 'omicron': '\xce\xbf',
+ 'oplus': '\xe2\x8a\x95',
+ 'or': '\xe2\x88\xa8',
+ 'otimes': '\xe2\x8a\x97',
+ 'part': '\xe2\x88\x82',
+ 'perp': '\xe2\x8a\xa5',
+ 'Phi': '\xce\xa6',
+ 'phi': '\xcf\x95',
+ 'phis': '\xcf\x86',
+ 'Pi': '\xce\xa0',
+ 'pi': '\xcf\x80',
+ 'piv': '\xcf\x96',
+ 'prime': '\xe2\x80\xb2',
+ 'prod': '\xe2\x88\x8f',
+ 'prop': '\xe2\x88\x9d',
+ 'Psi': '\xce\xa8',
+ 'psi': '\xcf\x88',
+ 'radic': '\xe2\x88\x9a',
+ 'rang': '\xe2\x8c\xaa',
+ 'rarr': '\xe2\x86\x92',
+ 'rArr': '\xe2\x87\x92',
+ 'rceil': '\xef\xa3\xb9',
+ 'real': '\xe2\x84\x9c',
+ 'rfloor': '\xef\xa3\xbb',
+ 'Rho': '\xce\xa1',
+ 'rho': '\xcf\x81',
+ 'sdot': '\xe2\x8b\x85',
+ 'Sigma': '\xce\xa3',
+ 'sigma': '\xcf\x83',
+ 'sigmaf': '\xcf\x82',
+ 'sigmav': '\xcf\x82',
+ 'sim': '\xe2\x88\xbc',
+ 'spades': '\xe2\x99\xa0',
+ 'sub': '\xe2\x8a\x82',
+ 'sube': '\xe2\x8a\x86',
+ 'sum': '\xe2\x88\x91',
+ 'sup': '\xe2\x8a\x83',
+ 'supe': '\xe2\x8a\x87',
+ 'Tau': '\xce\xa4',
+ 'tau': '\xcf\x84',
+ 'there4': '\xe2\x88\xb4',
+ 'Theta': '\xce\x98',
+ 'theta': '\xce\xb8',
+ 'thetasym': '\xcf\x91',
+ 'thetav': '\xcf\x91',
+ 'trade': '\xef\xa3\xaa',
+ 'uarr': '\xe2\x86\x91',
+ 'uArr': '\xe2\x87\x91',
+ 'upsih': '\xcf\x92',
+ 'Upsilon': '\xce\xa5',
+ 'upsilon': '\xcf\x85',
+ 'weierp': '\xe2\x84\x98',
+ 'Xi': '\xce\x9e',
+ 'xi': '\xce\xbe',
+ 'Zeta': '\xce\x96',
+ 'zeta': '\xce\xb6',
}
#------------------------------------------------------------------------
@@ -477,6 +356,15 @@
def end_u( self ):
self._pop(underline=1)
+ #### link
+ def start_link(self, attributes):
+ self._push(**self.getAttributes(attributes,_linkAttrMap))
+
+ def end_link(self):
+ frag = self._stack[-1]
+ del self._stack[-1]
+ assert frag.link!=None
+
#### super script
def start_super( self, attributes ):
self._push(super=1)
@@ -498,21 +386,14 @@
#### add symbol encoding
def handle_charref(self, name):
try:
- if name[0] == 'x':
- n = string.atoi(name[1:], 16)
+ if name[0]=='x':
+ n = int(name[1:],16)
else:
- n = string.atoi(name)
- except string.atoi_error:
+ n = int(name)
+ except ValueError:
self.unknown_charref(name)
return
- if 0 <=n<=255:
- self.handle_data(chr(n))
- elif symenc.has_key(n):
- self._push(greek=1)
- self.handle_data(symenc[n])
- self._pop(greek=1)
- else:
- self.unknown_charref(name)
+ self.handle_data(unichr(n).encode('utf8'))
def handle_entityref(self,name):
if greeks.has_key(name):
@@ -536,7 +417,7 @@
self._pop(greek=1)
def start_font(self,attr):
- apply(self._push,(),self.getAttributes(attr,_fontAttrMap))
+ self._push(**self.getAttributes(attr,_fontAttrMap))
def end_font(self):
self._pop()
@@ -555,6 +436,7 @@
frag.rise = 0
frag.underline = 0
frag.greek = 0
+ frag.link = None
if bullet:
frag.fontName, frag.bold, frag.italic = ps2tt(style.bulletFontName)
frag.fontSize = style.bulletFontSize
@@ -691,7 +573,7 @@
j = attrMap[k]
func = j[1]
try:
- A[j[0]] = (func is None) and v or apply(func,(v,))
+ A[j[0]] = (func is None) and v or func(v)
except:
self._syntax_error('%s: invalid value %s'%(k,v))
else:
@@ -764,6 +646,16 @@
If errors occur None will be returned and the
self.errors holds a list of the error messages.
"""
+ # AR 20040612 - when we feed Unicode strings in, sgmlop
+ # tries to coerce to ASCII. Must intercept, coerce to
+ # any 8-bit encoding which defines most of 256 points,
+ # and revert at end. Yuk. Preliminary step prior to
+ # removal of parser altogether.
+ enc = self._enc = 'cp1252' #our legacy default
+ self._UNI = type(text) is UnicodeType
+ if self._UNI:
+ text = text.encode(enc)
+
self._setup_for_parse(style)
# the xmlparser requires that all text be surrounded by xml
# tags, therefore we must throw some unused flags around the
@@ -784,6 +676,16 @@
self._iReset()
else:
fragList = bFragList = None
+
+ if self._UNI:
+ #reconvert to unicode
+ if fragList:
+ for frag in fragList:
+ frag.text = unicode(frag.text, self._enc)
+ if bFragList:
+ for frag in bFragList:
+ frag.text = unicode(frag.text, self._enc)
+
return style, fragList, bFragList
def _tt_parse(self,tt):
--- a/reportlab/platypus/tables.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/tables.py Wed Apr 05 15:18:32 2006 +0000
@@ -40,6 +40,8 @@
'alignment': 'LEFT',
'background': (1,1,1),
'valign': 'BOTTOM',
+ 'href': None,
+ 'destination':None,
}
LINECAPS={None: None, 'butt':0,'round':1,'projecting':2,'squared':2}
@@ -59,6 +61,8 @@
alignment = 'LEFT'
background = (1,1,1)
valign = "BOTTOM"
+ href = None
+ destination = None
def __init__(self, name, parent=None):
self.name = name
if parent is not None:
@@ -1251,6 +1255,15 @@
draw(x, y, v)
y -= leading
+ if cellstyle.href:
+ #external hyperlink
+ #print 'drawing URL %d,%d, %d, %d, %s' % (colpos, rowpos, colwidth, rowheight, cellstyle.href)
+ self.canv.linkURL(cellstyle.href, (colpos, rowpos, colpos + colwidth, rowpos + rowheight), relative=1)
+ if cellstyle.destination:
+ #external hyperlink
+ #print 'drawing destination %d,%d, %d, %d, %s' % (colpos, rowpos, colwidth, rowheight, cellstyle.destination)
+ self.canv.linkRect("", cellstyle.destination, Rect=(colpos, rowpos, colpos + colwidth, rowpos + rowheight), relative=1)
+
# for text,
# drawCentredString(self, x, y, text) where x is center
# drawRightString(self, x, y, text) where x is right
@@ -1308,6 +1321,10 @@
new.topPadding = values[0]
elif op == 'BOTTOMPADDING':
new.bottomPadding = values[0]
+ elif op == 'HREF':
+ new.href = values[0]
+ elif op == 'DESTINATION':
+ new.destination = values[0]
GRID_STYLE = TableStyle(
[('GRID', (0,0), (-1,-1), 0.25, colors.black),
--- a/reportlab/rl_config.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/rl_config.py Wed Apr 05 15:18:32 2006 +0000
@@ -20,16 +20,27 @@
overlapAttachedSpace= 1 #if set non false then adajacent flowable space after
#and space before are merged (max space is used).
longTableOptimize = 0 #default don't use Henning von Bargen's long table optimizations
+autoConvertEncoding = 0 #convert internally as needed (experimental)
_FUZZ= 1e-6 #fuzz for layout arithmetic
# places to look for T1Font information
T1SearchPath = (
+ 'c:/Program Files/Adobe/Acrobat 9.0/Resource/Font',
+ 'c:/Program Files/Adobe/Acrobat 8.0/Resource/Font',
+ 'c:/Program Files/Adobe/Acrobat 7.0/Resource/Font',
'c:/Program Files/Adobe/Acrobat 6.0/Resource/Font', #Win32, Acrobat 6
'c:/Program Files/Adobe/Acrobat 5.0/Resource/Font', #Win32, Acrobat 5
'c:/Program Files/Adobe/Acrobat 4.0/Resource/Font', #Win32, Acrobat 4
'%(disk)s/Applications/Python %(sys_version)s/reportlab/fonts', #Mac?
+ '/usr/lib/Acrobat9/Resource/Font', #Linux, Acrobat 5?
+ '/usr/lib/Acrobat8/Resource/Font', #Linux, Acrobat 5?
+ '/usr/lib/Acrobat7/Resource/Font', #Linux, Acrobat 5?
+ '/usr/lib/Acrobat6/Resource/Font', #Linux, Acrobat 5?
'/usr/lib/Acrobat5/Resource/Font', #Linux, Acrobat 5?
'/usr/lib/Acrobat4/Resource/Font', #Linux, Acrobat 4
+ '/usr/local/Acrobat9/Resource/Font', #Linux, Acrobat 5?
+ '/usr/local/Acrobat8/Resource/Font', #Linux, Acrobat 5?
+ '/usr/local/Acrobat7/Resource/Font', #Linux, Acrobat 5?
'/usr/local/Acrobat6/Resource/Font', #Linux, Acrobat 5?
'/usr/local/Acrobat5/Resource/Font', #Linux, Acrobat 5?
'/usr/local/Acrobat4/Resource/Font', #Linux, Acrobat 4
@@ -51,13 +62,23 @@
)
# places to look for CMap files - should ideally merge with above
-CMapSearchPath = ('/usr/lib/Acrobat6/Resource/CMap',
+CMapSearchPath = (
+ '/usr/lib/Acrobat9/Resource/CMap',
+ '/usr/lib/Acrobat8/Resource/CMap',
+ '/usr/lib/Acrobat7/Resource/CMap',
+ '/usr/lib/Acrobat6/Resource/CMap',
'/usr/lib/Acrobat5/Resource/CMap',
'/usr/lib/Acrobat4/Resource/CMap',
+ '/usr/local/Acrobat9/Resource/CMap',
+ '/usr/local/Acrobat8/Resource/CMap',
+ '/usr/local/Acrobat7/Resource/CMap',
'/usr/local/Acrobat6/Resource/CMap',
'/usr/local/Acrobat5/Resource/CMap',
'/usr/local/Acrobat4/Resource/CMap',
'C:\\Program Files\\Adobe\\Acrobat\\Resource\\CMap',
+ 'C:\\Program Files\\Adobe\\Acrobat 9.0\\Resource\\CMap',
+ 'C:\\Program Files\\Adobe\\Acrobat 8.0\\Resource\\CMap',
+ 'C:\\Program Files\\Adobe\\Acrobat 7.0\\Resource\\CMap',
'C:\\Program Files\\Adobe\\Acrobat 6.0\\Resource\\CMap',
'C:\\Program Files\\Adobe\\Acrobat 5.0\\Resource\\CMap',
'C:\\Program Files\\Adobe\\Acrobat 4.0\\Resource\\CMap'
--- a/reportlab/test/test_graphics_charts.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_graphics_charts.py Wed Apr 05 15:18:32 2006 +0000
@@ -131,7 +131,9 @@
def sample3(drawing=None):
"Add sample swatches to a diagram."
+
d = drawing or Drawing(400, 200)
+
swatches = Legend()
swatches.alignment = 'right'
swatches.x = 80
@@ -141,9 +143,12 @@
swatches.columnMaximum = 4
items = [(colors.red, 'before'), (colors.green, 'after')]
swatches.colorNamePairs = items
+
d.add(swatches, 'legend')
+
return d
+
def sample4pie():
width = 300
height = 150
--- a/reportlab/test/test_multibyte_jpn.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_multibyte_jpn.py Wed Apr 05 15:18:32 2006 +0000
@@ -26,15 +26,17 @@
def hDraw(self, c, msg, fnt, x, y):
"Helper - draws it with a box around"
c.setFont(fnt, 16, 16)
+ font = pdfmetrics.getFont(fnt)
c.drawString(x, y, msg)
- c.rect(x,y,pdfmetrics.stringWidth(msg, fnt, 16),16,stroke=1,fill=0)
+ width = font.stringWidth(msg, 16)
+ c.rect(x,y,width,16,stroke=1,fill=0)
def test0(self):
"A basic document drawing some strings"
# if they do not have the Japanese font files, go away quietly
try:
- from reportlab.pdfbase.cidfonts import CIDFont, findCMapFile
+ from reportlab.pdfbase.cidfonts import CIDFont, findCMapFile, UnicodeCIDFont
findCMapFile('90ms-RKSJ-H')
findCMapFile('90msp-RKSJ-H')
findCMapFile('UniJIS-UCS2-H')
@@ -80,16 +82,26 @@
pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','EUC-H'))
self.hDraw(c, '\xC5\xEC\xB5\xFE says Tokyo in EUC', 'HeiseiMin-W3-EUC-H', 100, 550)
- if 0:
- #this is super-slow until we do encoding caching.
- pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','UniJIS-UCS2-H'))
- def asciiToUCS2(text):
- s = ''
- for ch in text:
- s = s + chr(0) + ch
- return s
- self.hDraw(c, '\x67\x71\x4E\xAC' + asciiToUCS2(' says Tokyo in UCS2'),
- 'HeiseiMin-W3-UniJIS-UCS2-H', 100, 525)
+ #this is super-slow until we do encoding caching.
+ pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','UniJIS-UCS2-H'))
+
+ def asciiToUCS2(text):
+ s = ''
+ for ch in text:
+ s = s + chr(0) + ch
+ return s
+ msg = '\x67\x71\x4E\xAC' + asciiToUCS2(' says Tokyo in UTF16')
+ self.hDraw(c, msg,'HeiseiMin-W3-UniJIS-UCS2-H', 100, 525)
+
+ #unicode font automatically supplies the encoding
+ pdfmetrics.registerFont(UnicodeCIDFont('HeiseiMin-W3'))
+
+
+ msg = u'\u6771\u4EAC : Unicode font, unicode input'
+ self.hDraw(c, msg, 'HeiseiMin-W3', 100, 500)
+
+ msg = u'\u6771\u4EAC : Unicode font, utf8 input'.encode('utf8')
+ self.hDraw(c, msg, 'HeiseiMin-W3', 100, 475)
# now try verticals
@@ -105,8 +117,33 @@
height = c.stringWidth('\xC5\xEC\xB5\xFE vertical EUC', 'HeiseiMin-W3-EUC-V', 16)
c.rect(425-8,650,16,-height)
+
+
+ from reportlab.platypus.paragraph import Paragraph
+ from reportlab.lib.styles import ParagraphStyle
+ jStyle = ParagraphStyle('jtext',
+ fontName='HeiseiMin-W3',
+ fontSize=12,
+ wordWrap="CJK"
+ )
+
+ gatwickText = '\xe3\x82\xac\xe3\x83\x88\xe3\x82\xa6\xe3\x82\xa3\xe3\x83\x83\xe3\x82\xaf\xe7\xa9\xba\xe6\xb8\xaf\xe3\x81\xa8\xe9\x80\xa3\xe7\xb5\xa1\xe9\x80\x9a\xe8\xb7\xaf\xe3\x81\xa7\xe7\x9b\xb4\xe7\xb5\x90\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3\x82\x8b\xe5\x94\xaf\xe4\xb8\x80\xe3\x81\xae\xe3\x83\x9b\xe3\x83\x86\xe3\x83\xab\xe3\x81\xa7\xe3\x81\x82\xe3\x82\x8b\xe5\xbd\x93\xe3\x83\x9b\xe3\x83\x86\xe3\x83\xab\xe3\x81\xaf\xe3\x80\x81\xe8\xa1\x97\xe3\x81\xae\xe4\xb8\xad\xe5\xbf\x83\xe9\x83\xa8\xe3\x81\x8b\xe3\x82\x8930\xe5\x88\x86\xe3\x81\xae\xe5\xa0\xb4\xe6\x89\x80\xe3\x81\xab\xe3\x81\x94\xe3\x81\x96\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe5\x85\xa8\xe5\xae\xa2\xe5\xae\xa4\xe3\x81\xab\xe9\xab\x98\xe9\x80\x9f\xe3\x82\xa4\xe3\x83\xb3\xe3\x82\xbf\xe3\x83\xbc\xe3\x83\x8d\xe3\x83\x83\xe3\x83\x88\xe7\x92\xb0\xe5\xa2\x83\xe3\x82\x92\xe5\xae\x8c\xe5\x82\x99\xe3\x81\x97\xe3\x81\xa6\xe3\x81\x8a\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe3\x83\x95\xe3\x82\xa1\xe3\x83\x9f\xe3\x83\xaa\xe3\x83\xbc\xe3\x83\xab\xe3\x83\xbc\xe3\x83\xa0\xe3\x81\xaf5\xe5\x90\x8d\xe6\xa7\x98\xe3\x81\xbe\xe3\x81\xa7\xe3\x81\x8a\xe6\xb3\x8a\xe3\x82\x8a\xe3\x81\x84\xe3\x81\x9f\xe3\x81\xa0\xe3\x81\x91\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe3\x81\xbe\xe3\x81\x9f\xe3\x80\x81\xe3\x82\xa8\xe3\x82\xb0\xe3\x82\xbc\xe3\x82\xaf\xe3\x83\x86\xe3\x82\xa3\xe3\x83\x96\xe3\x83\xab\xe3\x83\xbc\xe3\x83\xa0\xe3\x81\xae\xe3\x81\x8a\xe5\xae\xa2\xe6\xa7\x98\xe3\x81\xaf\xe3\x80\x81\xe3\x82\xa8\xe3\x82\xb0\xe3\x82\xbc\xe3\x82\xaf\xe3\x83\x86\xe3\x82\xa3\xe3\x83\x96\xe3\x83\xa9\xe3\x82\xa6\xe3\x83\xb3\xe3\x82\xb8\xe3\x82\x92\xe3\x81\x94\xe5\x88\xa9\xe7\x94\xa8\xe3\x81\x84\xe3\x81\x9f\xe3\x81\xa0\xe3\x81\x91\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe4\xba\x8b\xe5\x89\x8d\xe3\x81\xab\xe3\x81\x94\xe4\xba\x88\xe7\xb4\x84\xe3\x81\x84\xe3\x81\x9f\xe3\x81\xa0\xe3\x81\x91\xe3\x82\x8b\xe3\x82\xbf\xe3\x82\xa4\xe3\x83\xa0\xe3\x83\x88\xe3\x82\xa5\xe3\x83\x95\xe3\x83\xa9\xe3\x82\xa4\xe3\x83\xbb\xe3\x83\x91\xe3\x83\x83\xe3\x82\xb1\xe3\x83\xbc\xe3\x82\xb8\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x81\xe7\xa9\xba\xe6\xb8\xaf\xe3\x81\xae\xe9\xa7\x90\xe8\xbb\x8a\xe6\x96\x99\xe9\x87\x91\xe3\x81\x8c\xe5\x90\xab\xe3\x81\xbe\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x8a\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82'
+
+ c.setFont('HeiseiMin-W3', 12)
+## from reportlab.lib.textsplit import wordSplit
+## y = 400
+## splat = wordSplit(gatwickText, 250, 'HeiseiMin-W3', 12, encoding='utf8')
+## for (line, extraSpace) in splat:
+## c.drawString(100,y,line)
+## y -= 14
+ jPara = Paragraph(gatwickText, jStyle)
+ jPara.wrap(250, 200)
+ #from pprint import pprint as pp
+ #pp(jPara.blPara)
+ jPara.drawOn(c, 100, 250)
+
c.setFillColor(colors.purple)
- tx = c.beginText(100, 250)
+ tx = c.beginText(100, 200)
tx.setFont('Helvetica', 12)
tx.textLines("""This document shows sample output in Japanese
from the Reportlab PDF library. This page shows the two fonts
@@ -119,6 +156,21 @@
c.drawText(tx)
c.setFont('Helvetica',10)
c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber())
+
+
+
+ c.showPage()
+
+ c.setFont('Helvetica', 30)
+ c.drawString(100,700, 'Japanese TrueType Font Support')
+ msg = u'\u6771\u4EAC : Unicode font, utf8 input'.encode('utf8')
+ from reportlab.pdfbase.ttfonts import TTFont
+ pdfmetrics.registerFont(TTFont('MS Mincho','msmincho.ttf'))
+ c.setFont('MS Mincho', 30)
+ c.drawString(100,600, msg)
+
+
+
c.showPage()
# realistic text sample
@@ -214,19 +266,81 @@
c.setFont('Helvetica',10)
c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber())
+
+
c.showPage()
- # full kuten chart in EUC
- c.setFont('Helvetica', 24)
- c.drawString(72,750, 'Characters available in JIS 0208-1997')
- y = 600
- for row in range(1, 95):
- KutenRowCodeChart(row, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, y)
- y = y - 125
- if y < 50:
- c.setFont('Helvetica',10)
- c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber())
- c.showPage()
- y = 700
+
+ from reportlab.lib import textsplit
+
+ c.setFont('HeiseiMin-W3', 14)
+ y = 700
+ c.drawString(70, y, 'cannot end line')
+ y -= 20
+ for group in textsplit.CANNOT_START_LINE:
+ c.drawString(70, y, group)
+ y -= 20
+ c.setFont('Helvetica',10)
+ c.drawString(70, y, ' '.join(map(lambda x: repr(x)[4:-1], group)))
+ c.setFont('HeiseiMin-W3', 14)
+ y -= 20
+
+
+
+ y -= 20
+ c.drawString(70, y, 'cannot end line')
+ y -= 20
+ for group in textsplit.CANNOT_END_LINE:
+ c.drawString(70, y, group)
+ y -= 20
+ c.setFont('Helvetica',10)
+ c.drawString(70, y, ' '.join(map(lambda x: repr(x)[2:], group)))
+ c.setFont('HeiseiMin-W3', 14)
+ y -= 20
+
+ c.showPage()
+## c.showPage()
+##
+##
+## # full kuten chart in EUC
+## c.setFont('Helvetica', 24)
+## c.drawString(72,750, 'Characters available in JIS 0208-1997')
+## y = 600
+## for row in range(1, 95):
+## KutenRowCodeChart(row, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, y)
+## y = y - 125
+## if y < 50:
+## c.setFont('Helvetica',10)
+## c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber())
+## c.showPage()
+## y = 700
+##
+## c.showPage()
+
+
+ #try with Unicode truetype - Mincho for starters
+## import time
+## started = time.clock()
+## c.showPage()
+## c.setFont('Helvetica',16)
+## c.drawString(100,750, 'About to say Tokyo in MS Gothic...')
+##
+## from reportlab.pdfbase.ttfonts import TTFont, TTFontFile
+## f = TTFontFile("msgothic.ttf")
+## print f.name
+##
+## pdfmetrics.registerFont(TTFont(f.name, f))
+##
+## utfText = u'Andr\202'.encode('utf8')
+## c.setFont(f.name,16)
+## c.drawString(100,700, utfText)
+##
+##
+## #tokyoUCS2 = '\x67\x71\x4E\xAC'
+## finished = time.clock()
+
+
+
+
c.save()
--- a/reportlab/test/test_pdfbase_encodings.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_pdfbase_encodings.py Wed Apr 05 15:18:32 2006 +0000
@@ -3,10 +3,165 @@
from reportlab.pdfgen.canvas import Canvas
from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.ttfonts import TTFont
+from reportlab.pdfbase import pdfutils
+
+from reportlab.platypus.paragraph import Paragraph
+from reportlab.lib.styles import ParagraphStyle
+from reportlab.graphics.shapes import Drawing, String, Ellipse
+import re
+import codecs
+textPat = re.compile(r'\([^(]*\)')
+
+#test sentences
+testCp1252 = 'copyright %s trademark %s registered %s ReportLab! Ol%s!' % (chr(169), chr(153),chr(174), chr(0xe9))
+testUni = unicode(testCp1252, 'cp1252')
+testUTF8 = testUni.encode('utf-8')
+# expected result is octal-escaped text in the PDF
+expectedCp1252 = pdfutils._escape(testCp1252)
+
+def extractText(pdfOps):
+ """Utility to rip out the PDF text within a block of PDF operators.
+
+ PDF will show a string draw as something like "(Hello World) Tj"
+ i.e. text is in curved brackets. Crude and dirty, probably fails
+ on escaped brackets.
+ """
+ found = textPat.findall(pdfOps)
+ #chop off '(' and ')'
+ return map(lambda x:x[1:-1], found)
+
+def subsetToUnicode(ttf, subsetCodeStr):
+ """Return unicode string represented by given subsetCode string
+ as found when TrueType font rendered to PDF, ttf must be the font
+ object that was used."""
+ # This relies on TTFont internals and uses the first document
+ # and subset it finds
+ subset = ttf.state.values()[0].subsets[0]
+ chrs = []
+ for codeStr in subsetCodeStr.split('\\'):
+ if codeStr:
+ chrs.append(unichr(subset[int(codeStr[1:], 8)]))
+ return u''.join(chrs)
+
+class TextEncodingTestCase(unittest.TestCase):
+ """Tests of expected Unicode and encoding behaviour
+ """
+ def setUp(self):
+ self.luxi = TTFont("Luxi", "luxiserif.ttf")
+ pdfmetrics.registerFont(self.luxi)
+ self.styNormal = ParagraphStyle(name='Helvetica', fontName='Helvetica-Oblique')
+ self.styTrueType = ParagraphStyle(name='TrueType', fontName='luxi')
+
+ def testStringWidth(self):
+ msg = 'Hello World'
+ assert abs(pdfmetrics.stringWidth(msg, 'Courier', 10) - 66.0) < 0.01
+ assert abs(pdfmetrics.stringWidth(msg, 'Helvetica', 10) - 51.67) < 0.01
+ assert abs(pdfmetrics.stringWidth(msg, 'Times-Roman', 10) - 50.27) < 0.01
+ assert abs(pdfmetrics.stringWidth(msg, 'Luxi', 10) - 50.22) < 0.01
+
+ uniMsg1 = u"Hello World"
+ assert abs(pdfmetrics.stringWidth(uniMsg1, 'Courier', 10) - 66.0) < 0.01
+ assert abs(pdfmetrics.stringWidth(uniMsg1, 'Helvetica', 10) - 51.67) < 0.01
+ assert abs(pdfmetrics.stringWidth(uniMsg1, 'Times-Roman', 10) - 50.27) < 0.01
+ assert abs(pdfmetrics.stringWidth(uniMsg1, 'Luxi', 10) - 50.22) < 0.01
+
+
+ # Courier are all 600 ems wide. So if one 'measures as utf8' one will
+ # get a wrong width as extra characters are seen
+ assert len(testCp1252) == 52
+ assert abs(pdfmetrics.stringWidth(testCp1252, 'Courier', 10, 'cp1252') - 312.0) < 0.01
+ # the test string has 5 more bytes and so "measures too long" if passed to
+ # a single-byte font which treats it as a single-byte string.
+ assert len(testUTF8)==57
+ assert abs(pdfmetrics.stringWidth(testUTF8, 'Courier', 10) - 312.0) < 0.01
+
+ assert len(testUni)==52
+ assert abs(pdfmetrics.stringWidth(testUni, 'Courier', 10) - 312.0) < 0.01
-class EncodingTestCase(unittest.TestCase):
- "Make documents with custom encodings"
+ # now try a TrueType font. Should be able to accept Unicode or UTF8
+ assert abs(pdfmetrics.stringWidth(testUTF8, 'Luxi', 10) - 224.44) < 0.01
+ assert abs(pdfmetrics.stringWidth(testUni, 'Luxi', 10) - 224.44) < 0.01
+
+ def testUtf8Canvas(self):
+ """Verify canvas declared as utf8 autoconverts.
+
+ This assumes utf8 input. It converts to the encoding of the
+ underlying font, so both text lines APPEAR the same."""
+
+
+ c = Canvas(outputfile('test_pdfbase_encodings_utf8.pdf'))
+
+ c.drawString(100,700, testUTF8)
+
+ # Set a font with UTF8 encoding
+ c.setFont('Luxi', 12)
+
+ # This should pass the UTF8 through unchanged
+ c.drawString(100,600, testUTF8)
+ # and this should convert from Unicode to UTF8
+ c.drawString(100,500, testUni)
+
+
+ # now add a paragraph in Latin-1 in the latin-1 style
+ p = Paragraph(testUTF8, style=self.styNormal, encoding="utf-8")
+ w, h = p.wrap(150, 100)
+ p.drawOn(c, 100, 400) #3
+ c.rect(100,300,w,h)
+
+ # now add a paragraph in UTF-8 in the UTF-8 style
+ p2 = Paragraph(testUTF8, style=self.styTrueType, encoding="utf-8")
+ w, h = p2.wrap(150, 100)
+ p2.drawOn(c, 300, 400) #4
+ c.rect(100,300,w,h)
+
+ # now add a paragraph in Unicode in the latin-1 style
+ p3 = Paragraph(testUni, style=self.styNormal)
+ w, h = p3.wrap(150, 100)
+ p3.drawOn(c, 100, 300)
+ c.rect(100,300,w,h)
+
+ # now add a paragraph in Unicode in the UTF-8 style
+ p4 = Paragraph(testUni, style=self.styTrueType)
+ p4.wrap(150, 100)
+ p4.drawOn(c, 300, 300)
+ c.rect(300,300,w,h)
+
+ # now a graphic
+ d1 = Drawing(400,50)
+ d1.add(Ellipse(200,25,200,12.5, fillColor=None))
+ d1.add(String(200,25,testUTF8, textAnchor='middle', encoding='utf-8'))
+ d1.drawOn(c, 100, 150)
+
+ # now a graphic in utf8
+ d2 = Drawing(400,50)
+ d2.add(Ellipse(200,25,200,12.5, fillColor=None))
+ d2.add(String(200,25,testUTF8, fontName='Luxi', textAnchor='middle', encoding='utf-8'))
+ d2.drawOn(c, 100, 100)
+
+ # now a graphic in Unicode with T1 font
+ d3 = Drawing(400,50)
+ d3.add(Ellipse(200,25,200,12.5, fillColor=None))
+ d3.add(String(200,25,testUni, textAnchor='middle'))
+ d3.drawOn(c, 100, 50)
+
+ # now a graphic in Unicode with TT font
+ d4 = Drawing(400,50)
+ d4.add(Ellipse(200,25,200,12.5, fillColor=None))
+ d4.add(String(200,25,testUni, fontName='Luxi', textAnchor='middle'))
+ d4.drawOn(c, 100, 0)
+
+ extracted = extractText(c.getCurrentPageContent())
+ self.assertEquals(extracted[0], expectedCp1252)
+ self.assertEquals(extracted[1], extracted[2])
+ #self.assertEquals(subsetToUnicode(self.luxi, extracted[1]), testUni)
+ c.save()
+
+class FontEncodingTestCase(unittest.TestCase):
+ """Make documents with custom encodings of Type 1 built-in fonts.
+
+ Nothing really to do with character encodings; this is about hacking the font itself"""
def test0(self):
"Make custom encodings of standard fonts"
@@ -26,6 +181,7 @@
pdfmetrics.registerEncoding(zenc)
# now we can make a font based on this encoding
+ # AR hack/workaround: the name of the encoding must be a Python codec!
f = pdfmetrics.Font('FontWithoutVowels', 'Helvetica-Oblique', 'EncodingWithoutVowels')
pdfmetrics.registerFont(f)
@@ -86,11 +242,12 @@
c.save()
-
+def makeSuite():
+ return makeSuiteForClasses(
+ TextEncodingTestCase,
-def makeSuite():
- return makeSuiteForClasses(EncodingTestCase)
-
+ #FontEncodingTestCase - nobbled for now due to old stuff which needs removing.
+ )
#noruntests
if __name__ == "__main__":
--- a/reportlab/test/test_pdfbase_pdfmetrics.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_pdfbase_pdfmetrics.py Wed Apr 05 15:18:32 2006 +0000
@@ -8,10 +8,8 @@
The main test prints out a PDF documents enabling checking of widths of every
glyph in every standard font. Long!
"""
-
from reportlab.test import unittest
from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation
-
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase import _fontdata
from reportlab.pdfgen.canvas import Canvas
@@ -30,8 +28,9 @@
def makeWidthTestForAllGlyphs(canv, fontName, outlining=1):
"""New page, then runs down doing all the glyphs in one encoding"""
thisFont = pdfmetrics.getFont(fontName)
+ encName = thisFont.encName
canv.setFont('Helvetica-Bold', 12)
- title = 'Glyph Metrics Test for font %s, ascent=%s, descent=%s' % (fontName, str(thisFont.face.ascent), str(thisFont.face.descent))
+ title = 'Glyph Metrics Test for font %s, ascent=%s, descent=%s, encoding=%s' % (fontName, str(thisFont.face.ascent), str(thisFont.face.descent), encName)
canv.drawString(80, 750, title)
canv.setFont('Helvetica-Oblique',10)
canv.drawCentredString(297, 54, 'Page %d' % canv.getPageNumber())
@@ -53,14 +52,14 @@
glyphName = glyphNames[i]
if glyphName is not None:
canv.setFont('Helvetica', 10)
- canv.drawString(80, y, '%03d %s ' % (i, glyphName))
+ text = unicode(chr(i),encName).encode('utf8')*30
try:
- glyphWidth = thisFont.face.glyphWidths[glyphName]
+ w = canv.stringWidth(text, fontName, 10)
+ canv.drawString(80, y, '%03d %s w=%3d' % (i, glyphName, int((w/3.)*10)))
canv.setFont(fontName, 10)
- canv.drawString(200, y, chr(i) * 30)
+ canv.drawString(200, y, text)
# now work out width and put a red marker next to the end.
- w = canv.stringWidth(chr(i) * 30, fontName, 10)
canv.setFillColor(colors.red)
canv.rect(200 + w, y-1, 5, 10, stroke=0, fill=1)
canv.setFillColor(colors.black)
--- a/reportlab/test/test_pdfgen_general.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_pdfgen_general.py Wed Apr 05 15:18:32 2006 +0000
@@ -205,6 +205,13 @@
framePage(c, 'PDFgen graphics API test script')
makesubsection(c, "PDFgen", 10*inch)
+ #quickie encoding test: when canvas encoding not set,
+ #the following should do (tm), (r) and (c)
+ c.drawString(100, 100, 'copyright %s trademark %s registered %s ReportLab!' % (unichr(169).encode('utf8'), unichr(153).encode('utf8'),unichr(174).encode('utf8')))
+
+
+
+
t = c.beginText(inch, 10*inch)
t.setFont('Times-Roman', 10)
drawCrossHairs(c, t.getX(),t.getY())
--- a/reportlab/test/test_pdfgen_links.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_pdfgen_links.py Wed Apr 05 15:18:32 2006 +0000
@@ -49,22 +49,22 @@
c.addOutlineEntry("Page 1","P1")
#Note : XYZ Left is ignored because at this zoom the whole page fits the screen
- c.bookmarkPage("P1_XYZ",fitType="XYZ",top=7*inch,left=3*inch,zoom=0.5)
+ c.bookmarkPage("P1_XYZ",fit="XYZ",top=7*inch,left=3*inch,zoom=0.5)
c.addOutlineEntry("Page 1 XYZ #1 (top=7,left=3,zoom=0.5)","P1_XYZ",level=1)
- c.bookmarkPage("P1_XYZ2",fitType="XYZ",top=7*inch,left=3*inch,zoom=5)
+ c.bookmarkPage("P1_XYZ2",fit="XYZ",top=7*inch,left=3*inch,zoom=5)
c.addOutlineEntry("Page 1 XYZ #2 (top=7,left=3,zoom=5)","P1_XYZ2",level=1)
- c.bookmarkPage("P1_FIT",fitType="Fit")
+ c.bookmarkPage("P1_FIT",fit="Fit")
c.addOutlineEntry("Page 1 Fit","P1_FIT",level=1)
- c.bookmarkPage("P1_FITH",fitType="FitH",top=2*inch)
+ c.bookmarkPage("P1_FITH",fit="FitH",top=2*inch)
c.addOutlineEntry("Page 1 FitH (top = 2 inch)","P1_FITH",level=1)
- c.bookmarkPage("P1_FITV",fitType="FitV",left=3*inch)
+ c.bookmarkPage("P1_FITV",fit="FitV",left=3*inch)
c.addOutlineEntry("Page 1 FitV (left = 3 inch)","P1_FITV",level=1)
- c.bookmarkPage("P1_FITR",fitType="FitR",left=1*inch,bottom=2*inch,right=5*inch,top=6*inch)
+ c.bookmarkPage("P1_FITR",fit="FitR",left=1*inch,bottom=2*inch,right=5*inch,top=6*inch)
c.addOutlineEntry("Page 1 FitR (left=1,bottom=2,right=5,top=6)","P1_FITR",level=1)
c.bookmarkPage("P1_FORWARD")
--- a/reportlab/test/test_platypus_tables.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_platypus_tables.py Wed Apr 05 15:18:32 2006 +0000
@@ -27,7 +27,9 @@
styles = []
for i in range(5):
styles.append(TableStyle([('ALIGN', (1,1), (-1,-1), 'RIGHT'),
- ('ALIGN', (0,0), (-1,0), 'CENTRE') ]))
+ ('ALIGN', (0,0), (-1,0), 'CENTRE'),
+ ('HREF', (0,0), (0,0), 'www.google.com'),
+ ]))
for style in styles[1:]:
style.add('GRID', (0,0), (-1,-1), 0.25, colors.black)
for style in styles[2:]:
--- a/reportlab/test/test_tools_pythonpoint.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_tools_pythonpoint.py Wed Apr 05 15:18:32 2006 +0000
@@ -1,16 +1,12 @@
"""Tests for the PythonPoint tool.
"""
-
import os, sys, string
from reportlab.test import unittest
from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation
-
import reportlab
-
class PythonPointTestCase(unittest.TestCase):
"Some very crude tests on PythonPoint."
-
def test0(self):
"Test if pythonpoint.pdf can be created from pythonpoint.xml."
@@ -37,7 +33,6 @@
def makeSuite():
return makeSuiteForClasses(PythonPointTestCase)
-
#noruntests
if __name__ == "__main__":
unittest.TextTestRunner().run(makeSuite())
--- a/reportlab/tools/docco/graphdocpy.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/tools/docco/graphdocpy.py Wed Apr 05 15:18:32 2006 +0000
@@ -80,7 +80,7 @@
canvas.setFont('Times-Roman', 12)
canvas.drawString(4 * inch, cm, "%d" % pageNumber)
if hasattr(canvas, 'headerLine'): # hackish
- headerline = string.join(canvas.headerLine, ' \215 ')
+ headerline = string.join(canvas.headerLine, ' \xc2\x8d ')
canvas.drawString(2*cm, A4[1]-1.75*cm, headerline)
canvas.setFont('Times-Roman', 8)
--- a/reportlab/tools/docco/rl_doc_utils.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/tools/docco/rl_doc_utils.py Wed Apr 05 15:18:32 2006 +0000
@@ -134,7 +134,7 @@
getStory().append(P)
def bullet(text):
- text='<bullet><font name="Symbol">'+chr(183)+'</font></bullet>' + quickfix(text)
+ text='<bullet><font name="Symbol">\xe2\x80\xa2</font></bullet>' + quickfix(text)
P = Paragraph(text, BU)
getStory().append(P)
--- a/reportlab/tools/pythonpoint/pythonpoint.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/tools/pythonpoint/pythonpoint.py Wed Apr 05 15:18:32 2006 +0000
@@ -951,13 +951,6 @@
global _styles
_styles = newStyleSheet
-
-##def test():
-## p = stdparser.PPMLParser()
-## p.feed(sample)
-## p.getPresentation().save()
-## p.close()
-
_pyRXP_Parser = None
def validate(rawdata):
global _pyRXP_Parser
@@ -988,7 +981,6 @@
#if pyRXP present, use it to check and get line numbers for errors...
validate(rawdata)
-
return _process(rawdata, datafilename, notes, handout, printout, cols, verbose, outDir, fx)
def _process(rawdata, datafilename, notes=0, handout=0, printout=0, cols=0, verbose=0, outDir=None, fx=1):
--- a/reportlab/tools/pythonpoint/stdparser.py Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/tools/pythonpoint/stdparser.py Wed Apr 05 15:18:32 2006 +0000
@@ -212,7 +212,6 @@
self.fx = 1
xmllib.XMLParser.__init__(self)
-
def _arg(self,tag,args,name):
"What's this for???"
if args.has_key(name):
@@ -224,7 +223,6 @@
v = None
return v
-
def ceval(self,tag,args,name):
if args.has_key(name):
v = args[name]
@@ -240,7 +238,6 @@
return eval(v)
-
def getPresentation(self):
return self._curPres
@@ -268,7 +265,6 @@
elif self._curSubject <> None:
self._curSubject = self._curSubject + data
-
def handle_cdata(self, data):
#just append to current paragraph text, so we can quote XML
if self._curPara:
@@ -286,7 +282,6 @@
elif self._curSubject <> None:
self._curSubject = self._curSubject + data
-
def start_presentation(self, args):
self._curPres = pythonpoint.PPPresentation()
self._curPres.filename = self._arg('presentation',args,'filename')
@@ -301,12 +296,10 @@
self._curPres.pageWidth = w
#print 'page size =', self._curPres.pageSize
-
def end_presentation(self):
pass
## print 'Fully parsed presentation',self._curPres.filename
-
def start_title(self, args):
self._curTitle = ''
@@ -315,25 +308,20 @@
self._curPres.title = self._curTitle
self._curTitle = None
-
def start_author(self, args):
self._curAuthor = ''
-
def end_author(self):
self._curPres.author = self._curAuthor
self._curAuthor = None
-
def start_subject(self, args):
self._curSubject = ''
-
def end_subject(self):
self._curPres.subject = self._curSubject
self._curSubject = None
-
def start_stylesheet(self, args):
#makes it the current style sheet.
path = self._arg('stylesheet',args,'path')
@@ -356,16 +344,13 @@
pythonpoint.setStyles(func())
## print 'set global stylesheet to %s.%s()' % (modulename, funcname)
-
def end_stylesheet(self):
pass
-
def start_section(self, args):
name = self._arg('section',args,'name')
self._curSection = pythonpoint.PPSection(name)
-
def end_section(self):
self._curSection = None
@@ -399,12 +384,10 @@
s.section = self._curSection
self._curSlide = s
-
def end_slide(self):
self._curPres.slides.append(self._curSlide)
self._curSlide = None
-
def start_frame(self, args):
self._curFrame = pythonpoint.PPFrame(
self.ceval('frame',args,'x'),
@@ -415,22 +398,18 @@
if self._arg('frame',args,'border')=='true':
self._curFrame.showBoundary = 1
-
def end_frame(self):
self._curSlide.frames.append(self._curFrame)
self._curFrame = None
-
def start_notes(self, args):
name = self._arg('notes',args,'name')
self._curNotes = pythonpoint.PPNotes()
-
def end_notes(self):
self._curSlide.notes.append(self._curNotes)
self._curNotes = None
-
def start_registerFont(self, args):
name = self._arg('font',args,'name')
path = self._arg('font',args,'path')
@@ -466,15 +445,14 @@
bt = self._arg('para',args,'bullettext')
if bt == '':
if self._curPara.style == 'Bullet':
- bt = '\267' # Symbol Font bullet character, reasonable default
+ bt = '\xc2\xb7' # Symbol Font bullet character, reasonable default
elif self._curPara.style == 'Bullet2':
- bt = '\267' # second-level bullet
+ bt = '\xc2\xb7' # second-level bullet
else:
bt = None
self._curPara.bulletText = bt
-
def end_para(self):
if self._curFrame:
self._curFrame.content.append(self._curPara)
@@ -737,7 +715,6 @@
initargs = self.ceval('customshape',args,'initargs')
self._curCustomShape = apply(func, initargs)
-
def end_customshape(self):
if self._curSlide:
self._curSlide.graphics.append(self._curCustomShape)
@@ -745,16 +722,11 @@
self._curSection.graphics.append(self._curCustomShape)
self._curCustomShape = None
-
-
def start_drawing(self, args):
#loads one
-
moduleName = args["module"]
funcName = args["constructor"]
-
showBoundary = int(args.get("showBoundary", "0"))
-
hAlign = args.get("hAlign", "CENTER")
@@ -783,12 +755,10 @@
self._curDrawing = pythonpoint.PPDrawing()
self._curDrawing.drawing = drawing
-
def end_drawing(self):
self._curFrame.content.append(self._curDrawing)
self._curDrawing = None
-
def start_pageCatcherFigure(self, args):
filename = args["filename"]
pageNo = int(args["pageNo"])
@@ -809,8 +779,6 @@
self._curFigure = pythonpoint.PPFigure()
self._curFigure.figure = fig
-
-
def end_pageCatcherFigure(self):
self._curFrame.content.append(self._curFigure)
self._curFigure = None
@@ -832,3 +800,14 @@
self._curPara.rawtext = self._curPara.rawtext + '</%s>'% tag
else:
print 'Unknown end tag %s' % tag
+
+ def handle_charref(self, name):
+ try:
+ if name[0]=='x':
+ n = int(name[1:],16)
+ else:
+ n = int(name)
+ except ValueError:
+ self.unknown_charref(name)
+ return
+ self.handle_data(unichr(n).encode('utf8'))