Initial commit: IT Site Survey AI v2 with all features
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
#Copyright ReportLab Europe Ltd. 2000-2017
|
||||
#see license.txt for license details
|
||||
#history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/platypus/__init__.py
|
||||
__version__='3.5.20'
|
||||
__doc__='''Page Layout and Typography Using Scripts" - higher-level framework for flowing documents'''
|
||||
|
||||
from .flowables import *
|
||||
from .frames import *
|
||||
from .multicol import *
|
||||
from .paragraph import *
|
||||
from .paraparser import *
|
||||
from .tables import *
|
||||
from .doctemplate import *
|
||||
from .xpreformatted import *
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,420 @@
|
||||
#Copyright ReportLab Europe Ltd. 2000-2017
|
||||
#see license.txt for license details
|
||||
#history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/platypus/figures.py
|
||||
"""This includes some demos of platypus for use in the API proposal"""
|
||||
__version__='3.3.0'
|
||||
|
||||
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):
|
||||
self.canv.drawBoundary(self.border,0,0,self.width, self.figureHeight)
|
||||
|
||||
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
@@ -0,0 +1,268 @@
|
||||
#Copyright ReportLab Europe Ltd. 2000-2017
|
||||
#see license.txt for license details
|
||||
#history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/platypus/frames.py
|
||||
|
||||
__version__='3.5.14'
|
||||
|
||||
__doc__="""A frame is a container for content on a page.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
'Frame',
|
||||
)
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('reportlab.platypus')
|
||||
|
||||
_geomAttr=('x1', 'y1', 'width', 'height', 'leftPadding', 'bottomPadding', 'rightPadding', 'topPadding')
|
||||
from reportlab import rl_config
|
||||
_FUZZ=rl_config._FUZZ
|
||||
|
||||
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()
|
||||
zeroSize = getattr(flowable,'_ZEROSIZE',False)
|
||||
if not self._atTop:
|
||||
s =flowable.getSpaceBefore()
|
||||
if self._oASpace:
|
||||
if getattr(flowable,'_SPACETRANSFER',False) or zeroSize:
|
||||
s = self._prevASpace
|
||||
s = max(s-self._prevASpace,0)
|
||||
h = y - p - s
|
||||
if h>0 or zeroSize:
|
||||
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.
|
||||
sa = flowable.getSpaceAfter()
|
||||
fbg = getattr(self,'_frameBGs',None)
|
||||
if fbg and fbg[-1].active:
|
||||
bg = fbg[-1]
|
||||
fbgl = bg.left
|
||||
fbgr = bg.right
|
||||
bgm = bg.start
|
||||
fbw = self._width-fbgl-fbgr
|
||||
fbx = self._x1+fbgl
|
||||
if not bgm:
|
||||
fbh = y + h + sa
|
||||
fby = max(p,y-sa)
|
||||
fbh = max(0,fbh-fby)
|
||||
else:
|
||||
fbh = y + h - s
|
||||
att = fbh>=self._y2 - self._topPadding
|
||||
if bgm=='frame' or bgm=='frame-permanent' or (att and bgm=='frame-permanent-1'):
|
||||
#first time or att top use
|
||||
fbh = max(0,(self._y2 if att else fbh)-self._y1)
|
||||
fby = self._y1
|
||||
if bgm=='frame-permanent':
|
||||
fbg[-1].start = 'frame-permanent-1'
|
||||
else:
|
||||
fby = fbw = fbh = 0
|
||||
bg.render(canv,self,fbx,fby,fbw,fbh)
|
||||
if bgm=='frame':
|
||||
fbg.pop()
|
||||
|
||||
flowable.drawOn(canv, self._x + self._leftExtraIndent, y, _sW=aW-w)
|
||||
flowable.canv=canv
|
||||
if self._debug: logger.debug('drew %s' % flowable.identity())
|
||||
y -= sa
|
||||
if self._oASpace:
|
||||
if getattr(flowable,'_SPACETRANSFER',False):
|
||||
sa = self._prevASpace
|
||||
self._prevASpace = sa
|
||||
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)
|
||||
h = y-p-s
|
||||
if h<=0 and not getattr(flowable,'_ZEROSIZE',False):
|
||||
return []
|
||||
flowable._frame = self #some flowables might need these
|
||||
flowable.canv = canv
|
||||
try:
|
||||
r = flowable.split(self._aW, h)
|
||||
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, __boundary__=None):
|
||||
canv.drawBoundary(__boundary__ or self.showBoundary, self._x1, self._y1,
|
||||
self._x2 - self._x1, self._y2 - self._y1)
|
||||
|
||||
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 '')
|
||||
@@ -0,0 +1,111 @@
|
||||
__all__ = '''MultiCol'''.split()
|
||||
from reportlab.lib.utils import strTypes
|
||||
from .flowables import Flowable, _Container, _FindSplitterMixin, _listWrapOn
|
||||
|
||||
class MultiCol(_Container,_FindSplitterMixin,Flowable):
|
||||
def __init__(self,contents,widths, minHeightNeeded=36, spaceBefore=None, spaceAfter=None):
|
||||
if len(contents)!=len(widths):
|
||||
raise ValueError('%r len(contents)=%d not the same as len(widths)=%d' % (self,len(contents),len(widths)))
|
||||
self.contents = contents
|
||||
self.widths = widths
|
||||
self.minHeightNeeded = minHeightNeeded
|
||||
self._spaceBefore = spaceBefore
|
||||
self._spaceAfter = spaceAfter
|
||||
self._naW = None
|
||||
|
||||
def nWidths(self,aW):
|
||||
if aW==self._naW: return self._nW
|
||||
nW = [].append
|
||||
widths = self.widths
|
||||
s = 0.0
|
||||
for i,w in enumerate(widths):
|
||||
if isinstance(w,strTypes):
|
||||
w=w.strip()
|
||||
pc = w.endswith('%')
|
||||
if pc: w=w[:-1]
|
||||
try:
|
||||
w = float(w)
|
||||
except:
|
||||
raise ValueError('%s: nWidths failed with value %r' % (self,widths[i]))
|
||||
if pc: w = w*0.01*aW
|
||||
elif not isinstance(w,(float,int)):
|
||||
raise ValueError('%s: nWidths failed with value %r' % (self,widths[i]))
|
||||
|
||||
s += w
|
||||
nW(w)
|
||||
|
||||
self._naW = aW
|
||||
s = aW / s
|
||||
self._nW = [w*s for w in nW.__self__]
|
||||
return self._nW
|
||||
|
||||
def wrap(self,aW,aH):
|
||||
widths = self.nWidths(aW)
|
||||
w = h = 0.0
|
||||
canv = self.canv
|
||||
h = 0
|
||||
for faW,F in zip(widths,self.contents):
|
||||
if not F:
|
||||
fW = faW
|
||||
fH = 0
|
||||
else:
|
||||
fW,fH = _listWrapOn(F,faW,canv)
|
||||
h = max(h,fH)
|
||||
w += fW
|
||||
self.width = w
|
||||
self.height = h
|
||||
return w, h
|
||||
|
||||
def split(self,aW,aH):
|
||||
if aH<self.minHeightNeeded:
|
||||
return []
|
||||
widths = self.nWidths(aW)
|
||||
S = [[],[]]
|
||||
canv = self.canv
|
||||
for faW,F in zip(widths,self.contents):
|
||||
if not F:
|
||||
fW = faW
|
||||
fH0 = 0
|
||||
S0 = []
|
||||
S1 = []
|
||||
else:
|
||||
fW,fH0,S0,S1 = self._findSplit(canv,faW,aH,content=F,paraFix=False)
|
||||
if S0 is F: return [] #we failed to find a split
|
||||
S[0].append(S0)
|
||||
S[1].append(S1)
|
||||
|
||||
return [
|
||||
MultiCol(S[0],
|
||||
self.widths,
|
||||
minHeightNeeded=self.minHeightNeeded,
|
||||
spaceBefore=self._spaceBefore,
|
||||
spaceAfter=self._spaceAfter),
|
||||
MultiCol(S[1],
|
||||
self.widths,
|
||||
minHeightNeeded=self.minHeightNeeded,
|
||||
spaceBefore=self._spaceBefore,
|
||||
spaceAfter=self._spaceAfter),
|
||||
]
|
||||
|
||||
def getSpaceAfter(self):
|
||||
m = self._spaceAfter
|
||||
if m is None:
|
||||
m = 0
|
||||
for F in self.contents:
|
||||
m = max(m,_Container.getSpaceAfter(self,F))
|
||||
return m
|
||||
|
||||
def getSpaceBefore(self):
|
||||
m = self._spaceBefore
|
||||
if m is None:
|
||||
m = 0
|
||||
for F in self.contents:
|
||||
m = max(m,_Container.getSpaceBefore(self,F))
|
||||
return m
|
||||
|
||||
def drawOn(self, canv, x, y, _sW=0):
|
||||
widths = self._nW
|
||||
xOffs = 0
|
||||
for faW,F in zip(widths,self.contents):
|
||||
_Container.drawOn(self, canv, x+xOffs, y, content=F, aW=faW)
|
||||
xOffs += faW
|
||||
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
@@ -0,0 +1,559 @@
|
||||
#Copyright ReportLab Europe Ltd. 2000-2017
|
||||
#see license.txt for license details
|
||||
#history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/platypus/tableofcontents.py
|
||||
|
||||
__version__='4.2.1.1'
|
||||
__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 multiBuild() 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.units import cm
|
||||
from reportlab.lib.utils import commasplit, escapeOnce, encode_label, decode_label, strTypes, asUnicode, asNative
|
||||
from reportlab.lib.styles import ParagraphStyle, _baseFontName
|
||||
from reportlab.lib import sequencer as rl_sequencer
|
||||
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
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth
|
||||
from reportlab.pdfgen import canvas
|
||||
import unicodedata
|
||||
from ast import literal_eval
|
||||
|
||||
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=' . ', formatter=None):
|
||||
'''
|
||||
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,**kwds):
|
||||
self.rightColumnWidth = kwds.pop('rightColumnWidth',72)
|
||||
self.levelStyles = kwds.pop('levelStyles',defaultLevelStyles)
|
||||
self.tableStyle = kwds.pop('tableStyle',defaultTableStyle)
|
||||
self.dotsMinLevel = kwds.pop('dotsMinLevel',1)
|
||||
self.formatter = kwds.pop('formatter',None)
|
||||
self._notifyKind = kwds.pop('notifyKind','TOCEntry')
|
||||
if kwds: raise ValueError('unexpected keyword arguments %s' % ', '.join(kwds.keys()))
|
||||
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 self._notifyKind (default TOCEntry) events only.
|
||||
"""
|
||||
if kind == self._notifyKind:
|
||||
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]), literal_eval(label[2])
|
||||
style = self.getLevelStyle(level)
|
||||
if self.dotsMinLevel >= 0 and level >= self.dotsMinLevel:
|
||||
dot = ' . '
|
||||
else:
|
||||
dot = ''
|
||||
if self.formatter: page = self.formatter(page)
|
||||
drawPageNumbers(canvas, style, [(page, key)], availWidth, availHeight, dot)
|
||||
self.canv.setNamedCB('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 isinstance(x,(list,tuple)):
|
||||
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._notifyKind = kwargs.pop('notifyKind','IndexEntry')
|
||||
self.setup(**kwargs)
|
||||
|
||||
def getFormatFunc(self,formatName):
|
||||
try:
|
||||
return getattr(rl_sequencer,'_format_%s' % formatName)
|
||||
except ImportError:
|
||||
raise ValueError('Unknown sequencer format %r' % formatName)
|
||||
|
||||
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):
|
||||
label = asNative(label,'latin1')
|
||||
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)
|
||||
c.setNamedCB(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 self._notifyKind (default IndexEntry) events only.
|
||||
"""
|
||||
if kind == self._notifyKind:
|
||||
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.'''
|
||||
lE = self._lastEntries or self._entries
|
||||
if not lE:
|
||||
return dummy
|
||||
return list(sorted(lE.items()))
|
||||
|
||||
def _build(self,availWidth,availHeight):
|
||||
_tempEntries = [(tuple(asUnicode(t) for t in texts),pageNumbers)
|
||||
for texts, pageNumbers in self._getlastEntries()]
|
||||
def getkey(seq):
|
||||
return [''.join((c for c in unicodedata.normalize('NFD', x.upper()) if unicodedata.category(c) != 'Mn')) 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.setNamedCB('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 = ''.join((c for c in unicodedata.normalize('NFD', texts[0][0].upper()) if unicodedata.category(c) != 'Mn'))
|
||||
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)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,345 @@
|
||||
#Copyright ReportLab Europe Ltd. 2000-2017
|
||||
#see license.txt for license details
|
||||
#history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/platypus/xpreformatted.py
|
||||
__all__ = (
|
||||
'XPreformatted',
|
||||
'PythonPreformatted',
|
||||
)
|
||||
__version__='3.5.20'
|
||||
__doc__='''A 'rich preformatted text' widget allowing internal markup'''
|
||||
from reportlab.lib import PyFontify
|
||||
from reportlab.platypus.paragraph import Paragraph, _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 += stringWidth(text, f.fontName, f.fontSize)
|
||||
|
||||
#s = s + _countSpaces(text)
|
||||
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."""
|
||||
|
||||
self._width_max = 0
|
||||
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)
|
||||
if currentWidth > self._width_max: self._width_max = currentWidth
|
||||
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
|
||||
if currentWidth > self._width_max: self._width_max = currentWidth
|
||||
lines.append(ParaLines(extraSpace=extraSpace,wordCount=n, words=words, fontSize=maxSize, ascent=maxAscent,descent=minDescent,currentWidth=currentWidth,preformatted=True))
|
||||
|
||||
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