Added printing requirements

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

View File

@@ -0,0 +1,17 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/__init__.py
__version__=''' $Id$ '''
__doc__='''Page Layout and Typography Using Scripts" - higher-level framework for flowing documents'''
from reportlab.platypus.flowables import Flowable, Image, Macro, PageBreak, Preformatted, Spacer, XBox, \
CondPageBreak, KeepTogether, TraceInfo, FailOnWrap, FailOnDraw, PTOContainer, \
KeepInFrame, ParagraphAndImage, ImageAndFlowables, ListFlowable, ListItem, FrameBG, \
PageBreakIfNotEmpty
from reportlab.platypus.paragraph import Paragraph, cleanBlockQuotedText, ParaLines
from reportlab.platypus.paraparser import ParaFrag
from reportlab.platypus.tables import Table, TableStyle, CellStyle, LongTable
from reportlab.platypus.frames import Frame
from reportlab.platypus.doctemplate import BaseDocTemplate, NextPageTemplate, PageTemplate, ActionFlowable, \
SimpleDocTemplate, FrameBreak, PageBegin, Indenter, NotAtTopPageBreak
from reportlab.platypus.xpreformatted import XPreformatted

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,433 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/figures.py
"""This includes some demos of platypus for use in the API proposal"""
__version__=''' $Id$ '''
import os
from reportlab.lib import colors
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.utils import recursiveImport, strTypes
from reportlab.platypus import Frame
from reportlab.platypus import Flowable
from reportlab.platypus import Paragraph
from reportlab.lib.units import inch
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER
from reportlab.lib.validators import isColor
from reportlab.lib.colors import toColor
from reportlab.lib.styles import _baseFontName, _baseFontNameI
captionStyle = ParagraphStyle('Caption', fontName=_baseFontNameI, fontSize=10, alignment=TA_CENTER)
class Figure(Flowable):
def __init__(self, width, height, caption="",
captionFont=_baseFontNameI, captionSize=12,
background=None,
captionTextColor=toColor('black'),
captionBackColor=None,
border=None,
spaceBefore=12,
spaceAfter=12,
captionGap=None,
captionAlign='centre',
captionPosition='bottom',
hAlign='CENTER',
):
Flowable.__init__(self)
self.width = width
self.figureHeight = height
self.caption = caption
self.captionFont = captionFont
self.captionSize = captionSize
self.captionTextColor = captionTextColor
self.captionBackColor = captionBackColor
self.captionGap = captionGap or 0.5*captionSize
self.captionAlign = captionAlign
self.captionPosition = captionPosition
self._captionData = None
self.captionHeight = 0 # work out later
self.background = background
self.border = border
self.spaceBefore = spaceBefore
self.spaceAfter = spaceAfter
self.hAlign=hAlign
self._getCaptionPara() #Larry Meyn's fix - otherwise they all get the number of the last chapter.
def _getCaptionPara(self):
caption = self.caption
captionFont = self.captionFont
captionSize = self.captionSize
captionTextColor = self.captionTextColor
captionBackColor = self.captionBackColor
captionAlign = self.captionAlign
captionPosition = self.captionPosition
if self._captionData!=(caption,captionFont,captionSize,captionTextColor,captionBackColor,captionAlign,captionPosition):
self._captionData = (caption,captionFont,captionSize,captionTextColor,captionBackColor,captionAlign,captionPosition)
if isinstance(caption,Paragraph):
self.captionPara = caption
elif isinstance(caption,strTypes):
self.captionStyle = ParagraphStyle(
'Caption',
fontName=captionFont,
fontSize=captionSize,
leading=1.2*captionSize,
textColor = captionTextColor,
backColor = captionBackColor,
#seems to be getting ignored
spaceBefore=self.captionGap,
alignment=TA_LEFT if captionAlign=='left' else TA_RIGHT if captionAlign=='right' else TA_CENTER,
)
#must build paragraph now to get sequencing in synch with rest of story
self.captionPara = Paragraph(self.caption, self.captionStyle)
else:
raise ValueError('Figure caption of type %r is not a string or Paragraph' % type(caption))
def wrap(self, availWidth, availHeight):
# try to get the caption aligned
if self.caption:
self._getCaptionPara()
w, h = self.captionPara.wrap(self.width, availHeight - self.figureHeight)
self.captionHeight = h + self.captionGap
self.height = self.captionHeight + self.figureHeight
if w>self.width: self.width = w
else:
self.height = self.figureHeight
if self.hAlign in ('CENTER','CENTRE',TA_CENTER):
self.dx = 0.5 * (availWidth - self.width)
elif self.hAlign in ('RIGHT',TA_RIGHT):
self.dx = availWidth - self.width
else:
self.dx = 0
return (self.width, self.height)
def draw(self):
self.canv.translate(self.dx, 0)
if self.caption and self.captionPosition=='bottom':
self.canv.translate(0, self.captionHeight)
if self.background:
self.drawBackground()
if self.border:
self.drawBorder()
self.canv.saveState()
self.drawFigure()
self.canv.restoreState()
if self.caption:
if self.captionPosition=='bottom':
self.canv.translate(0, -self.captionHeight)
else:
self.canv.translate(0, self.figureHeight+self.captionGap)
self._getCaptionPara()
self.drawCaption()
def drawBorder(self):
canv = self.canv
border = self.border
bc = getattr(border,'color',None)
bw = getattr(border,'width',None)
bd = getattr(border,'dashArray',None)
ss = bc or bw or bd
if ss:
canv.saveState()
if bc: canv.setStrokeColor(bc)
if bw: canv.setLineWidth(bw)
if bd: canv.setDash(bd)
canv.rect(0, 0, self.width, self.figureHeight,fill=0,stroke=1)
if ss:
canv.restoreState()
def _doBackground(self, color):
self.canv.saveState()
self.canv.setFillColor(self.background)
self.canv.rect(0, 0, self.width, self.figureHeight, fill=1)
self.canv.restoreState()
def drawBackground(self):
"""For use when using a figure on a differently coloured background.
Allows you to specify a colour to be used as a background for the figure."""
if isColor(self.background):
self._doBackground(self.background)
else:
try:
c = toColor(self.background)
self._doBackground(c)
except:
pass
def drawCaption(self):
self.captionPara.drawOn(self.canv, 0, 0)
def drawFigure(self):
pass
def drawPage(canvas,x, y, width, height):
#draws something which looks like a page
pth = canvas.beginPath()
corner = 0.05*width
# shaded backdrop offset a little
canvas.setFillColorRGB(0.5,0.5,0.5)
canvas.rect(x + corner, y - corner, width, height, stroke=0, fill=1)
#'sheet of paper' in light yellow
canvas.setFillColorRGB(1,1,0.9)
canvas.setLineWidth(0)
canvas.rect(x, y, width, height, stroke=1, fill=1)
#reset
canvas.setFillColorRGB(0,0,0)
canvas.setStrokeColorRGB(0,0,0)
class PageFigure(Figure):
"""Shows a blank page in a frame, and draws on that. Used in
illustrations of how PLATYPUS works."""
def __init__(self, background=None):
Figure.__init__(self, 3*inch, 3*inch)
self.caption = 'Figure 1 - a blank page'
self.captionStyle = captionStyle
self.background = background
def drawVirtualPage(self):
pass
def drawFigure(self):
drawPage(self.canv, 0.625*inch, 0.25*inch, 1.75*inch, 2.5*inch)
self.canv.translate(0.625*inch, 0.25*inch)
self.canv.scale(1.75/8.27, 2.5/11.69)
self.drawVirtualPage()
class PlatPropFigure1(PageFigure):
"""This shows a page with a frame on it"""
def __init__(self):
PageFigure.__init__(self)
self.caption = "Figure 1 - a page with a simple frame"
def drawVirtualPage(self):
demo1(self.canv)
class FlexFigure(Figure):
"""Base for a figure class with a caption. Can grow or shrink in proportion"""
def __init__(self, width, height, caption, background=None,
captionFont='Helvetica-Oblique',captionSize=8,
captionTextColor=colors.black,
shrinkToFit=1,
growToFit=1,
spaceBefore=12,
spaceAfter=12,
captionGap=9,
captionAlign='centre',
captionPosition='top',
scaleFactor=None,
hAlign='CENTER',
border=1,
):
Figure.__init__(self, width, height, caption,
captionFont=captionFont,
captionSize=captionSize,
background=None,
captionTextColor=captionTextColor,
spaceBefore = spaceBefore,
spaceAfter = spaceAfter,
captionGap=captionGap,
captionAlign=captionAlign,
captionPosition=captionPosition,
hAlign=hAlign,
border=border,
)
self.shrinkToFit = shrinkToFit #if set and wrap is too tight, shrinks
self.growToFit = growToFit #if set and wrap is too small, grows
self.scaleFactor = scaleFactor
self._scaleFactor = None
self.background = background
def _scale(self,availWidth,availHeight):
"Rescale to fit according to the rules, but only once"
if self._scaleFactor is None or self.width>availWidth or self.height>availHeight:
w, h = Figure.wrap(self, availWidth, availHeight)
captionHeight = h - self.figureHeight
if self.scaleFactor is None:
#scale factor None means auto
self._scaleFactor = min(availWidth/self.width,(availHeight-captionHeight)/self.figureHeight)
else: #they provided a factor
self._scaleFactor = self.scaleFactor
if self._scaleFactor<1 and self.shrinkToFit:
self.width = self.width * self._scaleFactor - 0.0001
self.figureHeight = self.figureHeight * self._scaleFactor
elif self._scaleFactor>1 and self.growToFit:
self.width = self.width*self._scaleFactor - 0.0001
self.figureHeight = self.figureHeight * self._scaleFactor
def wrap(self, availWidth, availHeight):
self._scale(availWidth,availHeight)
return Figure.wrap(self, availWidth, availHeight)
def split(self, availWidth, availHeight):
self._scale(availWidth,availHeight)
return Figure.split(self, availWidth, availHeight)
class ImageFigure(FlexFigure):
"""Image with a caption below it"""
def __init__(self, filename, caption, background=None,scaleFactor=None,hAlign='CENTER',border=None):
assert os.path.isfile(filename), 'image file %s not found' % filename
from reportlab.lib.utils import ImageReader
w, h = ImageReader(filename).getSize()
self.filename = filename
FlexFigure.__init__(self, w, h, caption, background,scaleFactor=scaleFactor,hAlign=hAlign,border=border)
def drawFigure(self):
self.canv.drawImage(self.filename,
0, 0,self.width, self.figureHeight)
class DrawingFigure(FlexFigure):
"""Drawing with a caption below it. Clunky, scaling fails."""
def __init__(self, modulename, classname, caption, baseDir=None, background=None):
module = recursiveImport(modulename, baseDir)
klass = getattr(module, classname)
self.drawing = klass()
FlexFigure.__init__(self,
self.drawing.width,
self.drawing.height,
caption,
background)
self.growToFit = 1
def drawFigure(self):
self.canv.scale(self._scaleFactor, self._scaleFactor)
self.drawing.drawOn(self.canv, 0, 0)
try:
from rlextra.pageCatcher.pageCatcher import restoreForms, storeForms, storeFormsInMemory, restoreFormsInMemory
_hasPageCatcher = 1
except ImportError:
_hasPageCatcher = 0
if _hasPageCatcher:
####################################################################
#
# PageCatcher plugins
# These let you use our PageCatcher product to add figures
# to other documents easily.
####################################################################
class PageCatcherCachingMixIn:
"Helper functions to cache pages for figures"
def getFormName(self, pdfFileName, pageNo):
#naming scheme works within a directory only
dirname, filename = os.path.split(pdfFileName)
root, ext = os.path.splitext(filename)
return '%s_page%d' % (root, pageNo)
def needsProcessing(self, pdfFileName, pageNo):
"returns 1 if no forms or form is older"
formName = self.getFormName(pdfFileName, pageNo)
if os.path.exists(formName + '.frm'):
formModTime = os.stat(formName + '.frm')[8]
pdfModTime = os.stat(pdfFileName)[8]
return (pdfModTime > formModTime)
else:
return 1
def processPDF(self, pdfFileName, pageNo):
formName = self.getFormName(pdfFileName, pageNo)
storeForms(pdfFileName, formName + '.frm',
prefix= formName + '_',
pagenumbers=[pageNo])
#print 'stored %s.frm' % formName
return formName + '.frm'
class cachePageCatcherFigureNonA4(FlexFigure, PageCatcherCachingMixIn):
"""PageCatcher page with a caption below it. Size to be supplied."""
# This should merge with PageFigure into one class that reuses
# form information to determine the page orientation...
def __init__(self, filename, pageNo, caption, width, height, background=None):
self.dirname, self.filename = os.path.split(filename)
if self.dirname == '':
self.dirname = os.curdir
self.pageNo = pageNo
self.formName = self.getFormName(self.filename, self.pageNo) + '_' + str(pageNo)
FlexFigure.__init__(self, width, height, caption, background)
def drawFigure(self):
self.canv.saveState()
if not self.canv.hasForm(self.formName):
restorePath = self.dirname + os.sep + self.filename
#does the form file exist? if not, generate it.
formFileName = self.getFormName(restorePath, self.pageNo) + '.frm'
if self.needsProcessing(restorePath, self.pageNo):
#print 'preprocessing PDF %s page %s' % (restorePath, self.pageNo)
self.processPDF(restorePath, self.pageNo)
names = restoreForms(formFileName, self.canv)
self.canv.scale(self._scaleFactor, self._scaleFactor)
self.canv.doForm(self.formName)
self.canv.restoreState()
class cachePageCatcherFigure(cachePageCatcherFigureNonA4):
"""PageCatcher page with a caption below it. Presumes A4, Portrait.
This needs our commercial PageCatcher product, or you'll get a blank."""
def __init__(self, filename, pageNo, caption, width=595, height=842, background=None):
cachePageCatcherFigureNonA4.__init__(self, filename, pageNo, caption, width, height, background=background)
class PageCatcherFigureNonA4(FlexFigure):
"""PageCatcher page with a caption below it. Size to be supplied."""
# This should merge with PageFigure into one class that reuses
# form information to determine the page orientation...
_cache = {}
def __init__(self, filename, pageNo, caption, width, height, background=None, caching=None):
fn = self.filename = filename
self.pageNo = pageNo
fn = fn.replace(os.sep,'_').replace('/','_').replace('\\','_').replace('-','_').replace(':','_')
self.prefix = fn.replace('.','_')+'_'+str(pageNo)+'_'
self.formName = self.prefix + str(pageNo)
self.caching = caching
FlexFigure.__init__(self, width, height, caption, background)
def drawFigure(self):
if not self.canv.hasForm(self.formName):
if self.filename in self._cache:
f,data = self._cache[self.filename]
else:
f = open(self.filename,'rb')
pdf = f.read()
f.close()
f, data = storeFormsInMemory(pdf, pagenumbers=[self.pageNo], prefix=self.prefix)
if self.caching=='memory':
self._cache[self.filename] = f, data
f = restoreFormsInMemory(data, self.canv)
self.canv.saveState()
self.canv.scale(self._scaleFactor, self._scaleFactor)
self.canv.doForm(self.formName)
self.canv.restoreState()
class PageCatcherFigure(PageCatcherFigureNonA4):
"""PageCatcher page with a caption below it. Presumes A4, Portrait.
This needs our commercial PageCatcher product, or you'll get a blank."""
def __init__(self, filename, pageNo, caption, width=595, height=842, background=None, caching=None):
PageCatcherFigureNonA4.__init__(self, filename, pageNo, caption, width, height, background=background, caching=caching)
def demo1(canvas):
frame = Frame(
2*inch, # x
4*inch, # y at bottom
4*inch, # width
5*inch, # height
showBoundary = 1 # helps us see what's going on
)
bodyStyle = ParagraphStyle('Body', fontName=_baseFontName, fontSize=24, leading=28, spaceBefore=6)
para1 = Paragraph('Spam spam spam spam. ' * 5, bodyStyle)
para2 = Paragraph('Eggs eggs eggs. ' * 5, bodyStyle)
mydata = [para1, para2]
#this does the packing and drawing. The frame will consume
#items from the front of the list as it prints them
frame.addFromList(mydata,canvas)
def test1():
c = Canvas('figures.pdf')
f = Frame(inch, inch, 6*inch, 9*inch, showBoundary=1)
v = PlatPropFigure1()
v.captionTextColor = toColor('blue')
v.captionBackColor = toColor('lightyellow')
f.addFromList([v],c)
c.save()
if __name__ == '__main__':
test1()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,282 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/frames.py
__version__=''' $Id$ '''
__doc__="""A frame is a container for content on a page.
"""
import logging
logger = logging.getLogger('reportlab.platypus')
_geomAttr=('x1', 'y1', 'width', 'height', 'leftPadding', 'bottomPadding', 'rightPadding', 'topPadding')
from reportlab import rl_config, isPy3
_FUZZ=rl_config._FUZZ
class ShowBoundaryValue:
def __init__(self,color=(0,0,0),width=0.1):
self.color = color
self.width = width
if isPy3:
def __bool__(self):
return self.color is not None and self.width>=0
else:
def __nonzero__(self):
return self.color is not None and self.width>=0
class Frame:
'''
A Frame is a piece of space in a document that is filled by the
"flowables" in the story. For example in a book like document most
pages have the text paragraphs in one or two frames. For generality
a page might have several frames (for example for 3 column text or
for text that wraps around a graphic).
After creation a Frame is not usually manipulated directly by the
applications program -- it is used internally by the platypus modules.
Here is a diagramatid abstraction for the definitional part of a Frame::
width x2,y2
+---------------------------------+
| l top padding r | h
| e +-------------------------+ i | e
| f | | g | i
| t | | h | g
| | | t | h
| p | | | t
| a | | p |
| d | | a |
| | | d |
| +-------------------------+ |
| bottom padding |
+---------------------------------+
(x1,y1) <-- lower left corner
NOTE!! Frames are stateful objects. No single frame should be used in
two documents at the same time (especially in the presence of multithreading.
'''
def __init__(self, x1, y1, width,height, leftPadding=6, bottomPadding=6,
rightPadding=6, topPadding=6, id=None, showBoundary=0,
overlapAttachedSpace=None,_debug=None):
self.id = id
self._debug = _debug
#these say where it goes on the page
self.__dict__['_x1'] = x1
self.__dict__['_y1'] = y1
self.__dict__['_width'] = width
self.__dict__['_height'] = height
#these create some padding.
self.__dict__['_leftPadding'] = leftPadding
self.__dict__['_bottomPadding'] = bottomPadding
self.__dict__['_rightPadding'] = rightPadding
self.__dict__['_topPadding'] = topPadding
# if we want a boundary to be shown
self.showBoundary = showBoundary
if overlapAttachedSpace is None: overlapAttachedSpace = rl_config.overlapAttachedSpace
self._oASpace = overlapAttachedSpace
self._geom()
self._reset()
def __getattr__(self,a):
if a in _geomAttr: return self.__dict__['_'+a]
raise AttributeError(a)
def __setattr__(self,a,v):
if a in _geomAttr:
self.__dict__['_'+a] = v
self._geom()
else:
self.__dict__[a] = v
def _saveGeom(self, **kwds):
if not self.__dict__.setdefault('_savedGeom',{}):
for ga in _geomAttr:
ga = '_'+ga
self.__dict__['_savedGeom'][ga] = self.__dict__[ga]
for k,v in kwds.items():
setattr(self,k,v)
def _restoreGeom(self):
if self.__dict__.get('_savedGeom',None):
for ga in _geomAttr:
ga = '_'+ga
self.__dict__[ga] = self.__dict__[ga]['_savedGeom']
del self.__dict__['_savedGeom']
self._geom()
def _geom(self):
self._x2 = self._x1 + self._width
self._y2 = self._y1 + self._height
#efficiency
self._y1p = self._y1 + self._bottomPadding
#work out the available space
self._aW = self._x2 - self._x1 - self._leftPadding - self._rightPadding
self._aH = self._y2 - self._y1p - self._topPadding
def _reset(self):
self._restoreGeom()
#drawing starts at top left
self._x = self._x1 + self._leftPadding
self._y = self._y2 - self._topPadding
self._atTop = 1
self._prevASpace = 0
# these two should NOT be set on a frame.
# they are used when Indenter flowables want
# to adjust edges e.g. to do nested lists
self._leftExtraIndent = 0.0
self._rightExtraIndent = 0.0
def _getAvailableWidth(self):
return self._aW - self._leftExtraIndent - self._rightExtraIndent
def _add(self, flowable, canv, trySplit=0):
""" Draws the flowable at the current position.
Returns 1 if successful, 0 if it would not fit.
Raises a LayoutError if the object is too wide,
or if it is too high for a totally empty frame,
to avoid infinite loops"""
flowable._frame = self
flowable.canv = canv #so they can use stringWidth etc
try:
if getattr(flowable,'frameAction',None):
flowable.frameAction(self)
return 1
y = self._y
p = self._y1p
s = 0
aW = self._getAvailableWidth()
if not self._atTop:
s =flowable.getSpaceBefore()
if self._oASpace:
if getattr(flowable,'_SPACETRANSFER',False):
s = self._prevASpace
s = max(s-self._prevASpace,0)
h = y - p - s
if h>0:
w, h = flowable.wrap(aW, h)
else:
return 0
h += s
y -= h
if y < p-_FUZZ:
if not rl_config.allowTableBoundsErrors and ((h>self._aH or w>aW) and not trySplit):
from reportlab.platypus.doctemplate import LayoutError
raise LayoutError("Flowable %s (%sx%s points) too large for frame (%sx%s points)." % (
flowable.__class__, w,h, aW,self._aH))
return 0
else:
#now we can draw it, and update the current point.
s = flowable.getSpaceAfter()
fbg = getattr(self,'_frameBGs',None)
if fbg:
fbgl, fbgr, fbgc = fbg[-1]
fbw = self._width-fbgl-fbgr
fbh = y + h + s
fby = max(p,y-s)
fbh = max(0,fbh-fby)
if abs(fbw)>_FUZZ and abs(fbh)>_FUZZ:
canv.saveState()
canv.setFillColor(fbgc)
canv.rect(self._x1+fbgl,fby,fbw,fbh,stroke=0,fill=1)
canv.restoreState()
flowable.drawOn(canv, self._x + self._leftExtraIndent, y, _sW=aW-w)
flowable.canv=canv
if self._debug: logger.debug('drew %s' % flowable.identity())
y -= s
if self._oASpace:
if getattr(flowable,'_SPACETRANSFER',False):
s = self._prevASpace
self._prevASpace = s
if y!=self._y: self._atTop = 0
self._y = y
return 1
finally:
#sometimes canv/_frame aren't still on the flowable
for a in ('canv', '_frame'):
if hasattr(flowable,a):
delattr(flowable,a)
add = _add
def split(self,flowable,canv):
'''Ask the flowable to split using up the available space.'''
y = self._y
p = self._y1p
s = 0
if not self._atTop:
s = flowable.getSpaceBefore()
if self._oASpace:
s = max(s-self._prevASpace,0)
flowable._frame = self #some flowables might need these
flowable.canv = canv
try:
r = flowable.split(self._aW, y-p-s)
finally:
#sometimes canv/_frame aren't still on the flowable
for a in ('canv', '_frame'):
if hasattr(flowable,a):
delattr(flowable,a)
return r
def drawBoundary(self,canv):
"draw the frame boundary as a rectangle (primarily for debugging)."
from reportlab.lib.colors import Color, toColor
sb = self.showBoundary
ss = isinstance(sb,(str,tuple,list)) or isinstance(sb,Color)
w = -1
if ss:
c = toColor(sb,self)
ss = c is not self
elif isinstance(sb,ShowBoundaryValue) and sb:
c = toColor(sb.color,self)
w = sb.width
ss = c is not self
if ss:
canv.saveState()
canv.setStrokeColor(c)
if w>=0:
canv.setLineWidth(w)
canv.rect(
self._x1,
self._y1,
self._x2 - self._x1,
self._y2 - self._y1
)
if ss: canv.restoreState()
def addFromList(self, drawlist, canv):
"""Consumes objects from the front of the list until the
frame is full. If it cannot fit one object, raises
an exception."""
if self._debug: logger.debug("enter Frame.addFromlist() for frame %s" % self.id)
if self.showBoundary:
self.drawBoundary(canv)
while len(drawlist) > 0:
head = drawlist[0]
if self.add(head,canv,trySplit=0):
del drawlist[0]
else:
#leave it in the list for later
break
def add_generated_content(self,*C):
self.__dict__.setdefault('_generated_content',[]).extend(C)
def _aSpaceString(self):
return '(%s x %s%s)' % (self._getAvailableWidth(),self._aH,self._atTop and '*' or '')

