"""_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 from fontTools.misc import sstruct import DefaultTable from fontTools import ttLib from fontTools.misc.textTools import safeEval, readHex from fontTools.misc.arrayTools import calcBounds import ttProgram import array 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 = GlyphCoordinates() 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"])) flags = array.array("B", flags) if not hasattr(self, "coordinates"): self.coordinates = coordinates self.flags = flags self.endPtsOfContours = [len(coordinates)-1] else: self.coordinates.extend (coordinates) self.flags.extend(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 self.coordinates = coordinates = GlyphCoordinates.zeros(nCoordinates) 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) coordinates.relativeToAbsolute() # discard all flags but for "flagOnCurve" self.flags = array.array("B", (f & flagOnCurve for f in flags)) def decompileCoordinatesRaw(self, nCoordinates, data): # unpack flags and prepare unpacking of coordinates flags = array.array("B", [0] * nCoordinates) # 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) coordinates = self.coordinates.copy() coordinates.absoluteToRelative() flags = self.flags compressedflags = [] xPoints = [] yPoints = [] xFormat = ">" yFormat = ">" lastflag = None repeat = 0 for i in range(len(coordinates)): # Oh, the horrors of TrueType flag = 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 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, self.xMax, self.yMax = calcBounds(coordinates) else: self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0) def isComposite(self): """Can be called on compact or expanded glyph.""" if hasattr(self, "data"): return struct.unpack(">h", self.data[:2])[0] == -1 else: 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 = GlyphCoordinates() allFlags = array.array("B") allEndPts = [] 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 x1,y1 = allCoords[compo.firstPt] x2,y2 = coordinates[compo.secondPt] move = x1-x2, y1-y2 else: move = compo.x, compo.y if not hasattr(compo, "transform"): coordinates.translate(move) 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.translate(move) coordinates.transform(compo.transform) else: # the MS way: first scale, then move coordinates.transform(compo.transform) coordinates.translate(move) offset = len(allCoords) allEndPts.extend(e + offset for e in endPts) allCoords.extend(coordinates) allFlags.extend(flags) return allCoords, allEndPts, allFlags else: return GlyphCoordinates(), [], array.array("B") def getComponents(self, glyfTable): if not hasattr(self, "data"): if self.isComposite(): return self.components else: return [] # Extract components without expanding glyph if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0: return [] # Not composite data = self.data i = 10 components = [] more = 1 while more: flags, glyphID = struct.unpack(">HH", data[i:i+4]) i += 4 flags = int(flags) components.append(glyfTable.getGlyphName(int(glyphID))) if flags & ARG_1_AND_2_ARE_WORDS: i += 4 else: i += 2 if flags & WE_HAVE_A_SCALE: i += 2 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8 more = flags & MORE_COMPONENTS return components def removeHinting(self): if not hasattr(self, "data"): self.program = ttLib.tables.ttProgram.Program() self.program.fromBytecode([]) return # Remove instructions without expanding glyph if not self.data: return numContours = struct.unpack(">h", self.data[:2])[0] data = array.array("B", self.data) i = 10 if numContours >= 0: i += 2 * numContours # endPtsOfContours nCoordinates = ((data[i-2] << 8) | data[i-1]) + 1 instructionLen = (data[i] << 8) | data[i+1] # Zero it data[i] = data [i+1] = 0 i += 2 if instructionLen: # Splice it out data = data[:i] + data[i+instructionLen:] if instructionLen % 4: # We now have to go ahead and drop # the old padding. Otherwise with # padding we have to add, we may # end up with more than 3 bytes of # padding. coordBytes = 0 j = 0 while 1: flag = data[i] i = i + 1 repeat = 1 if flag & flagRepeat: repeat = data[i] + 1 i = i + 1 xBytes = yBytes = 0 if flag & flagXShort: xBytes = 1 elif not (flag & flagXsame): xBytes = 2 if flag & flagYShort: yBytes = 1 elif not (flag & flagYsame): yBytes = 2 coordBytes += (xBytes + yBytes) * repeat j += repeat if j >= nCoordinates: break assert j == nCoordinates, "bad glyph flags" data = data[:i + coordBytes] else: more = 1 while more: flags =(data[i] << 8) | data[i+1] # Turn instruction flag off flags &= ~WE_HAVE_INSTRUCTIONS data[i+0] = flags >> 8 data[i+1] = flags & 0xFF i += 4 flags = int(flags) if flags & ARG_1_AND_2_ARE_WORDS: i += 4 else: i += 2 if flags & WE_HAVE_A_SCALE: i += 2 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8 more = flags & MORE_COMPONENTS # Cut off data = data[:i] data = data.tostring() if len(data) % 4: # add pad bytes nPadBytes = 4 - (len(data) % 4) data = data + "\0" * nPadBytes self.data = data def __cmp__(self, other): if type(self) != type(other) or \ self.__class__ != other.__class__: return cmp(id(self), id(other)) return cmp(self.__dict__, other.__dict__) 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:] x4000 = float(0x4000) 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 = [[scale/x4000, 0], [0, scale/x4000]] # 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 = [[xscale/x4000, 0], [0, yscale/x4000]] # 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 = [[xscale/x4000, scale01/x4000], [scale10/x4000, yscale/x4000]] # 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"): transform = [[int(x * 0x4000 + .5) for x in row] for row in self.transform] 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"): 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 = [[scalex, scale01], [scale10, scaley]] elif attrs.has_key("scalex"): scalex = safeEval(attrs["scalex"]) scaley = safeEval(attrs["scaley"]) self.transform = [[scalex, 0], [0, scaley]] elif attrs.has_key("scale"): scale = safeEval(attrs["scale"]) self.transform = [[scale, 0], [0, scale]] self.flags = safeEval(attrs["flags"]) def __cmp__(self, other): if type(self) != type(other) or \ self.__class__ != other.__class__: return cmp(id(self), id(other)) return cmp(self.__dict__, other.__dict__) class GlyphCoordinates: def __init__(self, iterable=[]): self._a = array.array("h") self.extend(iterable) @staticmethod def zeros(count): return GlyphCoordinates([(0,0)] * count) def copy(self): c = GlyphCoordinates() c._a.extend(self._a) return c def __len__(self): return len(self._a) / 2 def __getitem__(self, k): if isinstance(k, slice): indices = xrange(k.indices(len(self))) return [self[i] for i in indices] return self._a[2*k],self._a[2*k+1] def __setitem__(self, k, v): if isinstance(k, slice): indices = xrange(k.indices(len(self))) for j,i in enumerate(indices): self[i] = v[j] self._a[2*k],self._a[2*k+1] = v def __repr__(self): return 'GlyphCoordinates(['+','.join(str(c) for c in self)+'])' def append(self, (x,y)): self._a.extend((x,y)) def extend(self, iterable): for x,y in iterable: self._a.extend((x,y)) def relativeToAbsolute(self): a = self._a x,y = 0,0 for i in range(len(a) / 2): a[2*i ] = x = a[2*i ] + x a[2*i+1] = y = a[2*i+1] + y def absoluteToRelative(self): a = self._a x,y = 0,0 for i in range(len(a) / 2): dx = a[2*i ] - x dy = a[2*i+1] - y x = a[2*i ] y = a[2*i+1] a[2*i ] = dx a[2*i+1] = dy def translate(self, (x,y)): a = self._a for i in range(len(a) / 2): a[2*i ] += x a[2*i+1] += y def transform(self, t): a = self._a for i in range(len(a) / 2): x = a[2*i ] y = a[2*i+1] a[2*i ] = int(.5 + x * t[0][0] + y * t[1][0]) a[2*i+1] = int(.5 + x * t[0][1] + y * t[1][1]) 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