git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@419 4cde692c-a291-49d1-8350-778aa11640f8
811 lines
25 KiB
Python
811 lines
25 KiB
Python
"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
|
|
|
|
|
|
#
|
|
# The Apple and MS rasterizers behave differently for
|
|
# scaled composite components: one does scale first and then translate
|
|
# and the other does it vice versa. MS defined some flags to indicate
|
|
# the difference, but it seems nobody actually _sets_ those flags.
|
|
#
|
|
# Funny thing: Apple seems to _only_ do their thing in the
|
|
# WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE
|
|
# (eg. Charcoal)...
|
|
#
|
|
SCALE_COMPONENT_OFFSET_DEFAULT = 0 # 0 == MS, 1 == Apple
|
|
|
|
|
|
import struct, sstruct
|
|
import DefaultTable
|
|
from fontTools import ttLib
|
|
from fontTools.misc.textTools import safeEval, readHex
|
|
import ttProgram
|
|
import array
|
|
import Numeric
|
|
from types import StringType, TupleType
|
|
|
|
|
|
class table__g_l_y_f(DefaultTable.DefaultTable):
|
|
|
|
def decompile(self, data, ttFont):
|
|
loca = ttFont['loca']
|
|
last = loca[0]
|
|
self.glyphs = {}
|
|
self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
|
|
for i in range(0, len(loca)-1):
|
|
glyphName = glyphOrder[i]
|
|
next = loca[i+1]
|
|
glyphdata = data[last:next]
|
|
if len(glyphdata) <> (next - last):
|
|
raise ttLib.TTLibError, "not enough 'glyf' table data"
|
|
glyph = Glyph(glyphdata)
|
|
self.glyphs[glyphName] = glyph
|
|
last = next
|
|
# this should become a warning:
|
|
#if len(data) > next:
|
|
# raise ttLib.TTLibError, "too much 'glyf' table data"
|
|
|
|
def compile(self, ttFont):
|
|
if not hasattr(self, "glyphOrder"):
|
|
self.glyphOrder = ttFont.getGlyphOrder()
|
|
import string
|
|
locations = []
|
|
currentLocation = 0
|
|
dataList = []
|
|
recalcBBoxes = ttFont.recalcBBoxes
|
|
for glyphName in self.glyphOrder:
|
|
glyph = self.glyphs[glyphName]
|
|
glyphData = glyph.compile(self, recalcBBoxes)
|
|
locations.append(currentLocation)
|
|
currentLocation = currentLocation + len(glyphData)
|
|
dataList.append(glyphData)
|
|
locations.append(currentLocation)
|
|
data = string.join(dataList, "")
|
|
ttFont['loca'].set(locations)
|
|
ttFont['maxp'].numGlyphs = len(self.glyphs)
|
|
return data
|
|
|
|
def toXML(self, writer, ttFont, progress=None):
|
|
writer.newline()
|
|
glyphNames = ttFont.getGlyphNames()
|
|
writer.comment("The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler.")
|
|
writer.newline()
|
|
writer.newline()
|
|
counter = 0
|
|
progressStep = 10
|
|
numGlyphs = len(glyphNames)
|
|
for glyphName in glyphNames:
|
|
if not counter % progressStep and progress is not None:
|
|
progress.setLabel("Dumping 'glyf' table... (%s)" % glyphName)
|
|
progress.increment(progressStep / float(numGlyphs))
|
|
counter = counter + 1
|
|
glyph = self[glyphName]
|
|
if glyph.numberOfContours:
|
|
writer.begintag('TTGlyph', [
|
|
("name", glyphName),
|
|
("xMin", glyph.xMin),
|
|
("yMin", glyph.yMin),
|
|
("xMax", glyph.xMax),
|
|
("yMax", glyph.yMax),
|
|
])
|
|
writer.newline()
|
|
glyph.toXML(writer, ttFont)
|
|
writer.endtag('TTGlyph')
|
|
writer.newline()
|
|
else:
|
|
writer.simpletag('TTGlyph', name=glyphName)
|
|
writer.comment("contains no outline data")
|
|
writer.newline()
|
|
writer.newline()
|
|
|
|
def fromXML(self, (name, attrs, content), ttFont):
|
|
if name <> "TTGlyph":
|
|
return
|
|
if not hasattr(self, "glyphs"):
|
|
self.glyphs = {}
|
|
if not hasattr(self, "glyphOrder"):
|
|
self.glyphOrder = ttFont.getGlyphOrder()
|
|
glyphName = attrs["name"]
|
|
if ttFont.verbose:
|
|
ttLib.debugmsg("unpacking glyph '%s'" % glyphName)
|
|
glyph = Glyph()
|
|
for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
|
|
setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
|
|
self.glyphs[glyphName] = glyph
|
|
for element in content:
|
|
if type(element) <> TupleType:
|
|
continue
|
|
glyph.fromXML(element, ttFont)
|
|
if not ttFont.recalcBBoxes:
|
|
glyph.compact(self, 0)
|
|
|
|
def setGlyphOrder(self, glyphOrder):
|
|
self.glyphOrder = glyphOrder
|
|
|
|
def getGlyphName(self, glyphID):
|
|
return self.glyphOrder[glyphID]
|
|
|
|
def getGlyphID(self, glyphName):
|
|
# XXX optimize with reverse dict!!!
|
|
return self.glyphOrder.index(glyphName)
|
|
|
|
def keys(self):
|
|
return self.glyphs.keys()
|
|
|
|
def has_key(self, glyphName):
|
|
return self.glyphs.has_key(glyphName)
|
|
|
|
__contains__ = has_key
|
|
|
|
def __getitem__(self, glyphName):
|
|
glyph = self.glyphs[glyphName]
|
|
glyph.expand(self)
|
|
return glyph
|
|
|
|
def __setitem__(self, glyphName, glyph):
|
|
self.glyphs[glyphName] = glyph
|
|
if glyphName not in self.glyphOrder:
|
|
self.glyphOrder.append(glyphName)
|
|
|
|
def __delitem__(self, glyphName):
|
|
del self.glyphs[glyphName]
|
|
self.glyphOrder.remove(glyphName)
|
|
|
|
def __len__(self):
|
|
assert len(self.glyphOrder) == len(self.glyphs)
|
|
return len(self.glyphs)
|
|
|
|
|
|
glyphHeaderFormat = """
|
|
> # big endian
|
|
numberOfContours: h
|
|
xMin: h
|
|
yMin: h
|
|
xMax: h
|
|
yMax: h
|
|
"""
|
|
|
|
# flags
|
|
flagOnCurve = 0x01
|
|
flagXShort = 0x02
|
|
flagYShort = 0x04
|
|
flagRepeat = 0x08
|
|
flagXsame = 0x10
|
|
flagYsame = 0x20
|
|
flagReserved1 = 0x40
|
|
flagReserved2 = 0x80
|
|
|
|
|
|
ARG_1_AND_2_ARE_WORDS = 0x0001 # if set args are words otherwise they are bytes
|
|
ARGS_ARE_XY_VALUES = 0x0002 # if set args are xy values, otherwise they are points
|
|
ROUND_XY_TO_GRID = 0x0004 # for the xy values if above is true
|
|
WE_HAVE_A_SCALE = 0x0008 # Sx = Sy, otherwise scale == 1.0
|
|
NON_OVERLAPPING = 0x0010 # set to same value for all components (obsolete!)
|
|
MORE_COMPONENTS = 0x0020 # indicates at least one more glyph after this one
|
|
WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Sx, Sy
|
|
WE_HAVE_A_TWO_BY_TWO = 0x0080 # t00, t01, t10, t11
|
|
WE_HAVE_INSTRUCTIONS = 0x0100 # instructions follow
|
|
USE_MY_METRICS = 0x0200 # apply these metrics to parent glyph
|
|
OVERLAP_COMPOUND = 0x0400 # used by Apple in GX fonts
|
|
SCALED_COMPONENT_OFFSET = 0x0800 # composite designed to have the component offset scaled (designed for Apple)
|
|
UNSCALED_COMPONENT_OFFSET = 0x1000 # composite designed not to have the component offset scaled (designed for MS)
|
|
|
|
|
|
class Glyph:
|
|
|
|
def __init__(self, data=""):
|
|
if not data:
|
|
# empty char
|
|
self.numberOfContours = 0
|
|
return
|
|
self.data = data
|
|
|
|
def compact(self, glyfTable, recalcBBoxes=1):
|
|
data = self.compile(glyfTable, recalcBBoxes)
|
|
self.__dict__.clear()
|
|
self.data = data
|
|
|
|
def expand(self, glyfTable):
|
|
if not hasattr(self, "data"):
|
|
# already unpacked
|
|
return
|
|
if not self.data:
|
|
# empty char
|
|
self.numberOfContours = 0
|
|
return
|
|
dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
|
|
del self.data
|
|
if self.isComposite():
|
|
self.decompileComponents(data, glyfTable)
|
|
else:
|
|
self.decompileCoordinates(data)
|
|
|
|
def compile(self, glyfTable, recalcBBoxes=1):
|
|
if hasattr(self, "data"):
|
|
return self.data
|
|
if self.numberOfContours == 0:
|
|
return ""
|
|
if recalcBBoxes:
|
|
self.recalcBounds(glyfTable)
|
|
data = sstruct.pack(glyphHeaderFormat, self)
|
|
if self.isComposite():
|
|
data = data + self.compileComponents(glyfTable)
|
|
else:
|
|
data = data + self.compileCoordinates()
|
|
# From the spec: "Note that the local offsets should be word-aligned"
|
|
# From a later MS spec: "Note that the local offsets should be long-aligned"
|
|
# Let's be modern and align on 4-byte boundaries.
|
|
if len(data) % 4:
|
|
# add pad bytes
|
|
nPadBytes = 4 - (len(data) % 4)
|
|
data = data + "\0" * nPadBytes
|
|
return data
|
|
|
|
def toXML(self, writer, ttFont):
|
|
if self.isComposite():
|
|
for compo in self.components:
|
|
compo.toXML(writer, ttFont)
|
|
if hasattr(self, "program"):
|
|
writer.begintag("instructions")
|
|
self.program.toXML(writer, ttFont)
|
|
writer.endtag("instructions")
|
|
writer.newline()
|
|
else:
|
|
last = 0
|
|
for i in range(self.numberOfContours):
|
|
writer.begintag("contour")
|
|
writer.newline()
|
|
for j in range(last, self.endPtsOfContours[i] + 1):
|
|
writer.simpletag("pt", [
|
|
("x", self.coordinates[j][0]),
|
|
("y", self.coordinates[j][1]),
|
|
("on", self.flags[j] & flagOnCurve)])
|
|
writer.newline()
|
|
last = self.endPtsOfContours[i] + 1
|
|
writer.endtag("contour")
|
|
writer.newline()
|
|
if self.numberOfContours:
|
|
writer.begintag("instructions")
|
|
self.program.toXML(writer, ttFont)
|
|
writer.endtag("instructions")
|
|
writer.newline()
|
|
|
|
def fromXML(self, (name, attrs, content), ttFont):
|
|
if name == "contour":
|
|
self.numberOfContours = self.numberOfContours + 1
|
|
if self.numberOfContours < 0:
|
|
raise ttLib.TTLibError, "can't mix composites and contours in glyph"
|
|
coordinates = []
|
|
flags = []
|
|
for element in content:
|
|
if type(element) <> TupleType:
|
|
continue
|
|
name, attrs, content = element
|
|
if name <> "pt":
|
|
continue # ignore anything but "pt"
|
|
coordinates.append([safeEval(attrs["x"]), safeEval(attrs["y"])])
|
|
flags.append(not not safeEval(attrs["on"]))
|
|
coordinates = Numeric.array(coordinates, Numeric.Int16)
|
|
flags = Numeric.array(flags, Numeric.Int8)
|
|
if not hasattr(self, "coordinates"):
|
|
self.coordinates = coordinates
|
|
self.flags = flags
|
|
self.endPtsOfContours = [len(coordinates)-1]
|
|
else:
|
|
self.coordinates = Numeric.concatenate((self.coordinates, coordinates))
|
|
self.flags = Numeric.concatenate((self.flags, flags))
|
|
self.endPtsOfContours.append(len(self.coordinates)-1)
|
|
elif name == "component":
|
|
if self.numberOfContours > 0:
|
|
raise ttLib.TTLibError, "can't mix composites and contours in glyph"
|
|
self.numberOfContours = -1
|
|
if not hasattr(self, "components"):
|
|
self.components = []
|
|
component = GlyphComponent()
|
|
self.components.append(component)
|
|
component.fromXML((name, attrs, content), ttFont)
|
|
elif name == "instructions":
|
|
self.program = ttProgram.Program()
|
|
for element in content:
|
|
if type(element) <> TupleType:
|
|
continue
|
|
self.program.fromXML(element, ttFont)
|
|
|
|
def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
|
|
assert self.isComposite()
|
|
nContours = 0
|
|
nPoints = 0
|
|
for compo in self.components:
|
|
baseGlyph = glyfTable[compo.glyphName]
|
|
if baseGlyph.numberOfContours == 0:
|
|
continue
|
|
elif baseGlyph.numberOfContours > 0:
|
|
nP, nC = baseGlyph.getMaxpValues()
|
|
else:
|
|
nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues(
|
|
glyfTable, maxComponentDepth + 1)
|
|
nPoints = nPoints + nP
|
|
nContours = nContours + nC
|
|
return nPoints, nContours, maxComponentDepth
|
|
|
|
def getMaxpValues(self):
|
|
assert self.numberOfContours > 0
|
|
return len(self.coordinates), len(self.endPtsOfContours)
|
|
|
|
def decompileComponents(self, data, glyfTable):
|
|
self.components = []
|
|
more = 1
|
|
haveInstructions = 0
|
|
while more:
|
|
component = GlyphComponent()
|
|
more, haveInstr, data = component.decompile(data, glyfTable)
|
|
haveInstructions = haveInstructions | haveInstr
|
|
self.components.append(component)
|
|
if haveInstructions:
|
|
numInstructions, = struct.unpack(">h", data[:2])
|
|
data = data[2:]
|
|
self.program = ttProgram.Program()
|
|
self.program.fromBytecode(data[:numInstructions])
|
|
data = data[numInstructions:]
|
|
assert len(data) < 4, "bad composite data"
|
|
|
|
def decompileCoordinates(self, data):
|
|
endPtsOfContours = array.array("h")
|
|
endPtsOfContours.fromstring(data[:2*self.numberOfContours])
|
|
if ttLib.endian <> "big":
|
|
endPtsOfContours.byteswap()
|
|
self.endPtsOfContours = endPtsOfContours.tolist()
|
|
|
|
data = data[2*self.numberOfContours:]
|
|
|
|
instructionLength, = struct.unpack(">h", data[:2])
|
|
data = data[2:]
|
|
self.program = ttProgram.Program()
|
|
self.program.fromBytecode(data[:instructionLength])
|
|
data = data[instructionLength:]
|
|
nCoordinates = self.endPtsOfContours[-1] + 1
|
|
flags, xCoordinates, yCoordinates = \
|
|
self.decompileCoordinatesRaw(nCoordinates, data)
|
|
|
|
# fill in repetitions and apply signs
|
|
coordinates = Numeric.zeros((nCoordinates, 2), Numeric.Int16)
|
|
xIndex = 0
|
|
yIndex = 0
|
|
for i in range(nCoordinates):
|
|
flag = flags[i]
|
|
# x coordinate
|
|
if flag & flagXShort:
|
|
if flag & flagXsame:
|
|
x = xCoordinates[xIndex]
|
|
else:
|
|
x = -xCoordinates[xIndex]
|
|
xIndex = xIndex + 1
|
|
elif flag & flagXsame:
|
|
x = 0
|
|
else:
|
|
x = xCoordinates[xIndex]
|
|
xIndex = xIndex + 1
|
|
# y coordinate
|
|
if flag & flagYShort:
|
|
if flag & flagYsame:
|
|
y = yCoordinates[yIndex]
|
|
else:
|
|
y = -yCoordinates[yIndex]
|
|
yIndex = yIndex + 1
|
|
elif flag & flagYsame:
|
|
y = 0
|
|
else:
|
|
y = yCoordinates[yIndex]
|
|
yIndex = yIndex + 1
|
|
coordinates[i] = (x, y)
|
|
assert xIndex == len(xCoordinates)
|
|
assert yIndex == len(yCoordinates)
|
|
# convert relative to absolute coordinates
|
|
self.coordinates = Numeric.add.accumulate(coordinates)
|
|
# discard all flags but for "flagOnCurve"
|
|
if hasattr(Numeric, "__version__"):
|
|
self.flags = Numeric.bitwise_and(flags, flagOnCurve).astype(Numeric.Int8)
|
|
else:
|
|
self.flags = Numeric.boolean_and(flags, flagOnCurve).astype(Numeric.Int8)
|
|
|
|
def decompileCoordinatesRaw(self, nCoordinates, data):
|
|
# unpack flags and prepare unpacking of coordinates
|
|
flags = Numeric.array([0] * nCoordinates, Numeric.Int8)
|
|
# Warning: deep Python trickery going on. We use the struct module to unpack
|
|
# the coordinates. We build a format string based on the flags, so we can
|
|
# unpack the coordinates in one struct.unpack() call.
|
|
xFormat = ">" # big endian
|
|
yFormat = ">" # big endian
|
|
i = j = 0
|
|
while 1:
|
|
flag = ord(data[i])
|
|
i = i + 1
|
|
repeat = 1
|
|
if flag & flagRepeat:
|
|
repeat = ord(data[i]) + 1
|
|
i = i + 1
|
|
for k in range(repeat):
|
|
if flag & flagXShort:
|
|
xFormat = xFormat + 'B'
|
|
elif not (flag & flagXsame):
|
|
xFormat = xFormat + 'h'
|
|
if flag & flagYShort:
|
|
yFormat = yFormat + 'B'
|
|
elif not (flag & flagYsame):
|
|
yFormat = yFormat + 'h'
|
|
flags[j] = flag
|
|
j = j + 1
|
|
if j >= nCoordinates:
|
|
break
|
|
assert j == nCoordinates, "bad glyph flags"
|
|
data = data[i:]
|
|
# unpack raw coordinates, krrrrrr-tching!
|
|
xDataLen = struct.calcsize(xFormat)
|
|
yDataLen = struct.calcsize(yFormat)
|
|
if not (0 <= (len(data) - (xDataLen + yDataLen)) < 4):
|
|
raise ttLib.TTLibError, "bad glyph record (leftover bytes: %s)" % (len(data) - (xDataLen + yDataLen))
|
|
xCoordinates = struct.unpack(xFormat, data[:xDataLen])
|
|
yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
|
|
return flags, xCoordinates, yCoordinates
|
|
|
|
def compileComponents(self, glyfTable):
|
|
data = ""
|
|
lastcomponent = len(self.components) - 1
|
|
more = 1
|
|
haveInstructions = 0
|
|
for i in range(len(self.components)):
|
|
if i == lastcomponent:
|
|
haveInstructions = hasattr(self, "program")
|
|
more = 0
|
|
compo = self.components[i]
|
|
data = data + compo.compile(more, haveInstructions, glyfTable)
|
|
if haveInstructions:
|
|
instructions = self.program.getBytecode()
|
|
data = data + struct.pack(">h", len(instructions)) + instructions
|
|
return data
|
|
|
|
|
|
def compileCoordinates(self):
|
|
assert len(self.coordinates) == len(self.flags)
|
|
data = ""
|
|
endPtsOfContours = array.array("h", self.endPtsOfContours)
|
|
if ttLib.endian <> "big":
|
|
endPtsOfContours.byteswap()
|
|
data = data + endPtsOfContours.tostring()
|
|
instructions = self.program.getBytecode()
|
|
data = data + struct.pack(">h", len(instructions)) + instructions
|
|
nCoordinates = len(self.coordinates)
|
|
|
|
# make a copy
|
|
coordinates = self.coordinates.astype(self.coordinates.typecode())
|
|
# absolute to relative coordinates
|
|
coordinates[1:] = Numeric.subtract(coordinates[1:], coordinates[:-1])
|
|
flags = self.flags
|
|
compressedflags = []
|
|
xPoints = []
|
|
yPoints = []
|
|
xFormat = ">"
|
|
yFormat = ">"
|
|
lastflag = None
|
|
repeat = 0
|
|
for i in range(len(coordinates)):
|
|
# Oh, the horrors of TrueType
|
|
flag = self.flags[i]
|
|
x, y = coordinates[i]
|
|
# do x
|
|
if x == 0:
|
|
flag = flag | flagXsame
|
|
elif -255 <= x <= 255:
|
|
flag = flag | flagXShort
|
|
if x > 0:
|
|
flag = flag | flagXsame
|
|
else:
|
|
x = -x
|
|
xPoints.append(x)
|
|
xFormat = xFormat + 'B'
|
|
else:
|
|
xPoints.append(x)
|
|
xFormat = xFormat + 'h'
|
|
# do y
|
|
if y == 0:
|
|
flag = flag | flagYsame
|
|
elif -255 <= y <= 255:
|
|
flag = flag | flagYShort
|
|
if y > 0:
|
|
flag = flag | flagYsame
|
|
else:
|
|
y = -y
|
|
yPoints.append(y)
|
|
yFormat = yFormat + 'B'
|
|
else:
|
|
yPoints.append(y)
|
|
yFormat = yFormat + 'h'
|
|
# handle repeating flags
|
|
if flag == lastflag:
|
|
repeat = repeat + 1
|
|
if repeat == 1:
|
|
compressedflags.append(flag)
|
|
elif repeat > 1:
|
|
compressedflags[-2] = flag | flagRepeat
|
|
compressedflags[-1] = repeat
|
|
else:
|
|
compressedflags[-1] = repeat
|
|
else:
|
|
repeat = 0
|
|
compressedflags.append(flag)
|
|
lastflag = flag
|
|
data = data + array.array("B", compressedflags).tostring()
|
|
data = data + apply(struct.pack, (xFormat,)+tuple(xPoints))
|
|
data = data + apply(struct.pack, (yFormat,)+tuple(yPoints))
|
|
return data
|
|
|
|
def recalcBounds(self, glyfTable):
|
|
coordinates, endPts, flags = self.getCoordinates(glyfTable)
|
|
if len(coordinates) > 0:
|
|
self.xMin, self.yMin = Numeric.minimum.reduce(coordinates)
|
|
self.xMax, self.yMax = Numeric.maximum.reduce(coordinates)
|
|
else:
|
|
self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0)
|
|
|
|
def isComposite(self):
|
|
return self.numberOfContours == -1
|
|
|
|
def __getitem__(self, componentIndex):
|
|
if not self.isComposite():
|
|
raise ttLib.TTLibError, "can't use glyph as sequence"
|
|
return self.components[componentIndex]
|
|
|
|
def getCoordinates(self, glyfTable):
|
|
if self.numberOfContours > 0:
|
|
return self.coordinates, self.endPtsOfContours, self.flags
|
|
elif self.isComposite():
|
|
# it's a composite
|
|
allCoords = None
|
|
allFlags = None
|
|
allEndPts = None
|
|
for compo in self.components:
|
|
g = glyfTable[compo.glyphName]
|
|
coordinates, endPts, flags = g.getCoordinates(glyfTable)
|
|
if hasattr(compo, "firstPt"):
|
|
# move according to two reference points
|
|
move = allCoords[compo.firstPt] - coordinates[compo.secondPt]
|
|
else:
|
|
move = compo.x, compo.y
|
|
|
|
if not hasattr(compo, "transform"):
|
|
if len(coordinates) > 0:
|
|
coordinates = coordinates + move # I love NumPy!
|
|
else:
|
|
apple_way = compo.flags & SCALED_COMPONENT_OFFSET
|
|
ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
|
|
assert not (apple_way and ms_way)
|
|
if not (apple_way or ms_way):
|
|
scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT # see top of this file
|
|
else:
|
|
scale_component_offset = apple_way
|
|
if scale_component_offset:
|
|
# the Apple way: first move, then scale (ie. scale the component offset)
|
|
coordinates = coordinates + move
|
|
coordinates = Numeric.dot(coordinates, compo.transform)
|
|
else:
|
|
# the MS way: first scale, then move
|
|
coordinates = Numeric.dot(coordinates, compo.transform)
|
|
coordinates = coordinates + move
|
|
# due to the transformation the coords. are now floats;
|
|
# round them off nicely, and cast to short
|
|
coordinates = Numeric.floor(coordinates + 0.5).astype(Numeric.Int16)
|
|
if allCoords is None or len(allCoords) == 0:
|
|
allCoords = coordinates
|
|
allEndPts = endPts
|
|
allFlags = flags
|
|
else:
|
|
allEndPts = allEndPts + (Numeric.array(endPts) + len(allCoords)).tolist()
|
|
if len(coordinates) > 0:
|
|
allCoords = Numeric.concatenate((allCoords, coordinates))
|
|
allFlags = Numeric.concatenate((allFlags, flags))
|
|
return allCoords, allEndPts, allFlags
|
|
else:
|
|
return Numeric.array([], Numeric.Int16), [], Numeric.array([], Numeric.Int8)
|
|
|
|
def __cmp__(self, other):
|
|
if self.numberOfContours <= 0:
|
|
return cmp(self.__dict__, other.__dict__)
|
|
else:
|
|
if cmp(len(self.coordinates), len(other.coordinates)):
|
|
return 1
|
|
ctest = Numeric.alltrue(Numeric.alltrue(Numeric.equal(self.coordinates, other.coordinates)))
|
|
ftest = Numeric.alltrue(Numeric.equal(self.flags, other.flags))
|
|
if not ctest or not ftest:
|
|
return 1
|
|
return (
|
|
cmp(self.endPtsOfContours, other.endPtsOfContours) or
|
|
cmp(self.program, other.instructions)
|
|
)
|
|
|
|
|
|
class GlyphComponent:
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def getComponentInfo(self):
|
|
"""Return the base glyph name and a transform."""
|
|
# XXX Ignoring self.firstPt & self.lastpt for now: I need to implement
|
|
# something equivalent in fontTools.objects.glyph (I'd rather not
|
|
# convert it to an absolute offset, since it is valuable information).
|
|
# This method will now raise "AttributeError: x" on glyphs that use
|
|
# this TT feature.
|
|
if hasattr(self, "transform"):
|
|
[[xx, xy], [yx, yy]] = self.transform
|
|
trans = (xx, xy, yx, yy, self.x, self.y)
|
|
else:
|
|
trans = (1, 0, 0, 1, self.x, self.y)
|
|
return self.glyphName, trans
|
|
|
|
def decompile(self, data, glyfTable):
|
|
flags, glyphID = struct.unpack(">HH", data[:4])
|
|
self.flags = int(flags)
|
|
glyphID = int(glyphID)
|
|
self.glyphName = glyfTable.getGlyphName(int(glyphID))
|
|
#print ">>", reprflag(self.flags)
|
|
data = data[4:]
|
|
|
|
if self.flags & ARG_1_AND_2_ARE_WORDS:
|
|
if self.flags & ARGS_ARE_XY_VALUES:
|
|
self.x, self.y = struct.unpack(">hh", data[:4])
|
|
else:
|
|
x, y = struct.unpack(">HH", data[:4])
|
|
self.firstPt, self.secondPt = int(x), int(y)
|
|
data = data[4:]
|
|
else:
|
|
if self.flags & ARGS_ARE_XY_VALUES:
|
|
self.x, self.y = struct.unpack(">bb", data[:2])
|
|
else:
|
|
x, y = struct.unpack(">BB", data[:2])
|
|
self.firstPt, self.secondPt = int(x), int(y)
|
|
data = data[2:]
|
|
|
|
if self.flags & WE_HAVE_A_SCALE:
|
|
scale, = struct.unpack(">h", data[:2])
|
|
self.transform = Numeric.array(
|
|
[[scale, 0], [0, scale]]) / float(0x4000) # fixed 2.14
|
|
data = data[2:]
|
|
elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
|
|
xscale, yscale = struct.unpack(">hh", data[:4])
|
|
self.transform = Numeric.array(
|
|
[[xscale, 0], [0, yscale]]) / float(0x4000) # fixed 2.14
|
|
data = data[4:]
|
|
elif self.flags & WE_HAVE_A_TWO_BY_TWO:
|
|
(xscale, scale01,
|
|
scale10, yscale) = struct.unpack(">hhhh", data[:8])
|
|
self.transform = Numeric.array(
|
|
[[xscale, scale01], [scale10, yscale]]) / float(0x4000) # fixed 2.14
|
|
data = data[8:]
|
|
more = self.flags & MORE_COMPONENTS
|
|
haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
|
|
self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
|
|
SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
|
|
NON_OVERLAPPING)
|
|
return more, haveInstructions, data
|
|
|
|
def compile(self, more, haveInstructions, glyfTable):
|
|
data = ""
|
|
|
|
# reset all flags we will calculate ourselves
|
|
flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
|
|
SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
|
|
NON_OVERLAPPING)
|
|
if more:
|
|
flags = flags | MORE_COMPONENTS
|
|
if haveInstructions:
|
|
flags = flags | WE_HAVE_INSTRUCTIONS
|
|
|
|
if hasattr(self, "firstPt"):
|
|
if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
|
|
data = data + struct.pack(">BB", self.firstPt, self.secondPt)
|
|
else:
|
|
data = data + struct.pack(">HH", self.firstPt, self.secondPt)
|
|
flags = flags | ARG_1_AND_2_ARE_WORDS
|
|
else:
|
|
flags = flags | ARGS_ARE_XY_VALUES
|
|
if (-128 <= self.x <= 127) and (-128 <= self.y <= 127):
|
|
data = data + struct.pack(">bb", self.x, self.y)
|
|
else:
|
|
data = data + struct.pack(">hh", self.x, self.y)
|
|
flags = flags | ARG_1_AND_2_ARE_WORDS
|
|
|
|
if hasattr(self, "transform"):
|
|
# XXX needs more testing
|
|
transform = Numeric.floor(self.transform * 0x4000 + 0.5)
|
|
if transform[0][1] or transform[1][0]:
|
|
flags = flags | WE_HAVE_A_TWO_BY_TWO
|
|
data = data + struct.pack(">hhhh",
|
|
transform[0][0], transform[0][1],
|
|
transform[1][0], transform[1][1])
|
|
elif transform[0][0] <> transform[1][1]:
|
|
flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
|
|
data = data + struct.pack(">hh",
|
|
transform[0][0], transform[1][1])
|
|
else:
|
|
flags = flags | WE_HAVE_A_SCALE
|
|
data = data + struct.pack(">h",
|
|
transform[0][0])
|
|
|
|
glyphID = glyfTable.getGlyphID(self.glyphName)
|
|
return struct.pack(">HH", flags, glyphID) + data
|
|
|
|
def toXML(self, writer, ttFont):
|
|
attrs = [("glyphName", self.glyphName)]
|
|
if not hasattr(self, "firstPt"):
|
|
attrs = attrs + [("x", self.x), ("y", self.y)]
|
|
else:
|
|
attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)]
|
|
|
|
if hasattr(self, "transform"):
|
|
# XXX needs more testing
|
|
transform = self.transform
|
|
if transform[0][1] or transform[1][0]:
|
|
attrs = attrs + [
|
|
("scalex", transform[0][0]), ("scale01", transform[0][1]),
|
|
("scale10", transform[1][0]), ("scaley", transform[1][1]),
|
|
]
|
|
elif transform[0][0] <> transform[1][1]:
|
|
attrs = attrs + [
|
|
("scalex", transform[0][0]), ("scaley", transform[1][1]),
|
|
]
|
|
else:
|
|
attrs = attrs + [("scale", transform[0][0])]
|
|
attrs = attrs + [("flags", hex(self.flags))]
|
|
writer.simpletag("component", attrs)
|
|
writer.newline()
|
|
|
|
def fromXML(self, (name, attrs, content), ttFont):
|
|
self.glyphName = attrs["glyphName"]
|
|
if attrs.has_key("firstPt"):
|
|
self.firstPt = safeEval(attrs["firstPt"])
|
|
self.secondPt = safeEval(attrs["secondPt"])
|
|
else:
|
|
self.x = safeEval(attrs["x"])
|
|
self.y = safeEval(attrs["y"])
|
|
if attrs.has_key("scale01"):
|
|
scalex = safeEval(attrs["scalex"])
|
|
scale01 = safeEval(attrs["scale01"])
|
|
scale10 = safeEval(attrs["scale10"])
|
|
scaley = safeEval(attrs["scaley"])
|
|
self.transform = Numeric.array([[scalex, scale01], [scale10, scaley]])
|
|
elif attrs.has_key("scalex"):
|
|
scalex = safeEval(attrs["scalex"])
|
|
scaley = safeEval(attrs["scaley"])
|
|
self.transform = Numeric.array([[scalex, 0], [0, scaley]])
|
|
elif attrs.has_key("scale"):
|
|
scale = safeEval(attrs["scale"])
|
|
self.transform = Numeric.array([[scale, 0], [0, scale]])
|
|
self.flags = safeEval(attrs["flags"])
|
|
|
|
def __cmp__(self, other):
|
|
if hasattr(self, "transform"):
|
|
if Numeric.alltrue(Numeric.equal(self.transform, other.transform)):
|
|
selfdict = self.__dict__.copy()
|
|
otherdict = other.__dict__.copy()
|
|
del selfdict["transform"]
|
|
del otherdict["transform"]
|
|
return cmp(selfdict, otherdict)
|
|
else:
|
|
return 1
|
|
else:
|
|
return cmp(self.__dict__, other.__dict__)
|
|
|
|
|
|
def reprflag(flag):
|
|
bin = ""
|
|
if type(flag) == StringType:
|
|
flag = ord(flag)
|
|
while flag:
|
|
if flag & 0x01:
|
|
bin = "1" + bin
|
|
else:
|
|
bin = "0" + bin
|
|
flag = flag >> 1
|
|
bin = (14 - len(bin)) * "0" + bin
|
|
return bin
|
|
|