2366
reportlab/platypus/para.py Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,553 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/tableofcontents.py
__version__=''' $Id$ '''
__doc__="""Experimental class to generate Tables of Contents easily
This module defines a single TableOfContents() class that can be used to
create automatically a table of tontents for Platypus documents like
this:
story = []
toc = TableOfContents()
story.append(toc)
# some heading paragraphs here...
doc = MyTemplate(path)
doc.multiBuild(story)
The data needed to create the table is a list of (level, text, pageNum)
triplets, plus some paragraph styles for each level of the table itself.
The triplets will usually be created in a document template's method
like afterFlowable(), making notification calls using the notify()
method with appropriate data like this:
(level, text, pageNum) = ...
self.notify('TOCEntry', (level, text, pageNum))
Optionally the list can contain four items in which case the last item
is a destination key which the entry should point to. A bookmark
with this key needs to be created first like this:
key = 'ch%s' % self.seq.nextf('chapter')
self.canv.bookmarkPage(key)
self.notify('TOCEntry', (level, text, pageNum, key))
As the table of contents need at least two passes over the Platypus
story which is why the moultiBuild0() method must be called.
The level<NUMBER>ParaStyle variables are the paragraph styles used
to format the entries in the table of contents. Their indentation
is calculated like this: each entry starts at a multiple of some
constant named delta. If one entry spans more than one line, all
lines after the first are indented by the same constant named
epsilon.
"""
from reportlab.lib import enums
from reportlab.lib.units import cm
from reportlab.lib.utils import commasplit, escapeOnce, encode_label, decode_label, strTypes
from reportlab.lib.styles import ParagraphStyle, _baseFontName
from reportlab.platypus.paragraph import Paragraph
from reportlab.platypus.doctemplate import IndexingFlowable
from reportlab.platypus.tables import TableStyle, Table
from reportlab.platypus.flowables import Spacer, Flowable
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.pdfgen import canvas
def unquote(txt):
from xml.sax.saxutils import unescape
return unescape(txt, {"&apos;": "'", "&quot;": '"'})
try:
set
except:
class set(list):
def add(self,x):
if x not in self:
list.append(self,x)
def drawPageNumbers(canvas, style, pages, availWidth, availHeight, dot=' . '):
'''
Draws pagestr on the canvas using the given style.
If dot is None, pagestr is drawn at the current position in the canvas.
If dot is a string, pagestr is drawn right-aligned. If the string is not empty,
the gap is filled with it.
'''
pagestr = ', '.join([str(p) for p, _ in pages])
x, y = canvas._curr_tx_info['cur_x'], canvas._curr_tx_info['cur_y']
fontSize = style.fontSize
pagestrw = stringWidth(pagestr, style.fontName, fontSize)
#if it's too long to fit, we need to shrink to fit in 10% increments.
#it would be very hard to output multiline entries.
#however, we impose a minimum size of 1 point as we don't want an
#infinite loop. Ultimately we should allow a TOC entry to spill
#over onto a second line if needed.
freeWidth = availWidth-x
while pagestrw > freeWidth and fontSize >= 1.0:
fontSize = 0.9 * fontSize
pagestrw = stringWidth(pagestr, style.fontName, fontSize)
if isinstance(dot, strTypes):
if dot:
dotw = stringWidth(dot, style.fontName, fontSize)
dotsn = int((availWidth-x-pagestrw)/dotw)
else:
dotsn = dotw = 0
text = '%s%s' % (dotsn * dot, pagestr)
newx = availWidth - dotsn*dotw - pagestrw
pagex = availWidth - pagestrw
elif dot is None:
text = ', ' + pagestr
newx = x
pagex = newx
else:
raise TypeError('Argument dot should either be None or an instance of basestring.')
tx = canvas.beginText(newx, y)
tx.setFont(style.fontName, fontSize)
tx.setFillColor(style.textColor)
tx.textLine(text)
canvas.drawText(tx)
commaw = stringWidth(', ', style.fontName, fontSize)
for p, key in pages:
if not key:
continue
w = stringWidth(str(p), style.fontName, fontSize)
canvas.linkRect('', key, (pagex, y, pagex+w, y+style.leading), relative=1)
pagex += w + commaw
# Default paragraph styles for tables of contents.
# (This could also be generated automatically or even
# on-demand if it is not known how many levels the
# TOC will finally need to display...)
delta = 1*cm
epsilon = 0.5*cm
defaultLevelStyles = [
ParagraphStyle(
name='Level 0',
fontName=_baseFontName,
fontSize=10,
leading=11,
firstLineIndent = 0,
leftIndent = epsilon)]
defaultTableStyle = \
TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('RIGHTPADDING', (0,0), (-1,-1), 0),
('LEFTPADDING', (0,0), (-1,-1), 0),
])
class TableOfContents(IndexingFlowable):
"""This creates a formatted table of contents.
It presumes a correct block of data is passed in.
The data block contains a list of (level, text, pageNumber)
triplets. You can supply a paragraph style for each level
(starting at zero).
Set dotsMinLevel to determine from which level on a line of
dots should be drawn between the text and the page number.
If dotsMinLevel is set to a negative value, no dotted lines are drawn.
"""
def __init__(self):
self.rightColumnWidth = 72
self.levelStyles = defaultLevelStyles
self.tableStyle = defaultTableStyle
self.dotsMinLevel = 1
self._table = None
self._entries = []
self._lastEntries = []
def beforeBuild(self):
# keep track of the last run
self._lastEntries = self._entries[:]
self.clearEntries()
def isIndexing(self):
return 1
def isSatisfied(self):
return (self._entries == self._lastEntries)
def notify(self, kind, stuff):
"""The notification hook called to register all kinds of events.
Here we are interested in 'TOCEntry' events only.
"""
if kind == 'TOCEntry':
self.addEntry(*stuff)
def clearEntries(self):
self._entries = []
def getLevelStyle(self, n):
'''Returns the style for level n, generating and caching styles on demand if not present.'''
try:
return self.levelStyles[n]
except IndexError:
prevstyle = self.getLevelStyle(n-1)
self.levelStyles.append(ParagraphStyle(
name='%s-%d-indented' % (prevstyle.name, n),
parent=prevstyle,
firstLineIndent = prevstyle.firstLineIndent+delta,
leftIndent = prevstyle.leftIndent+delta))
return self.levelStyles[n]
def addEntry(self, level, text, pageNum, key=None):
"""Adds one entry to the table of contents.
This allows incremental buildup by a doctemplate.
Requires that enough styles are defined."""
assert type(level) == type(1), "Level must be an integer"
self._entries.append((level, text, pageNum, key))
def addEntries(self, listOfEntries):
"""Bulk creation of entries in the table of contents.
If you knew the titles but not the page numbers, you could
supply them to get sensible output on the first run."""
for entryargs in listOfEntries:
self.addEntry(*entryargs)
def wrap(self, availWidth, availHeight):
"All table properties should be known by now."
# makes an internal table which does all the work.
# we draw the LAST RUN's entries! If there are
# none, we make some dummy data to keep the table
# from complaining
if len(self._lastEntries) == 0:
_tempEntries = [(0,'Placeholder for table of contents',0,None)]
else:
_tempEntries = self._lastEntries
def drawTOCEntryEnd(canvas, kind, label):
'''Callback to draw dots and page numbers after each entry.'''
label = label.split(',')
page, level, key = int(label[0]), int(label[1]), eval(label[2],{})
style = self.getLevelStyle(level)
if self.dotsMinLevel >= 0 and level >= self.dotsMinLevel:
dot = ' . '
else:
dot = ''
drawPageNumbers(canvas, style, [(page, key)], availWidth, availHeight, dot)
self.canv.drawTOCEntryEnd = drawTOCEntryEnd
tableData = []
for (level, text, pageNum, key) in _tempEntries:
style = self.getLevelStyle(level)
if key:
text = '<a href="#%s">%s</a>' % (key, text)
keyVal = repr(key).replace(',','\\x2c').replace('"','\\x2c')
else:
keyVal = None
para = Paragraph('%s<onDraw name="drawTOCEntryEnd" label="%d,%d,%s"/>' % (text, pageNum, level, keyVal), style)
if style.spaceBefore:
tableData.append([Spacer(1, style.spaceBefore),])
tableData.append([para,])
self._table = Table(tableData, colWidths=(availWidth,), style=self.tableStyle)
self.width, self.height = self._table.wrapOn(self.canv,availWidth, availHeight)
return (self.width, self.height)
def split(self, availWidth, availHeight):
"""At this stage we do not care about splitting the entries,
we will just return a list of platypus tables. Presumably the
calling app has a pointer to the original TableOfContents object;
Platypus just sees tables.
"""
return self._table.splitOn(self.canv,availWidth, availHeight)
def drawOn(self, canvas, x, y, _sW=0):
"""Don't do this at home! The standard calls for implementing
draw(); we are hooking this in order to delegate ALL the drawing
work to the embedded table object.
"""
self._table.drawOn(canvas, x, y, _sW)
def makeTuple(x):
if hasattr(x, '__iter__'):
return tuple(x)
return (x,)
class SimpleIndex(IndexingFlowable):
"""Creates multi level indexes.
The styling can be cutomized and alphabetic headers turned on and off.
"""
def __init__(self, **kwargs):
"""
Constructor of SimpleIndex.
Accepts the same arguments as the setup method.
"""
#keep stuff in a dictionary while building
self._entries = {}
self._lastEntries = {}
self._flowable = None
self.setup(**kwargs)
def getFormatFunc(self,format):
try:
D = {}
exec('from reportlab.lib.sequencer import _format_%s as formatFunc' % format, D)
return D['formatFunc']
except ImportError:
raise ValueError('Unknown format %r' % format)
def setup(self, style=None, dot=None, tableStyle=None, headers=True, name=None, format='123', offset=0):
"""
This method makes it possible to change styling and other parameters on an existing object.
style is the paragraph style to use for index entries.
dot can either be None or a string. If it's None, entries are immediatly followed by their
corresponding page numbers. If it's a string, page numbers are aligned on the right side
of the document and the gap filled with a repeating sequence of the string.
tableStyle is the style used by the table which the index uses to draw itself. Use this to
change properties like spacing between elements.
headers is a boolean. If it is True, alphabetic headers are displayed in the Index when the first
letter changes. If False, we just output some extra space before the next item
name makes it possible to use several indexes in one document. If you want this use this
parameter to give each index a unique name. You can then index a term by refering to the
name of the index which it should appear in:
<index item="term" name="myindex" />
format can be 'I', 'i', '123', 'ABC', 'abc'
"""
if style is None:
style = ParagraphStyle(name='index',
fontName=_baseFontName,
fontSize=11)
self.textStyle = style
self.tableStyle = tableStyle or defaultTableStyle
self.dot = dot
self.headers = headers
if name is None:
from reportlab.platypus.paraparser import DEFAULT_INDEX_NAME as name
self.name = name
self.formatFunc = self.getFormatFunc(format)
self.offset = offset
def __call__(self,canv,kind,label):
try:
terms, format, offset = decode_label(label)
except:
terms = label
format = offset = None
if format is None:
formatFunc = self.formatFunc
else:
formatFunc = self.getFormatFunc(format)
if offset is None:
offset = self.offset
terms = commasplit(terms)
cPN = canv.getPageNumber()
pns = formatFunc(cPN-offset)
key = 'ix_%s_%s_p_%s' % (self.name, label, pns)
info = canv._curr_tx_info
canv.bookmarkHorizontal(key, info['cur_x'], info['cur_y'] + info['leading'])
self.addEntry(terms, (cPN,pns), key)
def getCanvasMaker(self, canvasmaker=canvas.Canvas):
def newcanvasmaker(*args, **kwargs):
from reportlab.pdfgen import canvas
c = canvasmaker(*args, **kwargs)
setattr(c,self.name,self)
return c
return newcanvasmaker
def isIndexing(self):
return 1
def isSatisfied(self):
return (self._entries == self._lastEntries)
def beforeBuild(self):
# keep track of the last run
self._lastEntries = self._entries.copy()
self.clearEntries()
def clearEntries(self):
self._entries = {}
def notify(self, kind, stuff):
"""The notification hook called to register all kinds of events.
Here we are interested in 'IndexEntry' events only.
"""
if kind == 'IndexEntry':
text, pageNum = stuff
self.addEntry(text, (self._canv.getPageNumber(),pageNum))
def addEntry(self, text, pageNum, key=None):
"""Allows incremental buildup"""
self._entries.setdefault(makeTuple(text),set([])).add((pageNum, key))
def split(self, availWidth, availHeight):
"""At this stage we do not care about splitting the entries,
we will just return a list of platypus tables. Presumably the
calling app has a pointer to the original TableOfContents object;
Platypus just sees tables.
"""
return self._flowable.splitOn(self.canv,availWidth, availHeight)
def _getlastEntries(self, dummy=[(['Placeholder for index'],enumerate((None,)*3))]):
'''Return the last run's entries! If there are none, returns dummy.'''
if not self._lastEntries:
if self._entries:
return list(self._entries.items())
return dummy
return list(self._lastEntries.items())
def _build(self,availWidth,availHeight):
_tempEntries = self._getlastEntries()
def getkey(seq):
return [x.upper() for x in seq[0]]
_tempEntries.sort(key=getkey)
leveloffset = self.headers and 1 or 0
def drawIndexEntryEnd(canvas, kind, label):
'''Callback to draw dots and page numbers after each entry.'''
style = self.getLevelStyle(leveloffset)
pages = [(p[1],k) for p,k in sorted(decode_label(label))]
drawPageNumbers(canvas, style, pages, availWidth, availHeight, self.dot)
self.canv.drawIndexEntryEnd = drawIndexEntryEnd
alpha = ''
tableData = []
lastTexts = []
alphaStyle = self.getLevelStyle(0)
for texts, pageNumbers in _tempEntries:
texts = list(texts)
#track when the first character changes; either output some extra
#space, or the first letter on a row of its own. We cannot do
#widow/orphan control, sadly.
nalpha = texts[0][0].upper()
if alpha != nalpha:
alpha = nalpha
if self.headers:
header = alpha
else:
header = ' '
tableData.append([Spacer(1, alphaStyle.spaceBefore),])
tableData.append([Paragraph(header, alphaStyle),])
tableData.append([Spacer(1, alphaStyle.spaceAfter),])
i, diff = listdiff(lastTexts, texts)
if diff:
lastTexts = texts
texts = texts[i:]
label = encode_label(list(pageNumbers))
texts[-1] = '%s<onDraw name="drawIndexEntryEnd" label="%s"/>' % (texts[-1], label)
for text in texts:
#Platypus and RML differ on how parsed XML attributes are escaped.
#e.g. <index item="M&S"/>. The only place this seems to bite us is in
#the index entries so work around it here.
text = escapeOnce(text)
style = self.getLevelStyle(i+leveloffset)
para = Paragraph(text, style)
if style.spaceBefore:
tableData.append([Spacer(1, style.spaceBefore),])
tableData.append([para,])
i += 1
self._flowable = Table(tableData, colWidths=[availWidth], style=self.tableStyle)
def wrap(self, availWidth, availHeight):
"All table properties should be known by now."
self._build(availWidth,availHeight)
self.width, self.height = self._flowable.wrapOn(self.canv,availWidth, availHeight)
return self.width, self.height
def drawOn(self, canvas, x, y, _sW=0):
"""Don't do this at home! The standard calls for implementing
draw(); we are hooking this in order to delegate ALL the drawing
work to the embedded table object.
"""
self._flowable.drawOn(canvas, x, y, _sW)
def draw(self):
t = self._flowable
ocanv = getattr(t,'canv',None)
if not ocanv:
t.canv = self.canv
try:
t.draw()
finally:
if not ocanv:
del t.canv
def getLevelStyle(self, n):
'''Returns the style for level n, generating and caching styles on demand if not present.'''
if not hasattr(self.textStyle, '__iter__'):
self.textStyle = [self.textStyle]
try:
return self.textStyle[n]
except IndexError:
self.textStyle = list(self.textStyle)
prevstyle = self.getLevelStyle(n-1)
self.textStyle.append(ParagraphStyle(
name='%s-%d-indented' % (prevstyle.name, n),
parent=prevstyle,
firstLineIndent = prevstyle.firstLineIndent+.2*cm,
leftIndent = prevstyle.leftIndent+.2*cm))
return self.textStyle[n]
AlphabeticIndex = SimpleIndex
def listdiff(l1, l2):
m = min(len(l1), len(l2))
for i in range(m):
if l1[i] != l2[i]:
return i, l2[i:]
return m, l2[m:]
class ReferenceText(IndexingFlowable):
"""Fakery to illustrate how a reference would work if we could
put it in a paragraph."""
def __init__(self, textPattern, targetKey):
self.textPattern = textPattern
self.target = targetKey
self.paraStyle = ParagraphStyle('tmp')
self._lastPageNum = None
self._pageNum = -999
self._para = None
def beforeBuild(self):
self._lastPageNum = self._pageNum
def notify(self, kind, stuff):
if kind == 'Target':
(key, pageNum) = stuff
if key == self.target:
self._pageNum = pageNum
def wrap(self, availWidth, availHeight):
text = self.textPattern % self._lastPageNum
self._para = Paragraph(text, self.paraStyle)
return self._para.wrap(availWidth, availHeight)
def drawOn(self, canvas, x, y, _sW=0):
self._para.drawOn(canvas, x, y, _sW)

