mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-07 23:49:42 +00:00
Added printing requirements
This commit is contained in:
17
reportlab/platypus/__init__.py
Normal file
17
reportlab/platypus/__init__.py
Normal 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
|
||||
1250
reportlab/platypus/doctemplate.py
Normal file
1250
reportlab/platypus/doctemplate.py
Normal file
File diff suppressed because it is too large
Load Diff
433
reportlab/platypus/figures.py
Normal file
433
reportlab/platypus/figures.py
Normal 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()
|
||||
1979
reportlab/platypus/flowables.py
Normal file
1979
reportlab/platypus/flowables.py
Normal file
File diff suppressed because it is too large
Load Diff
282
reportlab/platypus/frames.py
Normal file
282
reportlab/platypus/frames.py
Normal 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
2366
reportlab/platypus/para.py
Normal file
File diff suppressed because it is too large
Load Diff
1957
reportlab/platypus/paragraph.py
Normal file
1957
reportlab/platypus/paragraph.py
Normal file
File diff suppressed because it is too large
Load Diff
1349
reportlab/platypus/paraparser.py
Normal file
1349
reportlab/platypus/paraparser.py
Normal file
File diff suppressed because it is too large
Load Diff
553
reportlab/platypus/tableofcontents.py
Normal file
553
reportlab/platypus/tableofcontents.py
Normal 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, {"'": "'", """: '"'})
|
||||
|
||||
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
1612
reportlab/platypus/tables.py
Normal file
File diff suppressed because it is too large
Load Diff
338
reportlab/platypus/xpreformatted.py
Normal file
338
reportlab/platypus/xpreformatted.py
Normal 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('&', '&')
|
||||
s = s.replace('<', '<')
|
||||
s = s.replace('>', '>')
|
||||
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 &| & | 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)
|
||||
Reference in New Issue
Block a user