"""_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 sys import struct, sstruct import DefaultTable from fontTools import ttLib from fontTools.misc.textTools import safeEval, readHex import ttProgram import array import numpy from types import StringType, TupleType import warnings class table__g_l_y_f(DefaultTable.DefaultTable): def decompile(self, data, ttFont): loca = ttFont['loca'] last = int(loca[0]) noname = 0 self.glyphs = {} self.glyphOrder = glyphOrder = ttFont.getGlyphOrder() for i in range(0, len(loca)-1): try: glyphName = glyphOrder[i] except IndexError: noname = noname + 1 glyphName = 'ttxautoglyph%s' % i next = int(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 if len(data) > next: warnings.warn("too much 'glyf' table data") if noname: warnings.warn('%s glyphs have no name' % i) 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": if self.numberOfContours < 0: raise ttLib.TTLibError, "can't mix composites and contours in glyph" self.numberOfContours = self.numberOfContours + 1 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 = numpy.array(coordinates, numpy.int16) flags = numpy.array(flags, numpy.int8) if not hasattr(self, "coordinates"): self.coordinates = coordinates self.flags = flags self.endPtsOfContours = [len(coordinates)-1] else: self.coordinates = numpy.concatenate((self.coordinates, coordinates)) self.flags = numpy.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 sys.byteorder <> "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 = numpy.zeros((nCoordinates, 2), numpy.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 = numpy.add.accumulate(coordinates) # discard all flags but for "flagOnCurve" self.flags = numpy.bitwise_and(flags, flagOnCurve).astype(numpy.int8) def decompileCoordinatesRaw(self, nCoordinates, data): # unpack flags and prepare unpacking of coordinates flags = numpy.array([0] * nCoordinates, numpy.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 len(data) - (xDataLen + yDataLen) >= 4: warnings.warn("too much glyph data: %d excess bytes" % (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 sys.byteorder <> "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 = numpy.array(self.coordinates) # absolute to relative coordinates coordinates[1:] = numpy.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() xPoints = map(int, xPoints) # work around numpy vs. struct >= 2.5 bug yPoints = map(int, yPoints) 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 = numpy.minimum.reduce(coordinates) self.xMax, self.yMax = numpy.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 = numpy.dot(coordinates, compo.transform) else: # the MS way: first scale, then move coordinates = numpy.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 = numpy.floor(coordinates + 0.5).astype(numpy.int16) if allCoords is None or len(allCoords) == 0: allCoords = coordinates allEndPts = endPts allFlags = flags else: allEndPts = allEndPts + (numpy.array(endPts) + len(allCoords)).tolist() if len(coordinates) > 0: allCoords = numpy.concatenate((allCoords, coordinates)) allFlags = numpy.concatenate((allFlags, flags)) return allCoords, allEndPts, allFlags else: return numpy.array([], numpy.int16), [], numpy.array([], numpy.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 = numpy.alltrue(numpy.alltrue(numpy.equal(self.coordinates, other.coordinates))) ftest = numpy.alltrue(numpy.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 = numpy.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 = numpy.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 = numpy.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 = numpy.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 = numpy.array([[scalex, scale01], [scale10, scaley]]) elif attrs.has_key("scalex"): scalex = safeEval(attrs["scalex"]) scaley = safeEval(attrs["scaley"]) self.transform = numpy.array([[scalex, 0], [0, scaley]]) elif attrs.has_key("scale"): scale = safeEval(attrs["scale"]) self.transform = numpy.array([[scale, 0], [0, scale]]) self.flags = safeEval(attrs["flags"]) def __cmp__(self, other): if hasattr(self, "transform"): if numpy.alltrue(numpy.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