1612
reportlab/platypus/tables.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,338 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/xpreformatted.py
__version__=''' $Id$ '''
__doc__='''A 'rich preformatted text' widget allowing internal markup'''
from reportlab.lib import PyFontify
from reportlab.platypus.paragraph import Paragraph, cleanBlockQuotedText, _handleBulletWidth, \
ParaLines, _getFragWords, stringWidth, getAscentDescent, imgVRange, imgNormV
from reportlab.lib.utils import isSeq
from reportlab.platypus.flowables import _dedenter
def _getFragLines(frags):
lines = []
cline = []
W = frags[:]
while W != []:
w = W[0]
t = w.text
del W[0]
i = t.find('\n')
if i>=0:
tleft = t[i+1:]
cline.append(w.clone(text=t[:i]))
lines.append(cline)
cline = []
if tleft!='':
W.insert(0,w.clone(text=tleft))
else:
cline.append(w)
if cline!=[]:
lines.append(cline)
return lines
def _split_blPara(blPara,start,stop):
f = blPara.clone()
for a in ('lines', 'text'):
if hasattr(f,a): delattr(f,a)
f.lines = blPara.lines[start:stop]
return [f]
# Will be removed shortly.
def _countSpaces(text):
return text.count(' ')
## i = 0
## s = 0
## while 1:
## j = text.find(' ',i)
## if j<0: return s
## s = s + 1
## i = j + 1
def _getFragWord(frags,maxWidth):
''' given a fragment list return a list of lists
[size, spaces, (f00,w00), ..., (f0n,w0n)]
each pair f,w represents a style and some string
'''
W = []
n = 0
s = 0
for f in frags:
text = f.text[:]
W.append((f,text))
cb = getattr(f,'cbDefn',None)
if cb:
_w = getattr(cb,'width',0)
if hasattr(_w,'normalizedValue'):
_w._normalizer = maxWidth
n = n + stringWidth(text, f.fontName, f.fontSize)
#s = s + _countSpaces(text)
s = s + text.count(' ') # much faster for many blanks
#del f.text # we can't do this until we sort out splitting
# of paragraphs
return n, s, W
class XPreformatted(Paragraph):
def __init__(self, text, style, bulletText = None, frags=None, caseSensitive=1, dedent=0):
self.caseSensitive = caseSensitive
cleaner = lambda text, dedent=dedent: '\n'.join(_dedenter(text or '',dedent))
self._setup(text, style, bulletText, frags, cleaner)
def breakLines(self, width):
"""
Returns a broken line structure. There are two cases
A) For the simple case of a single formatting input fragment the output is
A fragment specifier with
- kind = 0
- fontName, fontSize, leading, textColor
- lines= A list of lines
Each line has two items:
1. unused width in points
2. a list of words
B) When there is more than one input formatting fragment the out put is
A fragment specifier with
- kind = 1
- lines = A list of fragments each having fields:
- extraspace (needed for justified)
- fontSize
- words=word list
- each word is itself a fragment with
- various settings
This structure can be used to easily draw paragraphs with the various alignments.
You can supply either a single width or a list of widths; the latter will have its
last item repeated until necessary. A 2-element list is useful when there is a
different first line indent; a longer list could be created to facilitate custom wraps
around irregular objects."""
if not isSeq(width): maxWidths = [width]
else: maxWidths = width
lines = []
lineno = 0
maxWidth = maxWidths[lineno]
style = self.style
fFontSize = float(style.fontSize)
requiredWidth = 0
#for bullets, work out width and ensure we wrap the right amount onto line one
_handleBulletWidth(self.bulletText,style,maxWidths)
self.height = 0
autoLeading = getattr(self,'autoLeading',getattr(style,'autoLeading',''))
calcBounds = autoLeading not in ('','off')
frags = self.frags
nFrags= len(frags)
if nFrags==1:
f = frags[0]
if hasattr(f,'text'):
fontSize = f.fontSize
fontName = f.fontName
ascent, descent = getAscentDescent(fontName,fontSize)
kind = 0
L=f.text.split('\n')
for l in L:
currentWidth = stringWidth(l,fontName,fontSize)
requiredWidth = max(currentWidth,requiredWidth)
extraSpace = maxWidth-currentWidth
lines.append((extraSpace,l.split(' '),currentWidth))
lineno = lineno+1
maxWidth = lineno<len(maxWidths) and maxWidths[lineno] or maxWidths[-1]
blPara = f.clone(kind=kind, lines=lines,ascent=ascent,descent=descent,fontSize=fontSize)
else:
kind = f.kind
lines = f.lines
for L in lines:
if kind==0:
currentWidth = L[2]
else:
currentWidth = L.currentWidth
requiredWidth = max(currentWidth,requiredWidth)
blPara = f.clone(kind=kind, lines=lines)
self.width = max(self.width,requiredWidth)
return blPara
elif nFrags<=0:
return ParaLines(kind=0, fontSize=style.fontSize, fontName=style.fontName,
textColor=style.textColor, ascent=style.fontSize,descent=-0.2*style.fontSize,
lines=[])
else:
for L in _getFragLines(frags):
currentWidth, n, w = _getFragWord(L,maxWidth)
f = w[0][0]
maxSize = f.fontSize
maxAscent, minDescent = getAscentDescent(f.fontName,maxSize)
words = [f.clone()]
words[-1].text = w[0][1]
for i in w[1:]:
f = i[0].clone()
f.text=i[1]
words.append(f)
fontSize = f.fontSize
fontName = f.fontName
if calcBounds:
cbDefn = getattr(f,'cbDefn',None)
if getattr(cbDefn,'width',0):
descent,ascent = imgVRange(imgNormV(cbDefn.height,fontSize),cbDefn.valign,fontSize)
else:
ascent, descent = getAscentDescent(fontName,fontSize)
else:
ascent, descent = getAscentDescent(fontName,fontSize)
maxSize = max(maxSize,fontSize)
maxAscent = max(maxAscent,ascent)
minDescent = min(minDescent,descent)
lineno += 1
maxWidth = lineno<len(maxWidths) and maxWidths[lineno] or maxWidths[-1]
requiredWidth = max(currentWidth,requiredWidth)
extraSpace = maxWidth - currentWidth
lines.append(ParaLines(extraSpace=extraSpace,wordCount=n, words=words, fontSize=maxSize, ascent=maxAscent,descent=minDescent,currentWidth=currentWidth))
self.width = max(self.width,requiredWidth)
return ParaLines(kind=1, lines=lines)
return lines
breakLinesCJK = breakLines #TODO fixme fixme fixme
# we need this her to get the right splitter
def _get_split_blParaFunc(self):
return _split_blPara
class PythonPreformatted(XPreformatted):
"""Used for syntax-colored Python code, otherwise like XPreformatted.
"""
formats = {
'rest' : ('', ''),
'comment' : ('<font color="green">', '</font>'),
'keyword' : ('<font color="blue"><b>', '</b></font>'),
'parameter' : ('<font color="black">', '</font>'),
'identifier' : ('<font color="red">', '</font>'),
'string' : ('<font color="gray">', '</font>') }
def __init__(self, text, style, bulletText = None, dedent=0, frags=None):
if text:
text = self.fontify(self.escapeHtml(text))
XPreformatted.__init__(self, text, style,bulletText=bulletText,dedent=dedent,frags=frags)
def escapeHtml(self, text):
s = text.replace('&', '&amp;')
s = s.replace('<', '&lt;')
s = s.replace('>', '&gt;')
return s
def fontify(self, code):
"Return a fontified version of some Python code."
if code[0] == '\n':
code = code[1:]
tags = PyFontify.fontify(code)
fontifiedCode = ''
pos = 0
for k, i, j, dummy in tags:
fontifiedCode = fontifiedCode + code[pos:i]
s, e = self.formats[k]
fontifiedCode = fontifiedCode + s + code[i:j] + e
pos = j
fontifiedCode = fontifiedCode + code[pos:]
return fontifiedCode
if __name__=='__main__': #NORUNTESTS
import sys
def dumpXPreformattedLines(P):
print('\n############dumpXPreforemattedLines(%s)' % str(P))
lines = P.blPara.lines
n =len(lines)
outw=sys.stdout.write
for l in range(n):
line = lines[l]
words = line.words
nwords = len(words)
outw('line%d: %d(%d)\n ' % (l,nwords,line.wordCount))
for w in range(nwords):
outw(" %d:'%s'"%(w,words[w].text))
print()
def dumpXPreformattedFrags(P):
print('\n############dumpXPreforemattedFrags(%s)' % str(P))
frags = P.frags
n =len(frags)
for l in range(n):
print("frag%d: '%s'" % (l, frags[l].text))
outw=sys.stdout.write
l = 0
for L in _getFragLines(frags):
n=0
for W in _getFragWords(L,360):
outw("frag%d.%d: size=%d" % (l, n, W[0]))
n = n + 1
for w in W[1:]:
outw(" '%s'" % w[1])
print()
l = l + 1
def try_it(text,style,dedent,aW,aH):
P=XPreformatted(text,style,dedent=dedent)
dumpXPreformattedFrags(P)
w,h = P.wrap(aW, aH)
dumpXPreformattedLines(P)
S = P.split(aW,aH)
dumpXPreformattedLines(P)
for s in S:
s.wrap(aW,aH)
dumpXPreformattedLines(s)
aH = 500
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
styleSheet = getSampleStyleSheet()
B = styleSheet['BodyText']
DTstyle = ParagraphStyle("discussiontext", parent=B)
DTstyle.fontName= 'Helvetica'
for (text,dedent,style, aW, aH, active) in [('''
The <font name=courier color=green>CMYK</font> or subtractive
method follows the way a printer
mixes three pigments (cyan, magenta, and yellow) to form colors.
Because mixing chemicals is more difficult than combining light there
is a fourth parameter for darkness. For example a chemical
combination of the <font name=courier color=green>CMY</font> pigments generally never makes a perfect
black -- instead producing a muddy color -- so, to get black printers
don't use the <font name=courier color=green>CMY</font> pigments but use a direct black ink. Because
<font name=courier color=green>CMYK</font> maps more directly to the way printer hardware works it may
be the case that &amp;| &amp; | colors specified in <font name=courier color=green>CMYK</font> will provide better fidelity
and better control when printed.
''',0,DTstyle, 456.0, 42.8, 0),
('''
This is a non rearranging form of the <b>Paragraph</b> class;
<b><font color=red>XML</font></b> tags are allowed in <i>text</i> and have the same
meanings as for the <b>Paragraph</b> class.
As for <b>Preformatted</b>, if dedent is non zero <font color=red size=+1>dedent</font>
common leading spaces will be removed from the
front of each line.
''',3, DTstyle, 456.0, 42.8, 0),
("""\
<font color=blue>class </font><font color=red>FastXMLParser</font>:
# Nonsense method
def nonsense(self):
self.foo = 'bar'
""",0, styleSheet['Code'], 456.0, 4.8, 1),
]:
if active: try_it(text,style,dedent,aW,aH)