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