_g_l_y_f.py revision ff6a25cdb99c86ce7d60c6f6780178fdbfa8674b
1"""_g_l_y_f.py -- Converter classes for the 'glyf' table.""" 2 3 4# 5# The Apple and MS rasterizers behave differently for 6# scaled composite components: one does scale first and then translate 7# and the other does it vice versa. MS defined some flags to indicate 8# the difference, but it seems nobody actually _sets_ those flags. 9# 10# Funny thing: Apple seems to _only_ do their thing in the 11# WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE 12# (eg. Charcoal)... 13# 14SCALE_COMPONENT_OFFSET_DEFAULT = 0 # 0 == MS, 1 == Apple 15 16 17import struct, sstruct 18import DefaultTable 19from fontTools import ttLib 20from fontTools.misc.textTools import safeEval, readHex 21import ttProgram 22import array 23import Numeric 24from types import StringType, TupleType 25 26 27class table__g_l_y_f(DefaultTable.DefaultTable): 28 29 def decompile(self, data, ttFont): 30 loca = ttFont['loca'] 31 last = loca[0] 32 self.glyphs = {} 33 self.glyphOrder = glyphOrder = ttFont.getGlyphOrder() 34 for i in range(0, len(loca)-1): 35 glyphName = glyphOrder[i] 36 next = loca[i+1] 37 glyphdata = data[last:next] 38 if len(glyphdata) <> (next - last): 39 raise ttLib.TTLibError, "not enough 'glyf' table data" 40 glyph = Glyph(glyphdata) 41 self.glyphs[glyphName] = glyph 42 last = next 43 # this should become a warning: 44 #if len(data) > next: 45 # raise ttLib.TTLibError, "too much 'glyf' table data" 46 47 def compile(self, ttFont): 48 if not hasattr(self, "glyphOrder"): 49 self.glyphOrder = ttFont.getGlyphOrder() 50 import string 51 locations = [] 52 currentLocation = 0 53 dataList = [] 54 recalcBBoxes = ttFont.recalcBBoxes 55 for glyphName in self.glyphOrder: 56 glyph = self.glyphs[glyphName] 57 glyphData = glyph.compile(self, recalcBBoxes) 58 locations.append(currentLocation) 59 currentLocation = currentLocation + len(glyphData) 60 dataList.append(glyphData) 61 locations.append(currentLocation) 62 data = string.join(dataList, "") 63 ttFont['loca'].set(locations) 64 ttFont['maxp'].numGlyphs = len(self.glyphs) 65 return data 66 67 def toXML(self, writer, ttFont, progress=None): 68 writer.newline() 69 glyphNames = ttFont.getGlyphNames() 70 writer.comment("The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler.") 71 writer.newline() 72 writer.newline() 73 counter = 0 74 progressStep = 10 75 numGlyphs = len(glyphNames) 76 for glyphName in glyphNames: 77 if not counter % progressStep and progress is not None: 78 progress.setLabel("Dumping 'glyf' table... (%s)" % glyphName) 79 progress.increment(progressStep / float(numGlyphs)) 80 counter = counter + 1 81 glyph = self[glyphName] 82 if glyph.numberOfContours: 83 writer.begintag('TTGlyph', [ 84 ("name", glyphName), 85 ("xMin", glyph.xMin), 86 ("yMin", glyph.yMin), 87 ("xMax", glyph.xMax), 88 ("yMax", glyph.yMax), 89 ]) 90 writer.newline() 91 glyph.toXML(writer, ttFont) 92 writer.endtag('TTGlyph') 93 writer.newline() 94 else: 95 writer.simpletag('TTGlyph', name=glyphName) 96 writer.comment("contains no outline data") 97 writer.newline() 98 writer.newline() 99 100 def fromXML(self, (name, attrs, content), ttFont): 101 if name <> "TTGlyph": 102 return 103 if not hasattr(self, "glyphs"): 104 self.glyphs = {} 105 if not hasattr(self, "glyphOrder"): 106 self.glyphOrder = ttFont.getGlyphOrder() 107 glyphName = attrs["name"] 108 if ttFont.verbose: 109 ttLib.debugmsg("unpacking glyph '%s'" % glyphName) 110 glyph = Glyph() 111 for attr in ['xMin', 'yMin', 'xMax', 'yMax']: 112 setattr(glyph, attr, safeEval(attrs.get(attr, '0'))) 113 self.glyphs[glyphName] = glyph 114 for element in content: 115 if type(element) <> TupleType: 116 continue 117 glyph.fromXML(element, ttFont) 118 if not ttFont.recalcBBoxes: 119 glyph.compact(self, 0) 120 121 def setGlyphOrder(self, glyphOrder): 122 self.glyphOrder = glyphOrder 123 124 def getGlyphName(self, glyphID): 125 return self.glyphOrder[glyphID] 126 127 def getGlyphID(self, glyphName): 128 # XXX optimize with reverse dict!!! 129 return self.glyphOrder.index(glyphName) 130 131 def __getitem__(self, glyphName): 132 glyph = self.glyphs[glyphName] 133 glyph.expand(self) 134 return glyph 135 136 def __setitem__(self, glyphName, glyph): 137 self.glyphs[glyphName] = glyph 138 if glyphName not in self.glyphOrder: 139 self.glyphOrder.append(glyphName) 140 141 def __delitem__(self, glyphName): 142 del self.glyphs[glyphName] 143 self.glyphOrder.remove(glyphName) 144 145 def __len__(self): 146 assert len(self.glyphOrder) == len(self.glyphs) 147 return len(self.glyphs) 148 149 150glyphHeaderFormat = """ 151 > # big endian 152 numberOfContours: h 153 xMin: h 154 yMin: h 155 xMax: h 156 yMax: h 157""" 158 159# flags 160flagOnCurve = 0x01 161flagXShort = 0x02 162flagYShort = 0x04 163flagRepeat = 0x08 164flagXsame = 0x10 165flagYsame = 0x20 166flagReserved1 = 0x40 167flagReserved2 = 0x80 168 169 170ARG_1_AND_2_ARE_WORDS = 0x0001 # if set args are words otherwise they are bytes 171ARGS_ARE_XY_VALUES = 0x0002 # if set args are xy values, otherwise they are points 172ROUND_XY_TO_GRID = 0x0004 # for the xy values if above is true 173WE_HAVE_A_SCALE = 0x0008 # Sx = Sy, otherwise scale == 1.0 174NON_OVERLAPPING = 0x0010 # set to same value for all components (obsolete!) 175MORE_COMPONENTS = 0x0020 # indicates at least one more glyph after this one 176WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Sx, Sy 177WE_HAVE_A_TWO_BY_TWO = 0x0080 # t00, t01, t10, t11 178WE_HAVE_INSTRUCTIONS = 0x0100 # instructions follow 179USE_MY_METRICS = 0x0200 # apply these metrics to parent glyph 180OVERLAP_COMPOUND = 0x0400 # used by Apple in GX fonts 181SCALED_COMPONENT_OFFSET = 0x0800 # composite designed to have the component offset scaled (designed for Apple) 182UNSCALED_COMPONENT_OFFSET = 0x1000 # composite designed not to have the component offset scaled (designed for MS) 183 184 185class Glyph: 186 187 def __init__(self, data=""): 188 if not data: 189 # empty char 190 self.numberOfContours = 0 191 return 192 self.data = data 193 194 def compact(self, glyfTable, recalcBBoxes=1): 195 data = self.compile(glyfTable, recalcBBoxes) 196 self.__dict__.clear() 197 self.data = data 198 199 def expand(self, glyfTable): 200 if not hasattr(self, "data"): 201 # already unpacked 202 return 203 if not self.data: 204 # empty char 205 self.numberOfContours = 0 206 return 207 dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self) 208 del self.data 209 if self.isComposite(): 210 self.decompileComponents(data, glyfTable) 211 else: 212 self.decompileCoordinates(data) 213 214 def compile(self, glyfTable, recalcBBoxes=1): 215 if hasattr(self, "data"): 216 return self.data 217 if self.numberOfContours == 0: 218 return "" 219 if recalcBBoxes: 220 self.recalcBounds(glyfTable) 221 data = sstruct.pack(glyphHeaderFormat, self) 222 if self.isComposite(): 223 data = data + self.compileComponents(glyfTable) 224 else: 225 data = data + self.compileCoordinates() 226 # From the spec: "Note that the local offsets should be word-aligned" 227 # From a later MS spec: "Note that the local offsets should be long-aligned" 228 # Let's be modern and align on 4-byte boundaries. 229 if len(data) % 4: 230 # add pad bytes 231 nPadBytes = 4 - (len(data) % 4) 232 data = data + "\0" * nPadBytes 233 return data 234 235 def toXML(self, writer, ttFont): 236 if self.isComposite(): 237 for compo in self.components: 238 compo.toXML(writer, ttFont) 239 if hasattr(self, "program"): 240 writer.begintag("instructions") 241 self.program.toXML(writer, ttFont) 242 writer.endtag("instructions") 243 writer.newline() 244 else: 245 last = 0 246 for i in range(self.numberOfContours): 247 writer.begintag("contour") 248 writer.newline() 249 for j in range(last, self.endPtsOfContours[i] + 1): 250 writer.simpletag("pt", [ 251 ("x", self.coordinates[j][0]), 252 ("y", self.coordinates[j][1]), 253 ("on", self.flags[j] & flagOnCurve)]) 254 writer.newline() 255 last = self.endPtsOfContours[i] + 1 256 writer.endtag("contour") 257 writer.newline() 258 if self.numberOfContours: 259 writer.begintag("instructions") 260 self.program.toXML(writer, ttFont) 261 writer.endtag("instructions") 262 writer.newline() 263 264 def fromXML(self, (name, attrs, content), ttFont): 265 if name == "contour": 266 self.numberOfContours = self.numberOfContours + 1 267 if self.numberOfContours < 0: 268 raise ttLib.TTLibError, "can't mix composites and contours in glyph" 269 coordinates = [] 270 flags = [] 271 for element in content: 272 if type(element) <> TupleType: 273 continue 274 name, attrs, content = element 275 if name <> "pt": 276 continue # ignore anything but "pt" 277 coordinates.append([safeEval(attrs["x"]), safeEval(attrs["y"])]) 278 flags.append(not not safeEval(attrs["on"])) 279 coordinates = Numeric.array(coordinates, Numeric.Int16) 280 flags = Numeric.array(flags, Numeric.Int8) 281 if not hasattr(self, "coordinates"): 282 self.coordinates = coordinates 283 self.flags = flags 284 self.endPtsOfContours = [len(coordinates)-1] 285 else: 286 self.coordinates = Numeric.concatenate((self.coordinates, coordinates)) 287 self.flags = Numeric.concatenate((self.flags, flags)) 288 self.endPtsOfContours.append(len(self.coordinates)-1) 289 elif name == "component": 290 if self.numberOfContours > 0: 291 raise ttLib.TTLibError, "can't mix composites and contours in glyph" 292 self.numberOfContours = -1 293 if not hasattr(self, "components"): 294 self.components = [] 295 component = GlyphComponent() 296 self.components.append(component) 297 component.fromXML((name, attrs, content), ttFont) 298 elif name == "instructions": 299 self.program = ttProgram.Program() 300 for element in content: 301 if type(element) <> TupleType: 302 continue 303 self.program.fromXML(element, ttFont) 304 305 def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1): 306 assert self.isComposite() 307 nContours = 0 308 nPoints = 0 309 for compo in self.components: 310 baseGlyph = glyfTable[compo.glyphName] 311 if baseGlyph.numberOfContours == 0: 312 continue 313 elif baseGlyph.numberOfContours > 0: 314 nP, nC = baseGlyph.getMaxpValues() 315 else: 316 nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues( 317 glyfTable, maxComponentDepth + 1) 318 nPoints = nPoints + nP 319 nContours = nContours + nC 320 return nPoints, nContours, maxComponentDepth 321 322 def getMaxpValues(self): 323 assert self.numberOfContours > 0 324 return len(self.coordinates), len(self.endPtsOfContours) 325 326 def decompileComponents(self, data, glyfTable): 327 self.components = [] 328 more = 1 329 haveInstructions = 0 330 while more: 331 component = GlyphComponent() 332 more, haveInstr, data = component.decompile(data, glyfTable) 333 haveInstructions = haveInstructions | haveInstr 334 self.components.append(component) 335 if haveInstructions: 336 numInstructions, = struct.unpack(">h", data[:2]) 337 data = data[2:] 338 self.program = ttProgram.Program() 339 self.program.fromBytecode(data[:numInstructions]) 340 data = data[numInstructions:] 341 assert len(data) < 4, "bad composite data" 342 343 def decompileCoordinates(self, data): 344 endPtsOfContours = array.array("h") 345 endPtsOfContours.fromstring(data[:2*self.numberOfContours]) 346 if ttLib.endian <> "big": 347 endPtsOfContours.byteswap() 348 self.endPtsOfContours = endPtsOfContours.tolist() 349 350 data = data[2*self.numberOfContours:] 351 352 instructionLength, = struct.unpack(">h", data[:2]) 353 data = data[2:] 354 self.program = ttProgram.Program() 355 self.program.fromBytecode(data[:instructionLength]) 356 data = data[instructionLength:] 357 nCoordinates = self.endPtsOfContours[-1] + 1 358 flags, xCoordinates, yCoordinates = \ 359 self.decompileCoordinatesRaw(nCoordinates, data) 360 361 # fill in repetitions and apply signs 362 coordinates = Numeric.zeros((nCoordinates, 2), Numeric.Int16) 363 xIndex = 0 364 yIndex = 0 365 for i in range(nCoordinates): 366 flag = flags[i] 367 # x coordinate 368 if flag & flagXShort: 369 if flag & flagXsame: 370 x = xCoordinates[xIndex] 371 else: 372 x = -xCoordinates[xIndex] 373 xIndex = xIndex + 1 374 elif flag & flagXsame: 375 x = 0 376 else: 377 x = xCoordinates[xIndex] 378 xIndex = xIndex + 1 379 # y coordinate 380 if flag & flagYShort: 381 if flag & flagYsame: 382 y = yCoordinates[yIndex] 383 else: 384 y = -yCoordinates[yIndex] 385 yIndex = yIndex + 1 386 elif flag & flagYsame: 387 y = 0 388 else: 389 y = yCoordinates[yIndex] 390 yIndex = yIndex + 1 391 coordinates[i] = (x, y) 392 assert xIndex == len(xCoordinates) 393 assert yIndex == len(yCoordinates) 394 # convert relative to absolute coordinates 395 self.coordinates = Numeric.add.accumulate(coordinates) 396 # discard all flags but for "flagOnCurve" 397 if hasattr(Numeric, "__version__"): 398 self.flags = Numeric.bitwise_and(flags, flagOnCurve).astype(Numeric.Int8) 399 else: 400 self.flags = Numeric.boolean_and(flags, flagOnCurve).astype(Numeric.Int8) 401 402 def decompileCoordinatesRaw(self, nCoordinates, data): 403 # unpack flags and prepare unpacking of coordinates 404 flags = Numeric.array([0] * nCoordinates, Numeric.Int8) 405 # Warning: deep Python trickery going on. We use the struct module to unpack 406 # the coordinates. We build a format string based on the flags, so we can 407 # unpack the coordinates in one struct.unpack() call. 408 xFormat = ">" # big endian 409 yFormat = ">" # big endian 410 i = j = 0 411 while 1: 412 flag = ord(data[i]) 413 i = i + 1 414 repeat = 1 415 if flag & flagRepeat: 416 repeat = ord(data[i]) + 1 417 i = i + 1 418 for k in range(repeat): 419 if flag & flagXShort: 420 xFormat = xFormat + 'B' 421 elif not (flag & flagXsame): 422 xFormat = xFormat + 'h' 423 if flag & flagYShort: 424 yFormat = yFormat + 'B' 425 elif not (flag & flagYsame): 426 yFormat = yFormat + 'h' 427 flags[j] = flag 428 j = j + 1 429 if j >= nCoordinates: 430 break 431 assert j == nCoordinates, "bad glyph flags" 432 data = data[i:] 433 # unpack raw coordinates, krrrrrr-tching! 434 xDataLen = struct.calcsize(xFormat) 435 yDataLen = struct.calcsize(yFormat) 436 if not (0 <= (len(data) - (xDataLen + yDataLen)) < 4): 437 raise ttLib.TTLibError, "bad glyph record (leftover bytes: %s)" % (len(data) - (xDataLen + yDataLen)) 438 xCoordinates = struct.unpack(xFormat, data[:xDataLen]) 439 yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen]) 440 return flags, xCoordinates, yCoordinates 441 442 def compileComponents(self, glyfTable): 443 data = "" 444 lastcomponent = len(self.components) - 1 445 more = 1 446 haveInstructions = 0 447 for i in range(len(self.components)): 448 if i == lastcomponent: 449 haveInstructions = hasattr(self, "program") 450 more = 0 451 compo = self.components[i] 452 data = data + compo.compile(more, haveInstructions, glyfTable) 453 if haveInstructions: 454 instructions = self.program.getBytecode() 455 data = data + struct.pack(">h", len(instructions)) + instructions 456 return data 457 458 459 def compileCoordinates(self): 460 assert len(self.coordinates) == len(self.flags) 461 data = "" 462 endPtsOfContours = array.array("h", self.endPtsOfContours) 463 if ttLib.endian <> "big": 464 endPtsOfContours.byteswap() 465 data = data + endPtsOfContours.tostring() 466 instructions = self.program.getBytecode() 467 data = data + struct.pack(">h", len(instructions)) + instructions 468 nCoordinates = len(self.coordinates) 469 470 # make a copy 471 coordinates = self.coordinates.astype(self.coordinates.typecode()) 472 # absolute to relative coordinates 473 coordinates[1:] = Numeric.subtract(coordinates[1:], coordinates[:-1]) 474 flags = self.flags 475 compressedflags = [] 476 xPoints = [] 477 yPoints = [] 478 xFormat = ">" 479 yFormat = ">" 480 lastflag = None 481 repeat = 0 482 for i in range(len(coordinates)): 483 # Oh, the horrors of TrueType 484 flag = self.flags[i] 485 x, y = coordinates[i] 486 # do x 487 if x == 0: 488 flag = flag | flagXsame 489 elif -255 <= x <= 255: 490 flag = flag | flagXShort 491 if x > 0: 492 flag = flag | flagXsame 493 else: 494 x = -x 495 xPoints.append(x) 496 xFormat = xFormat + 'B' 497 else: 498 xPoints.append(x) 499 xFormat = xFormat + 'h' 500 # do y 501 if y == 0: 502 flag = flag | flagYsame 503 elif -255 <= y <= 255: 504 flag = flag | flagYShort 505 if y > 0: 506 flag = flag | flagYsame 507 else: 508 y = -y 509 yPoints.append(y) 510 yFormat = yFormat + 'B' 511 else: 512 yPoints.append(y) 513 yFormat = yFormat + 'h' 514 # handle repeating flags 515 if flag == lastflag: 516 repeat = repeat + 1 517 if repeat == 1: 518 compressedflags.append(flag) 519 elif repeat > 1: 520 compressedflags[-2] = flag | flagRepeat 521 compressedflags[-1] = repeat 522 else: 523 compressedflags[-1] = repeat 524 else: 525 repeat = 0 526 compressedflags.append(flag) 527 lastflag = flag 528 data = data + array.array("B", compressedflags).tostring() 529 data = data + apply(struct.pack, (xFormat,)+tuple(xPoints)) 530 data = data + apply(struct.pack, (yFormat,)+tuple(yPoints)) 531 return data 532 533 def recalcBounds(self, glyfTable): 534 coordinates, endPts, flags = self.getCoordinates(glyfTable) 535 if len(coordinates) > 0: 536 self.xMin, self.yMin = Numeric.minimum.reduce(coordinates) 537 self.xMax, self.yMax = Numeric.maximum.reduce(coordinates) 538 else: 539 self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0) 540 541 def isComposite(self): 542 return self.numberOfContours == -1 543 544 def __getitem__(self, componentIndex): 545 if not self.isComposite(): 546 raise ttLib.TTLibError, "can't use glyph as sequence" 547 return self.components[componentIndex] 548 549 def getCoordinates(self, glyfTable): 550 if self.numberOfContours > 0: 551 return self.coordinates, self.endPtsOfContours, self.flags 552 elif self.isComposite(): 553 # it's a composite 554 allCoords = None 555 allFlags = None 556 allEndPts = None 557 for compo in self.components: 558 g = glyfTable[compo.glyphName] 559 coordinates, endPts, flags = g.getCoordinates(glyfTable) 560 if hasattr(compo, "firstPt"): 561 # move according to two reference points 562 move = allCoords[compo.firstPt] - coordinates[compo.secondPt] 563 else: 564 move = compo.x, compo.y 565 566 if not hasattr(compo, "transform"): 567 if len(coordinates) > 0: 568 coordinates = coordinates + move # I love NumPy! 569 else: 570 apple_way = compo.flags & SCALED_COMPONENT_OFFSET 571 ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET 572 assert not (apple_way and ms_way) 573 if not (apple_way or ms_way): 574 scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT # see top of this file 575 else: 576 scale_component_offset = apple_way 577 if scale_component_offset: 578 # the Apple way: first move, then scale (ie. scale the component offset) 579 coordinates = coordinates + move 580 coordinates = Numeric.dot(coordinates, compo.transform) 581 else: 582 # the MS way: first scale, then move 583 coordinates = Numeric.dot(coordinates, compo.transform) 584 coordinates = coordinates + move 585 # due to the transformation the coords. are now floats; 586 # round them off nicely, and cast to short 587 coordinates = Numeric.floor(coordinates + 0.5).astype(Numeric.Int16) 588 if allCoords is None or len(allCoords) == 0: 589 allCoords = coordinates 590 allEndPts = endPts 591 allFlags = flags 592 else: 593 allEndPts = allEndPts + (Numeric.array(endPts) + len(allCoords)).tolist() 594 if len(coordinates) > 0: 595 allCoords = Numeric.concatenate((allCoords, coordinates)) 596 allFlags = Numeric.concatenate((allFlags, flags)) 597 return allCoords, allEndPts, allFlags 598 else: 599 return Numeric.array([], Numeric.Int16), [], Numeric.array([], Numeric.Int8) 600 601 def __cmp__(self, other): 602 if self.numberOfContours <= 0: 603 return cmp(self.__dict__, other.__dict__) 604 else: 605 if cmp(len(self.coordinates), len(other.coordinates)): 606 return 1 607 ctest = Numeric.alltrue(Numeric.alltrue(Numeric.equal(self.coordinates, other.coordinates))) 608 ftest = Numeric.alltrue(Numeric.equal(self.flags, other.flags)) 609 if not ctest or not ftest: 610 return 1 611 return ( 612 cmp(self.endPtsOfContours, other.endPtsOfContours) or 613 cmp(self.program, other.instructions) 614 ) 615 616 617class GlyphComponent: 618 619 def __init__(self): 620 pass 621 622 def getComponentInfo(self): 623 """Return the base glyph name and a Transformation object.""" 624 # XXX Ignoring self.firstPt & self.lastpt for now: I need to implement 625 # something equivalent in fontTools.objects.glyph (I'd rather not 626 # convert it to an absolute offset, since it is valuable information). 627 # This method will now raise "AttributeError: x" on glyphs that use 628 # this TT feature. 629 if hasattr(self, "transform"): 630 [[xx, xy], [yx, yy]] = self.transform 631 trans = (xx, xy, yx, yy, self.x, self.y) 632 else: 633 trans = (1, 0, 0, 1, self.x, self.y) 634 return self.glyphName, trans 635 636 def decompile(self, data, glyfTable): 637 flags, glyphID = struct.unpack(">HH", data[:4]) 638 self.flags = int(flags) 639 glyphID = int(glyphID) 640 self.glyphName = glyfTable.getGlyphName(int(glyphID)) 641 #print ">>", reprflag(self.flags) 642 data = data[4:] 643 644 if self.flags & ARG_1_AND_2_ARE_WORDS: 645 if self.flags & ARGS_ARE_XY_VALUES: 646 self.x, self.y = struct.unpack(">hh", data[:4]) 647 else: 648 x, y = struct.unpack(">HH", data[:4]) 649 self.firstPt, self.secondPt = int(x), int(y) 650 data = data[4:] 651 else: 652 if self.flags & ARGS_ARE_XY_VALUES: 653 self.x, self.y = struct.unpack(">bb", data[:2]) 654 else: 655 x, y = struct.unpack(">BB", data[:2]) 656 self.firstPt, self.secondPt = int(x), int(y) 657 data = data[2:] 658 659 if self.flags & WE_HAVE_A_SCALE: 660 scale, = struct.unpack(">h", data[:2]) 661 self.transform = Numeric.array( 662 [[scale, 0], [0, scale]]) / float(0x4000) # fixed 2.14 663 data = data[2:] 664 elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE: 665 xscale, yscale = struct.unpack(">hh", data[:4]) 666 self.transform = Numeric.array( 667 [[xscale, 0], [0, yscale]]) / float(0x4000) # fixed 2.14 668 data = data[4:] 669 elif self.flags & WE_HAVE_A_TWO_BY_TWO: 670 (xscale, scale01, 671 scale10, yscale) = struct.unpack(">hhhh", data[:8]) 672 self.transform = Numeric.array( 673 [[xscale, scale01], [scale10, yscale]]) / float(0x4000) # fixed 2.14 674 data = data[8:] 675 more = self.flags & MORE_COMPONENTS 676 haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS 677 self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS | 678 SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET | 679 NON_OVERLAPPING) 680 return more, haveInstructions, data 681 682 def compile(self, more, haveInstructions, glyfTable): 683 data = "" 684 685 # reset all flags we will calculate ourselves 686 flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS | 687 SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET | 688 NON_OVERLAPPING) 689 if more: 690 flags = flags | MORE_COMPONENTS 691 if haveInstructions: 692 flags = flags | WE_HAVE_INSTRUCTIONS 693 694 if hasattr(self, "firstPt"): 695 if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255): 696 data = data + struct.pack(">BB", self.firstPt, self.secondPt) 697 else: 698 data = data + struct.pack(">HH", self.firstPt, self.secondPt) 699 flags = flags | ARG_1_AND_2_ARE_WORDS 700 else: 701 flags = flags | ARGS_ARE_XY_VALUES 702 if (-128 <= self.x <= 127) and (-128 <= self.y <= 127): 703 data = data + struct.pack(">bb", self.x, self.y) 704 else: 705 data = data + struct.pack(">hh", self.x, self.y) 706 flags = flags | ARG_1_AND_2_ARE_WORDS 707 708 if hasattr(self, "transform"): 709 # XXX needs more testing 710 transform = Numeric.floor(self.transform * 0x4000 + 0.5) 711 if transform[0][1] or transform[1][0]: 712 flags = flags | WE_HAVE_A_TWO_BY_TWO 713 data = data + struct.pack(">hhhh", 714 transform[0][0], transform[0][1], 715 transform[1][0], transform[1][1]) 716 elif transform[0][0] <> transform[1][1]: 717 flags = flags | WE_HAVE_AN_X_AND_Y_SCALE 718 data = data + struct.pack(">hh", 719 transform[0][0], transform[1][1]) 720 else: 721 flags = flags | WE_HAVE_A_SCALE 722 data = data + struct.pack(">h", 723 transform[0][0]) 724 725 glyphID = glyfTable.getGlyphID(self.glyphName) 726 return struct.pack(">HH", flags, glyphID) + data 727 728 def toXML(self, writer, ttFont): 729 attrs = [("glyphName", self.glyphName)] 730 if not hasattr(self, "firstPt"): 731 attrs = attrs + [("x", self.x), ("y", self.y)] 732 else: 733 attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)] 734 735 if hasattr(self, "transform"): 736 # XXX needs more testing 737 transform = self.transform 738 if transform[0][1] or transform[1][0]: 739 attrs = attrs + [ 740 ("scalex", transform[0][0]), ("scale01", transform[0][1]), 741 ("scale10", transform[1][0]), ("scaley", transform[1][1]), 742 ] 743 elif transform[0][0] <> transform[1][1]: 744 attrs = attrs + [ 745 ("scalex", transform[0][0]), ("scaley", transform[1][1]), 746 ] 747 else: 748 attrs = attrs + [("scale", transform[0][0])] 749 attrs = attrs + [("flags", hex(self.flags))] 750 writer.simpletag("component", attrs) 751 writer.newline() 752 753 def fromXML(self, (name, attrs, content), ttFont): 754 self.glyphName = attrs["glyphName"] 755 if attrs.has_key("firstPt"): 756 self.firstPt = safeEval(attrs["firstPt"]) 757 self.secondPt = safeEval(attrs["secondPt"]) 758 else: 759 self.x = safeEval(attrs["x"]) 760 self.y = safeEval(attrs["y"]) 761 if attrs.has_key("scale01"): 762 scalex = safeEval(attrs["scalex"]) 763 scale01 = safeEval(attrs["scale01"]) 764 scale10 = safeEval(attrs["scale10"]) 765 scaley = safeEval(attrs["scaley"]) 766 self.transform = Numeric.array([[scalex, scale01], [scale10, scaley]]) 767 elif attrs.has_key("scalex"): 768 scalex = safeEval(attrs["scalex"]) 769 scaley = safeEval(attrs["scaley"]) 770 self.transform = Numeric.array([[scalex, 0], [0, scaley]]) 771 elif attrs.has_key("scale"): 772 scale = safeEval(attrs["scale"]) 773 self.transform = Numeric.array([[scale, 0], [0, scale]]) 774 self.flags = safeEval(attrs["flags"]) 775 776 def __cmp__(self, other): 777 if hasattr(self, "transform"): 778 if Numeric.alltrue(Numeric.equal(self.transform, other.transform)): 779 selfdict = self.__dict__.copy() 780 otherdict = other.__dict__.copy() 781 del selfdict["transform"] 782 del otherdict["transform"] 783 return cmp(selfdict, otherdict) 784 else: 785 return 1 786 else: 787 return cmp(self.__dict__, other.__dict__) 788 789 790def reprflag(flag): 791 bin = "" 792 if type(flag) == StringType: 793 flag = ord(flag) 794 while flag: 795 if flag & 0x01: 796 bin = "1" + bin 797 else: 798 bin = "0" + bin 799 flag = flag >> 1 800 bin = (14 - len(bin)) * "0" + bin 801 return bin 802 803