'''fohelpers.py: XSL-FO helper functions Do not edit this file directly. It is extracted automatically from the documentation: http://www.nmt.edu/tcc/projects/fohelpers/ ''' # - - - - - I m p o r t s import sys from etbuilder import et, E, subElement from decimal import Decimal, getcontext getcontext().prec = 6 # - - - - - M a n i f e s t c o n s t a n t s FO_NAMESPACE = "http://www.w3.org/1999/XSL/Format" UNITS_MM = "mm" UNITS_CM = "cm" UNITS_IN = "in" UNITS_PC = "pc" UNITS_PT = "pt" PAPER_LETTER = 'letter' PAPER_LETTER_LAND = 'letter-landscape' PAPER_LEGAL = 'legal' PAPER_3X5 = '3x5' # - - - - - c l a s s F l o w T r e e class FlowTree(object): '''Represents an XSL-FO document. Exports: FlowTree(): [ return a new, empty FlowTree ] .doc: [ the document as an et.ElementTree ] .root: [ the document root as an et.Element ] .masters: [ the layout-master-set as an et.Element ] .write(outFile): [ outFile is a writeable file-like object -> outFile +:= self as XML ] ''' # - - - F l o w T r e e . _ _ i n i t _ _ def __init__(self): '''Constructor ''' #-- 1 -- # [ self.root := a new root et.Element # self.doc := a new et.ElementTree with that root ] self.root = E.root(xmlns=FO_NAMESPACE) self.doc = et.ElementTree(self.root) #-- 2 -- # [ self.root +:= a new layout-master-set child # self.masters := that child ] self.masters = subElement(self.root, E("layout-master-set") ) # - - - F l o w T r e e . w r i t e def write(self, outFile): '''Serialize as XML. ''' self.doc.write(outFile) # - - - - - c l a s s F o D i m class FoDim(object): '''Represents a length dimension on XSL-FO. Exports: FoDim(n, units): [ (n is a number in a form acceptable to the Decimal() constructor) and (units is in ['in', 'mm', 'cm', 'pc', 'pt']) -> return a FoDim instance representing that length .__str__(self): [ return a string representing self as an XSL-FO length ] .__add__(self, other): [ other is a FoDim instance -> return a new FoDim representing self+other ] .__sub__(self, other): [ other is a FoDim instance -> return a new FoDim representing self-other ] .__mul__(self, other): [ other is a value acceptable to the Decimal() constructor -> return a new FoDim representing self*other ] .__div__(self, other): [ other is a value acceptable to the Decimal() constructor -> if other is zero -> raise ZeroDivisionError else -> return a new FoDim representing self/other .__neg__(self): [ return a new FoDim representing 0-self ] .convert(newUnits): [ newUnits is one of the units acceptable to FoDim() -> return a new FoDim representing self expressed in newUnits ] State/Invariants: .n: [ as passed to constructor, read-only ] .units: [ as passed to constructor, read-only ] ''' # - - - F o D i m . _ _ i n i t _ _ def __init__(self, n, units): '''Constructor ''' self.n = Decimal(n) self.units = units # - - - F o D i m . _ _ s t r _ _ def __str__(self): '''Convert to a string. ''' return "%s%s" % (self.n, self.units) # - - - F o D i m . _ _ a d d _ _ def __add__(self, other): '''Add two dimensions. ''' #-- 1 -- # [ otherCon := other expressed in units of (self.units) ] otherCon = other.convert(self.units) #-- 2 -- return FoDim(self.n + otherCon.n, self.units) # - - - F o D i m . _ _ s u b _ _ def __sub__(self, other): '''Subtract two dimensions ''' #-- 1 -- negOther = -other #-- 2 -- return self + negOther # - - - F o D i m . _ _ m u l _ _ def __mul__ ( self, other ): '''Multiply self by a constant. ''' return FoDim ( self.n * Decimal(other), self.units ) # - - - F o D i m . _ _ d i v _ _ def __div__ ( self, other ): '''Divide self by a constant. ''' return FoDim ( self.n / Decimal(other), self.units ) # - - - F o D i m . _ _ n e g _ _ def __neg__ ( self ): '''Negate a dimension. ''' return FoDim(-self.n, self.units) # - - - F o D i m . c o n v e r t def convert(self, newUnits): '''Convert to the same value in different units (appproximately). ''' #-- 1 -- # [ factor := the number by which self.n must be multiplied # to convert from self.units to newUnits ] factor = FoDim.confactor(self.units, newUnits) #-- 2 -- return FoDim(self.n * factor, newUnits) # - - - F o D i m . c o n f a c t o r (static method) unitList = (UNITS_PC, UNITS_PT, UNITS_IN, UNITS_CM, UNITS_MM) factorList = ( Decimal(12), # 1pc = 12pt Decimal(1)/Decimal('72.27'), # 1pt = 1/72.27in Decimal('2.54'), # 1in = 2.54cm Decimal(10) ) # 1cm = 10mm @staticmethod def confactor(fromUnits, toUnits): '''Find any arbitrary conversion factor. [ fromUnits and toUnits are XSL-FO dimensional units -> return the factor that must be multiplied by a quantity using fromUnits to express it as toUnits ] ''' #-- 1 -- # [ fromPos := position of fromUnits in unitList # toPos := position of toUnits in unitList # result := Decimal(1) ] fromPos = FoDim.unitList.index(fromUnits) toPos = FoDim.unitList.index(toUnits) result = Decimal(1) #-- 2 -- # [ if fromPos < toPos -> # result *:= elements of FoDim.factorList in positions # fromPos, fromPos+1, ..., toPos-1, inclusive # else if fromPos > toPos -> # result /:= elements of FoDim.factorList in positions # fromPos-1, fromPos-2, ..., toPos, inclusive ] if fromPos < toPos: for pos in range(fromPos, toPos): result *= FoDim.factorList[pos] elif fromPos > toPos: for pos in range(fromPos-1, toPos-1, -1): result /= FoDim.factorList[pos] #-- 3 -- return result # - - - - - c l a s s B o x class Box(object): '''Represents the dimensions of a rectangle. Exports: Box(wide, high): [ wide and high are FoDim instances -> return a new Box representing those dimensions ] .wide: [ as passed to constructor ] .high: [ as passed to constructor ] .__str__(self): [ return self as a string ] ''' # - - - B o x . _ _ i n i t _ _ def __init__(self, wide, high): '''Constructor. ''' self.wide, self.high = wide, high # - - - B o x . _ _ s t r _ _ def __str__(self): return ("".format( self.wide, self.high)) # - - - - - c l a s s M a r g i n S e t class MarginSet(object): '''Represents a set of four margin sizes. Exports: MarginSet(top="0.0in", bot="0.0in", left="0.0in", right="0.0in"): [ arguments are valid XSL-FO dimensions -> return a new MarginSet instance representing top margin (top), bottom margin (bot), left margin (left), and right margin (right) ] .top, .bot, .left, .right: [ as passed to constructor ] .dict(): [ return a dict whose keys are "margin-top", etc., with corresponding values from self.top, etc. ] .__str__(): [ return self as a string ] ''' # - - - M a r g i n S e t . _ _ i n i t _ _ def __init__(self, top="0.0in", bot="0.0in", left="0.0in", right="0.0in"): '''Constructor. ''' self.top = top self.bot = bot self.left = left self.right = right # - - - M a r g i n S e t . d i c t def dict(self): '''Return a dictionary of margin attributes. ''' return dash(marginTop=str(self.top), marginBottom=str(self.bot), marginLeft=str(self.left), marginRight=str(self.right)) # - - - M a r g i n S e t . _ _ s t r _ _ def __str__(self): return("".format( self.top, self.bot, self.left, self.right)) # - - - - - c l a s s P a g e D i m class PageDim(object): '''Represents a page layout. Exports: PageDim(pageBox, pageMargins=None, frameMargins=None, bodyMargins=None): [ (pageBox is the paper size as a Box instance) and (pageMargins represents the page margins as a MarginSet instance, defaulting to no margins) and (frameMargins represents the header and footer heights as a MarginSet instance, defaulting to zero) and (bodyMargins represents the margin above and below the body as a MarginSet instance, defaulting to zero) -> return a new PageDim instance representing that layout ] .pageBox: [ as passed to constructor ] .pageMargins: [ as passed to constructor ] .frameMargins: [ as passed to constructor ] .bodyMargins: [ as passed to constructor ] Invariants: .pageMargins, .frameMargins, and .bodyMargins always contain a MarginSet instance. ''' # - - - P a g e D i m . _ _ i n i t _ _ def __init__(self, pageBox, pageMargins=None, frameMargins=None, bodyMargins=None): '''Constructor ''' self.pageBox = pageBox self.pageMargins = pageMargins or MarginSet() self.frameMargins = frameMargins or MarginSet() self.bodyMargins = bodyMargins or MarginSet() # - - - P a g e D i m . _ _ s t r _ _ def __str__(self): return ("".format( self.pageBox, self.pageMargins, self.frameMargins, self.bodyMargins)) # - - - p a g e D i m F a c t o r y def pageDimFactory ( paperType ): '''Create a new PageDim instance for a standard sheet size. ''' return paperTypeMap[paperType]() def letterPage(): return PageDim ( pageBox=Box(FoDim("8.5", "in"), FoDim("11", "in")), pageMargins=MarginSet(top=FoDim("0.5", "in"), bot=FoDim("0.5", "in"), left=FoDim("0.5", "in"), right=FoDim("0.5", "in")), frameMargins=MarginSet(top=FoDim("3", "pc"), bot=FoDim("3", "pc"), left=FoDim("0", "pc"), right=FoDim("0", "pc")), bodyMargins=MarginSet(top=FoDim("1", "pc"), bot=FoDim("1", "pc"), left=FoDim("0", "pc"), right=FoDim("0", "pc"))) def letterLandPage(): return PageDim ( pageBox=Box(FoDim("11", "in"), FoDim("8.5", "in")), pageMargins=MarginSet(top=FoDim("0.5", "in"), bot=FoDim("0.5", "in"), left=FoDim("0.5", "in"), right=FoDim("0.5", "in")), frameMargins=MarginSet(top=FoDim("3", "pc"), bot=FoDim("3", "pc"), left=FoDim("0", "pc"), right=FoDim("0", "pc")), bodyMargins=MarginSet(top=FoDim("1", "pc"), bot=FoDim("1", "pc"), left=FoDim("0", "pc"), right=FoDim("0", "pc"))) def legalPage(): return PageDim ( pageBox=Box(FoDim("8.5", "in"), FoDim("14", "in")), pageMargins=MarginSet(top=FoDim("0.5", "in"), bot=FoDim("0.5", "in"), left=FoDim("0.5", "in"), right=FoDim("0.5" "in")), frameMargins=MarginSet(top=FoDim("3", "pc"), bot=FoDim("3", "pc"), left=FoDim("0", "pc"), right=FoDim("0", "pc")), bodyMargins=MarginSet(top=FoDim("1", "pc"), bot=FoDim("1", "pc"), left=FoDim("0", "pc"), right=FoDim("0", "pc"))) def indexCardPage(): return PageDim ( pageBox=Box(FoDim("3", "in"), FoDim("5", "in")), pageMargins=MarginSet(top=FoDim("0.1", "in"), bot=FoDim("0.1", "in"), left=FoDim("0.25", "in"), right=FoDim("0.25", "in")), frameMargins=MarginSet(top=FoDim("1.5", "pc"), bot=FoDim("1", "pc"), left=FoDim("0", "pc"), right=FoDim("0", "pc")), bodyMargins=MarginSet(top=FoDim("1", "pc"), bot=FoDim("1", "pc"), left=FoDim("0", "pc"), right=FoDim("0", "pc")) ) paperTypeMap = { PAPER_LETTER: letterPage, PAPER_LETTER_LAND: letterLandPage, PAPER_LEGAL: legalPage, PAPER_3X5: indexCardPage } # - - - s i m p l e M a s t e r def simpleMaster(masterName, pageDim, *contents, **attrs): '''Return a new simple-page-master tree. [ (masterName is a valid XML name) and (pageDim is a PageDim instance) and (contents is a sequence of values acceptable to E()) and (attrs is a dictionary of attribute names and values) -> return a new simple-page-master et.Element with that master-name, page size from pageDim.pageBox, page margins from pageDim.pageMargins, contents (contents), and attributes (attrs) ] ''' #-- 1 -- return E("simple-page-master", dash(masterName=masterName, pageWidth=pageDim.pageBox.wide, pageHeight=pageDim.pageBox.high), pageDim.pageMargins.dict(), attrs, *contents) # - - - r e g i o n B o d y def regionBody(pageDim, *contents, **attrs): '''Creates a region-body element. ''' return E("region-body", pageDim.bodyMargins.dict(), *contents, **attrs) # - - - r e g i o n B e f o r e def regionBefore(pageDim, regName): '''Create a region-before element. [ (pageDim is a PageDim element) and (regName is a valid XML name) -> return a new region-before element whose extent comes from pageDim.frameMargins.top and whose region-name is regName ] ''' return E("region-before", dash(regionName=regName, extent=pageDim.frameMargins.top)) # - - - r e g i o n A f t e r def regionAfter(pageDim, regName): '''Create a region-after element. ''' return E("region-after", dash(regionName=regName, extent=pageDim.frameMargins.bot)) # - - - r e p e a t M a s t e r def repeatMaster(masterRef, *condList): '''Set up a set of page master alternatives. [ (masterName is the name of a new page-sequence-master) and (the remaining arguments are conditional-page-master-reference elements) -> return a new page-sequence-master named masterName containing a repeatable-page-master-alternatives child whose children are condList[0], condList[1], etc. ] ''' return E("page-sequence-master", dash(masterName=masterRef), E("repeatable-page-master-alternatives", *condList)) # - - - c o n d i t i o n a l M a s t e r def conditionalMaster ( masterRef, pagePos=None, oddEven=None, blankNon=None): '''Create a conditional-page-master-reference. [ (masterRef is the name of the referenced master) and (pagePos is "first", "rest", "last", defaulting to no page position test) and (oddEven is "odd" or "even", defaulting to no test) and (blankNon is "blank" or "non-blank", defaulting to no test) -> return a new conditional-page-master-reference element with those values ] ''' node = E("conditional-page-master-reference", dash(masterReference=masterRef)) if pagePos: node.attrib["page-position"] = pagePos if oddEven: node.attrib["odd-or-even"] = oddEven if blankNon: node.attrib["blank-or-not-blank"] = blankNon return node # - - - p a g e S e q u e n c e def pageSequence ( masterRef ): '''Create a page-sequence element. ''' return E("page-sequence", dash(masterReference=masterRef)) # - - - s t a t i c C o n t e n t def staticContent ( flowName, *contents, **attrs ): '''Generate a static-content element. ''' return E("static-content", dash(flowName=flowName), attrs, *contents ) # - - - f l o w def flow(): '''Return a flow element for xsl-region-body ''' return E.flow(dash(flowName="xsl-region-body")) # - - - b l o c k def block(*contents, **attrs): '''Generate a block element. ''' return E ( "block", attrs, *contents ) # - - - i n l i n e def inline(*contents, **attrs): '''Generate an inline element. ''' return E ( "inline", attrs, *contents ) # - - - l e a d e r def leader(length=None, pattern=None): '''Create a leader element. ''' #-- 1 -- lengthDict = ({} if length is None else dash(leaderLength=length)) #-- 2 leaderPattern = ('space' if pattern is None else pattern ) #-- 3 return E.leader(lengthDict, dash(leaderPattern=leaderPattern)) # - - - f o n t fontArgMap = { 'fontFamily': 'font-family', 'fontSize': 'font-size', 'fontStyle': 'font-style', 'fontWeight': 'font-weight', 'fontVariant': 'font-variant', 'fontStretch': 'font-stretch' } def font(**kw): '''Return a dictionary of font properties. ''' result = {} for key in kw: result[fontArgMap[key]] = kw[key] return result # - - - d a s h def dash(**kw): '''XSL-FO attribute dictionary builder. [ kw is a dictionary -> return a new dictionary with the same elements as kw, but with each key transformed by substituting "x-y" for any sequence "xY" where x is lowercase and Y is uppercase ] ''' #-- 1 result = {} #-- 2 for key in kw: result[deCamel(key)] = kw[key] #-- 3 return result # - - - d e C a m e l def deCamel(name): '''Change camelCase names to hyphenated-names. [ name is a str -> return name, transformed by substituting "x-y" for any sequence "xY" where x is lowercase and Y is uppercase ] ''' #-- 1 last = 'A' result = [] #-- 2 # [ result +:= all but the first character of (last+name) # with sequences "xY" replaced replaced by "x-y" # last := the last character of name ] for c in name: #-- 2 body result.append (("-" + c.lower()) if last.islower() and c.isupper() else c) last = c #-- 3 return ''.join(result)