237 lines
7 KiB
Python
237 lines
7 KiB
Python
|
# docbook.py: extension module
|
||
|
# $Id: docbook.py 8353 2009-03-17 16:57:50Z mzjn $
|
||
|
import libxml2
|
||
|
import libxslt
|
||
|
import re
|
||
|
import math
|
||
|
|
||
|
# Some globals
|
||
|
pixelsPerInch = 96.0
|
||
|
unitHash = { 'in': pixelsPerInch,
|
||
|
'cm': pixelsPerInch / 2.54,
|
||
|
'mm': pixelsPerInch / 25.4,
|
||
|
'pc': (pixelsPerInch / 72.0) * 12,
|
||
|
'pt': pixelsPerInch / 72.0,
|
||
|
'px': 1 }
|
||
|
|
||
|
# ======================================================================
|
||
|
|
||
|
def adjustColumnWidths(ctx, nodeset):
|
||
|
#
|
||
|
# Small check to verify the context is correcly accessed
|
||
|
#
|
||
|
try:
|
||
|
pctxt = libxslt.xpathParserContext(_obj=ctx)
|
||
|
ctxt = pctxt.context()
|
||
|
tctxt = ctxt.transformContext()
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
# Get the nominal table width
|
||
|
varString = lookupVariable(tctxt, "nominal.table.width", None)
|
||
|
if varString is None:
|
||
|
nominalWidth = 6 * pixelsPerInch
|
||
|
else:
|
||
|
nominalWidth = convertLength(varString)
|
||
|
|
||
|
# Get the requested table width
|
||
|
tableWidth = lookupVariable(tctxt, "table.width", "100%")
|
||
|
|
||
|
foStylesheet = (tctxt.variableLookup("stylesheet.result.type", None) == "fo")
|
||
|
|
||
|
relTotal = 0
|
||
|
relParts = []
|
||
|
|
||
|
absTotal = 0
|
||
|
absParts = []
|
||
|
|
||
|
colgroup = libxml2.xmlNode(_obj = nodeset[0])
|
||
|
# If this is an foStylesheet, we've been passed a list of fo:table-columns.
|
||
|
# Otherwise we've been passed a colgroup that contains a list of cols.
|
||
|
if foStylesheet:
|
||
|
colChildren = colgroup
|
||
|
else:
|
||
|
colChildren = colgroup.children
|
||
|
|
||
|
col = colChildren
|
||
|
while col is not None:
|
||
|
if foStylesheet:
|
||
|
width = col.prop("column-width")
|
||
|
else:
|
||
|
width = col.prop("width")
|
||
|
|
||
|
if width is None:
|
||
|
width = "1*"
|
||
|
|
||
|
relPart = 0.0
|
||
|
absPart = 0.0
|
||
|
starPos = width.find("*")
|
||
|
if starPos >= 0:
|
||
|
relPart, absPart = width.split("*", 2)
|
||
|
relPart = float(relPart)
|
||
|
relTotal = relTotal + float(relPart)
|
||
|
else:
|
||
|
absPart = width
|
||
|
|
||
|
pixels = convertLength(absPart)
|
||
|
absTotal = absTotal + pixels
|
||
|
|
||
|
relParts.append(relPart)
|
||
|
absParts.append(pixels)
|
||
|
|
||
|
col = col.__next__
|
||
|
|
||
|
# Ok, now we have the relative widths and absolute widths in
|
||
|
# two parallel arrays.
|
||
|
#
|
||
|
# - If there are no relative widths, output the absolute widths
|
||
|
# - If there are no absolute widths, output the relative widths
|
||
|
# - If there are a mixture of relative and absolute widths,
|
||
|
# - If the table width is absolute, turn these all into absolute
|
||
|
# widths.
|
||
|
# - If the table width is relative, turn these all into absolute
|
||
|
# widths in the nominalWidth and then turn them back into
|
||
|
# percentages.
|
||
|
|
||
|
widths = []
|
||
|
|
||
|
if relTotal == 0:
|
||
|
for absPart in absParts:
|
||
|
if foStylesheet:
|
||
|
inches = absPart / pixelsPerInch
|
||
|
widths.append("%4.2fin" % inches)
|
||
|
else:
|
||
|
widths.append("%d" % absPart)
|
||
|
elif absTotal == 0:
|
||
|
for relPart in relParts:
|
||
|
rel = relPart / relTotal * 100
|
||
|
widths.append(rel)
|
||
|
widths = correctRoundingError(widths)
|
||
|
else:
|
||
|
pixelWidth = nominalWidth
|
||
|
if '%' not in tableWidth:
|
||
|
pixelWidth = convertLength(tableWidth)
|
||
|
|
||
|
if pixelWidth <= absTotal:
|
||
|
print("Table is wider than table width")
|
||
|
else:
|
||
|
pixelWidth = pixelWidth - absTotal
|
||
|
|
||
|
absTotal = 0
|
||
|
for count in range(len(relParts)):
|
||
|
rel = relParts[count] / relTotal * pixelWidth
|
||
|
relParts[count] = rel + absParts[count]
|
||
|
absTotal = absTotal + rel + absParts[count]
|
||
|
|
||
|
if '%' not in tableWidth:
|
||
|
for count in range(len(relParts)):
|
||
|
if foStylesheet:
|
||
|
pixels = relParts[count]
|
||
|
inches = pixels / pixelsPerInch
|
||
|
widths.append("%4.2fin" % inches)
|
||
|
else:
|
||
|
widths.append(relParts[count])
|
||
|
else:
|
||
|
for count in range(len(relParts)):
|
||
|
rel = relParts[count] / absTotal * 100
|
||
|
widths.append(rel)
|
||
|
widths = correctRoundingError(widths)
|
||
|
|
||
|
# Danger, Will Robinson! In-place modification of the result tree!
|
||
|
# Side-effect free? We don' need no steenkin' side-effect free!
|
||
|
count = 0
|
||
|
col = colChildren
|
||
|
while col is not None:
|
||
|
if foStylesheet:
|
||
|
col.setProp("column-width", widths[count])
|
||
|
else:
|
||
|
col.setProp("width", widths[count])
|
||
|
|
||
|
count = count+1
|
||
|
col = col.__next__
|
||
|
|
||
|
return nodeset
|
||
|
|
||
|
def convertLength(length):
|
||
|
# Given "3.4in" return the width in pixels
|
||
|
global pixelsPerInch
|
||
|
global unitHash
|
||
|
|
||
|
m = re.search('([+-]?[\d.]+)(\S+)', length)
|
||
|
if m is not None and m.lastindex > 1:
|
||
|
unit = pixelsPerInch
|
||
|
if m.group(2) in unitHash:
|
||
|
unit = unitHash[m.group(2)]
|
||
|
else:
|
||
|
print("Unrecognized length: " + m.group(2))
|
||
|
|
||
|
pixels = unit * float(m.group(1))
|
||
|
else:
|
||
|
pixels = 0
|
||
|
|
||
|
return pixels
|
||
|
|
||
|
def correctRoundingError(floatWidths):
|
||
|
# The widths are currently floating point numbers, we have to truncate
|
||
|
# them back to integers and then distribute the error so that they sum
|
||
|
# to exactly 100%.
|
||
|
|
||
|
totalWidth = 0
|
||
|
widths = []
|
||
|
for width in floatWidths:
|
||
|
width = math.floor(width)
|
||
|
widths.append(width)
|
||
|
totalWidth = totalWidth + math.floor(width)
|
||
|
|
||
|
totalError = 100 - totalWidth
|
||
|
columnError = totalError / len(widths)
|
||
|
error = 0
|
||
|
for count in range(len(widths)):
|
||
|
width = widths[count]
|
||
|
error = error + columnError
|
||
|
if error >= 1.0:
|
||
|
adj = math.floor(error)
|
||
|
error = error - adj
|
||
|
widths[count] = "%d%%" % (width + adj)
|
||
|
else:
|
||
|
widths[count] = "%d%%" % width
|
||
|
|
||
|
return widths
|
||
|
|
||
|
def lookupVariable(tctxt, varName, default):
|
||
|
varString = tctxt.variableLookup(varName, None)
|
||
|
if varString is None:
|
||
|
return default
|
||
|
|
||
|
# If it's a list, get the first element
|
||
|
if type(varString) == type([]):
|
||
|
varString = varString[0]
|
||
|
|
||
|
# If it's not a string, it must be a node, get its content
|
||
|
if type(varString) != type(""):
|
||
|
varString = varString.content
|
||
|
|
||
|
return varString
|
||
|
|
||
|
# ======================================================================
|
||
|
# Random notes...
|
||
|
|
||
|
#once you have a node which is a libxml2 python xmlNode wrapper all common
|
||
|
#operations are possible:
|
||
|
# .children .last .parent .next .prev .doc for navigation
|
||
|
# .content .type for introspection
|
||
|
# .prop("attribute_name") to lookup attribute values
|
||
|
|
||
|
# # Now make a nodeset to return
|
||
|
# # Danger, Will Robinson! This creates a memory leak!
|
||
|
# newDoc = libxml2.newDoc("1.0")
|
||
|
# newColGroup = newDoc.newDocNode(None, "colgroup", None)
|
||
|
# newDoc.addChild(newColGroup)
|
||
|
# col = colgroup.children
|
||
|
# while col != None:
|
||
|
# newCol = newDoc.newDocNode(None, "col", None)
|
||
|
# newCol.copyPropList(col);
|
||
|
# newCol.setProp("width", "4")
|
||
|
# newColGroup.addChild(newCol)
|
||
|
# col = col.next
|