1 """_g_l_y_f.py -- Converter classes for the 'glyf' table.""" 2 3 4 from __future__ import print_function, division, absolute_import 5 from fontTools.misc.py23 import * 6 from fontTools.misc import sstruct 7 from fontTools import ttLib 8 from fontTools.misc.textTools import safeEval 9 from fontTools.misc.arrayTools import calcBounds, calcIntBounds, pointInRect 10 from fontTools.misc.bezierTools import calcQuadraticBounds 11 from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi 12 from . import DefaultTable 13 from . import ttProgram 14 import sys 15 import struct 16 import array 17 import warnings 18 19 # 20 # The Apple and MS rasterizers behave differently for 21 # scaled composite components: one does scale first and then translate 22 # and the other does it vice versa. MS defined some flags to indicate 23 # the difference, but it seems nobody actually _sets_ those flags. 24 # 25 # Funny thing: Apple seems to _only_ do their thing in the 26 # WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE 27 # (eg. Charcoal)... 28 # 29 SCALE_COMPONENT_OFFSET_DEFAULT = 0 # 0 == MS, 1 == Apple 30 31 32 class table__g_l_y_f(DefaultTable.DefaultTable): 33 34 def decompile(self, data, ttFont): 35 loca = ttFont['loca'] 36 last = int(loca[0]) 37 noname = 0 38 self.glyphs = {} 39 self.glyphOrder = glyphOrder = ttFont.getGlyphOrder() 40 for i in range(0, len(loca)-1): 41 try: 42 glyphName = glyphOrder[i] 43 except IndexError: 44 noname = noname + 1 45 glyphName = 'ttxautoglyph%s' % i 46 next = int(loca[i+1]) 47 glyphdata = data[last:next] 48 if len(glyphdata) != (next - last): 49 raise ttLib.TTLibError("not enough 'glyf' table data") 50 glyph = Glyph(glyphdata) 51 self.glyphs[glyphName] = glyph 52 last = next 53 if len(data) > next: 54 warnings.warn("too much 'glyf' table data") 55 if noname: 56 warnings.warn('%s glyphs have no name' % i) 57 if not ttFont.lazy: 58 for glyph in self.glyphs.values(): 59 glyph.expand(self) 60 61 def compile(self, ttFont): 62 if not hasattr(self, "glyphOrder"): 63 self.glyphOrder = ttFont.getGlyphOrder() 64 locations = [] 65 currentLocation = 0 66 dataList = [] 67 recalcBBoxes = ttFont.recalcBBoxes 68 for glyphName in self.glyphOrder: 69 glyph = self.glyphs[glyphName] 70 glyphData = glyph.compile(self, recalcBBoxes) 71 locations.append(currentLocation) 72 currentLocation = currentLocation + len(glyphData) 73 dataList.append(glyphData) 74 locations.append(currentLocation) 75 data = bytesjoin(dataList) 76 if 'loca' in ttFont: 77 ttFont['loca'].set(locations) 78 ttFont['maxp'].numGlyphs = len(self.glyphs) 79 return data 80 81 def toXML(self, writer, ttFont, progress=None): 82 writer.newline() 83 glyphNames = ttFont.getGlyphNames() 84 writer.comment("The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler.") 85 writer.newline() 86 writer.newline() 87 counter = 0 88 progressStep = 10 89 numGlyphs = len(glyphNames) 90 for glyphName in glyphNames: 91 if not counter % progressStep and progress is not None: 92 progress.setLabel("Dumping 'glyf' table... (%s)" % glyphName) 93 progress.increment(progressStep / numGlyphs) 94 counter = counter + 1 95 glyph = self[glyphName] 96 if glyph.numberOfContours: 97 writer.begintag('TTGlyph', [ 98 ("name", glyphName), 99 ("xMin", glyph.xMin), 100 ("yMin", glyph.yMin), 101 ("xMax", glyph.xMax), 102 ("yMax", glyph.yMax), 103 ]) 104 writer.newline() 105 glyph.toXML(writer, ttFont) 106 writer.endtag('TTGlyph') 107 writer.newline() 108 else: 109 writer.simpletag('TTGlyph', name=glyphName) 110 writer.comment("contains no outline data") 111 writer.newline() 112 writer.newline() 113 114 def fromXML(self, name, attrs, content, ttFont): 115 if name != "TTGlyph": 116 return 117 if not hasattr(self, "glyphs"): 118 self.glyphs = {} 119 if not hasattr(self, "glyphOrder"): 120 self.glyphOrder = ttFont.getGlyphOrder() 121 glyphName = attrs["name"] 122 if ttFont.verbose: 123 ttLib.debugmsg("unpacking glyph '%s'" % glyphName) 124 glyph = Glyph() 125 for attr in ['xMin', 'yMin', 'xMax', 'yMax']: 126 setattr(glyph, attr, safeEval(attrs.get(attr, '0'))) 127 self.glyphs[glyphName] = glyph 128 for element in content: 129 if not isinstance(element, tuple): 130 continue 131 name, attrs, content = element 132 glyph.fromXML(name, attrs, content, ttFont) 133 if not ttFont.recalcBBoxes: 134 glyph.compact(self, 0) 135 136 def setGlyphOrder(self, glyphOrder): 137 self.glyphOrder = glyphOrder 138 139 def getGlyphName(self, glyphID): 140 return self.glyphOrder[glyphID] 141 142 def getGlyphID(self, glyphName): 143 # XXX optimize with reverse dict!!! 144 return self.glyphOrder.index(glyphName) 145 146 def keys(self): 147 return self.glyphs.keys() 148 149 def has_key(self, glyphName): 150 return glyphName in self.glyphs 151 152 __contains__ = has_key 153 154 def __getitem__(self, glyphName): 155 glyph = self.glyphs[glyphName] 156 glyph.expand(self) 157 return glyph 158 159 def __setitem__(self, glyphName, glyph): 160 self.glyphs[glyphName] = glyph 161 if glyphName not in self.glyphOrder: 162 self.glyphOrder.append(glyphName) 163 164 def __delitem__(self, glyphName): 165 del self.glyphs[glyphName] 166 self.glyphOrder.remove(glyphName) 167 168 def __len__(self): 169 assert len(self.glyphOrder) == len(self.glyphs) 170 return len(self.glyphs) 171 172 173 glyphHeaderFormat = """ 174 > # big endian 175 numberOfContours: h 176 xMin: h 177 yMin: h 178 xMax: h 179 yMax: h 180 """ 181 182 # flags 183 flagOnCurve = 0x01 184 flagXShort = 0x02 185 flagYShort = 0x04 186 flagRepeat = 0x08 187 flagXsame = 0x10 188 flagYsame = 0x20 189 flagReserved1 = 0x40 190 flagReserved2 = 0x80 191 192 193 ARG_1_AND_2_ARE_WORDS = 0x0001 # if set args are words otherwise they are bytes 194 ARGS_ARE_XY_VALUES = 0x0002 # if set args are xy values, otherwise they are points 195 ROUND_XY_TO_GRID = 0x0004 # for the xy values if above is true 196 WE_HAVE_A_SCALE = 0x0008 # Sx = Sy, otherwise scale == 1.0 197 NON_OVERLAPPING = 0x0010 # set to same value for all components (obsolete!) 198 MORE_COMPONENTS = 0x0020 # indicates at least one more glyph after this one 199 WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Sx, Sy 200 WE_HAVE_A_TWO_BY_TWO = 0x0080 # t00, t01, t10, t11 201 WE_HAVE_INSTRUCTIONS = 0x0100 # instructions follow 202 USE_MY_METRICS = 0x0200 # apply these metrics to parent glyph 203 OVERLAP_COMPOUND = 0x0400 # used by Apple in GX fonts 204 SCALED_COMPONENT_OFFSET = 0x0800 # composite designed to have the component offset scaled (designed for Apple) 205 UNSCALED_COMPONENT_OFFSET = 0x1000 # composite designed not to have the component offset scaled (designed for MS) 206 207 208 class Glyph(object): 209 210 def __init__(self, data=""): 211 if not data: 212 # empty char 213 self.numberOfContours = 0 214 return 215 self.data = data 216 217 def compact(self, glyfTable, recalcBBoxes=True): 218 data = self.compile(glyfTable, recalcBBoxes) 219 self.__dict__.clear() 220 self.data = data 221 222 def expand(self, glyfTable): 223 if not hasattr(self, "data"): 224 # already unpacked 225 return 226 if not self.data: 227 # empty char 228 self.numberOfContours = 0 229 return 230 dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self) 231 del self.data 232 if self.isComposite(): 233 self.decompileComponents(data, glyfTable) 234 else: 235 self.decompileCoordinates(data) 236 237 def compile(self, glyfTable, recalcBBoxes=True): 238 if hasattr(self, "data"): 239 return self.data 240 if self.numberOfContours == 0: 241 return "" 242 if recalcBBoxes: 243 self.recalcBounds(glyfTable) 244 data = sstruct.pack(glyphHeaderFormat, self) 245 if self.isComposite(): 246 data = data + self.compileComponents(glyfTable) 247 else: 248 data = data + self.compileCoordinates() 249 # From the spec: "Note that the local offsets should be word-aligned" 250 # From a later MS spec: "Note that the local offsets should be long-aligned" 251 # Let's be modern and align on 4-byte boundaries. 252 if len(data) % 4: 253 # add pad bytes 254 nPadBytes = 4 - (len(data) % 4) 255 data = data + b"\0" * nPadBytes 256 return data 257 258 def toXML(self, writer, ttFont): 259 if self.isComposite(): 260 for compo in self.components: 261 compo.toXML(writer, ttFont) 262 if hasattr(self, "program"): 263 writer.begintag("instructions") 264 self.program.toXML(writer, ttFont) 265 writer.endtag("instructions") 266 writer.newline() 267 else: 268 last = 0 269 for i in range(self.numberOfContours): 270 writer.begintag("contour") 271 writer.newline() 272 for j in range(last, self.endPtsOfContours[i] + 1): 273 writer.simpletag("pt", [ 274 ("x", self.coordinates[j][0]), 275 ("y", self.coordinates[j][1]), 276 ("on", self.flags[j] & flagOnCurve)]) 277 writer.newline() 278 last = self.endPtsOfContours[i] + 1 279 writer.endtag("contour") 280 writer.newline() 281 if self.numberOfContours: 282 writer.begintag("instructions") 283 self.program.toXML(writer, ttFont) 284 writer.endtag("instructions") 285 writer.newline() 286 287 def fromXML(self, name, attrs, content, ttFont): 288 if name == "contour": 289 if self.numberOfContours < 0: 290 raise ttLib.TTLibError("can't mix composites and contours in glyph") 291 self.numberOfContours = self.numberOfContours + 1 292 coordinates = GlyphCoordinates() 293 flags = [] 294 for element in content: 295 if not isinstance(element, tuple): 296 continue 297 name, attrs, content = element 298 if name != "pt": 299 continue # ignore anything but "pt" 300 coordinates.append((safeEval(attrs["x"]), safeEval(attrs["y"]))) 301 flags.append(not not safeEval(attrs["on"])) 302 flags = array.array("B", flags) 303 if not hasattr(self, "coordinates"): 304 self.coordinates = coordinates 305 self.flags = flags 306 self.endPtsOfContours = [len(coordinates)-1] 307 else: 308 self.coordinates.extend (coordinates) 309 self.flags.extend(flags) 310 self.endPtsOfContours.append(len(self.coordinates)-1) 311 elif name == "component": 312 if self.numberOfContours > 0: 313 raise ttLib.TTLibError("can't mix composites and contours in glyph") 314 self.numberOfContours = -1 315 if not hasattr(self, "components"): 316 self.components = [] 317 component = GlyphComponent() 318 self.components.append(component) 319 component.fromXML(name, attrs, content, ttFont) 320 elif name == "instructions": 321 self.program = ttProgram.Program() 322 for element in content: 323 if not isinstance(element, tuple): 324 continue 325 name, attrs, content = element 326 self.program.fromXML(name, attrs, content, ttFont) 327 328 def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1): 329 assert self.isComposite() 330 nContours = 0 331 nPoints = 0 332 for compo in self.components: 333 baseGlyph = glyfTable[compo.glyphName] 334 if baseGlyph.numberOfContours == 0: 335 continue 336 elif baseGlyph.numberOfContours > 0: 337 nP, nC = baseGlyph.getMaxpValues() 338 else: 339 nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues( 340 glyfTable, maxComponentDepth + 1) 341 nPoints = nPoints + nP 342 nContours = nContours + nC 343 return nPoints, nContours, maxComponentDepth 344 345 def getMaxpValues(self): 346 assert self.numberOfContours > 0 347 return len(self.coordinates), len(self.endPtsOfContours) 348 349 def decompileComponents(self, data, glyfTable): 350 self.components = [] 351 more = 1 352 haveInstructions = 0 353 while more: 354 component = GlyphComponent() 355 more, haveInstr, data = component.decompile(data, glyfTable) 356 haveInstructions = haveInstructions | haveInstr 357 self.components.append(component) 358 if haveInstructions: 359 numInstructions, = struct.unpack(">h", data[:2]) 360 data = data[2:] 361 self.program = ttProgram.Program() 362 self.program.fromBytecode(data[:numInstructions]) 363 data = data[numInstructions:] 364 assert len(data) < 4, "bad composite data" 365 366 def decompileCoordinates(self, data): 367 endPtsOfContours = array.array("h") 368 endPtsOfContours.fromstring(data[:2*self.numberOfContours]) 369 if sys.byteorder != "big": 370 endPtsOfContours.byteswap() 371 self.endPtsOfContours = endPtsOfContours.tolist() 372 373 data = data[2*self.numberOfContours:] 374 375 instructionLength, = struct.unpack(">h", data[:2]) 376 data = data[2:] 377 self.program = ttProgram.Program() 378 self.program.fromBytecode(data[:instructionLength]) 379 data = data[instructionLength:] 380 nCoordinates = self.endPtsOfContours[-1] + 1 381 flags, xCoordinates, yCoordinates = \ 382 self.decompileCoordinatesRaw(nCoordinates, data) 383 384 # fill in repetitions and apply signs 385 self.coordinates = coordinates = GlyphCoordinates.zeros(nCoordinates) 386 xIndex = 0 387 yIndex = 0 388 for i in range(nCoordinates): 389 flag = flags[i] 390 # x coordinate 391 if flag & flagXShort: 392 if flag & flagXsame: 393 x = xCoordinates[xIndex] 394 else: 395 x = -xCoordinates[xIndex] 396 xIndex = xIndex + 1 397 elif flag & flagXsame: 398 x = 0 399 else: 400 x = xCoordinates[xIndex] 401 xIndex = xIndex + 1 402 # y coordinate 403 if flag & flagYShort: 404 if flag & flagYsame: 405 y = yCoordinates[yIndex] 406 else: 407 y = -yCoordinates[yIndex] 408 yIndex = yIndex + 1 409 elif flag & flagYsame: 410 y = 0 411 else: 412 y = yCoordinates[yIndex] 413 yIndex = yIndex + 1 414 coordinates[i] = (x, y) 415 assert xIndex == len(xCoordinates) 416 assert yIndex == len(yCoordinates) 417 coordinates.relativeToAbsolute() 418 # discard all flags but for "flagOnCurve" 419 self.flags = array.array("B", (f & flagOnCurve for f in flags)) 420 421 def decompileCoordinatesRaw(self, nCoordinates, data): 422 # unpack flags and prepare unpacking of coordinates 423 flags = array.array("B", [0] * nCoordinates) 424 # Warning: deep Python trickery going on. We use the struct module to unpack 425 # the coordinates. We build a format string based on the flags, so we can 426 # unpack the coordinates in one struct.unpack() call. 427 xFormat = ">" # big endian 428 yFormat = ">" # big endian 429 i = j = 0 430 while True: 431 flag = byteord(data[i]) 432 i = i + 1 433 repeat = 1 434 if flag & flagRepeat: 435 repeat = byteord(data[i]) + 1 436 i = i + 1 437 for k in range(repeat): 438 if flag & flagXShort: 439 xFormat = xFormat + 'B' 440 elif not (flag & flagXsame): 441 xFormat = xFormat + 'h' 442 if flag & flagYShort: 443 yFormat = yFormat + 'B' 444 elif not (flag & flagYsame): 445 yFormat = yFormat + 'h' 446 flags[j] = flag 447 j = j + 1 448 if j >= nCoordinates: 449 break 450 assert j == nCoordinates, "bad glyph flags" 451 data = data[i:] 452 # unpack raw coordinates, krrrrrr-tching! 453 xDataLen = struct.calcsize(xFormat) 454 yDataLen = struct.calcsize(yFormat) 455 if len(data) - (xDataLen + yDataLen) >= 4: 456 warnings.warn("too much glyph data: %d excess bytes" % (len(data) - (xDataLen + yDataLen))) 457 xCoordinates = struct.unpack(xFormat, data[:xDataLen]) 458 yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen]) 459 return flags, xCoordinates, yCoordinates 460 461 def compileComponents(self, glyfTable): 462 data = b"" 463 lastcomponent = len(self.components) - 1 464 more = 1 465 haveInstructions = 0 466 for i in range(len(self.components)): 467 if i == lastcomponent: 468 haveInstructions = hasattr(self, "program") 469 more = 0 470 compo = self.components[i] 471 data = data + compo.compile(more, haveInstructions, glyfTable) 472 if haveInstructions: 473 instructions = self.program.getBytecode() 474 data = data + struct.pack(">h", len(instructions)) + instructions 475 return data 476 477 478 def compileCoordinates(self): 479 assert len(self.coordinates) == len(self.flags) 480 data = b"" 481 endPtsOfContours = array.array("h", self.endPtsOfContours) 482 if sys.byteorder != "big": 483 endPtsOfContours.byteswap() 484 data = data + endPtsOfContours.tostring() 485 instructions = self.program.getBytecode() 486 data = data + struct.pack(">h", len(instructions)) + instructions 487 nCoordinates = len(self.coordinates) 488 489 coordinates = self.coordinates.copy() 490 coordinates.absoluteToRelative() 491 flags = self.flags 492 compressedflags = [] 493 xPoints = [] 494 yPoints = [] 495 xFormat = ">" 496 yFormat = ">" 497 lastflag = None 498 repeat = 0 499 for i in range(len(coordinates)): 500 # Oh, the horrors of TrueType 501 flag = flags[i] 502 x, y = coordinates[i] 503 # do x 504 if x == 0: 505 flag = flag | flagXsame 506 elif -255 <= x <= 255: 507 flag = flag | flagXShort 508 if x > 0: 509 flag = flag | flagXsame 510 else: 511 x = -x 512 xPoints.append(x) 513 xFormat = xFormat + 'B' 514 else: 515 xPoints.append(x) 516 xFormat = xFormat + 'h' 517 # do y 518 if y == 0: 519 flag = flag | flagYsame 520 elif -255 <= y <= 255: 521 flag = flag | flagYShort 522 if y > 0: 523 flag = flag | flagYsame 524 else: 525 y = -y 526 yPoints.append(y) 527 yFormat = yFormat + 'B' 528 else: 529 yPoints.append(y) 530 yFormat = yFormat + 'h' 531 # handle repeating flags 532 if flag == lastflag and repeat != 255: 533 repeat = repeat + 1 534 if repeat == 1: 535 compressedflags.append(flag) 536 else: 537 compressedflags[-2] = flag | flagRepeat 538 compressedflags[-1] = repeat 539 else: 540 repeat = 0 541 compressedflags.append(flag) 542 lastflag = flag 543 data = data + array.array("B", compressedflags).tostring() 544 if coordinates.isFloat(): 545 # Warn? 546 xPoints = [int(round(x)) for x in xPoints] 547 yPoints = [int(round(y)) for y in xPoints] 548 data = data + struct.pack(*(xFormat,)+tuple(xPoints)) 549 data = data + struct.pack(*(yFormat,)+tuple(yPoints)) 550 return data 551 552 def recalcBounds(self, glyfTable): 553 coords, endPts, flags = self.getCoordinates(glyfTable) 554 if len(coords) > 0: 555 if 0: 556 # This branch calculates exact glyph outline bounds 557 # analytically, handling cases without on-curve 558 # extremas, etc. However, the glyf table header 559 # simply says that the bounds should be min/max x/y 560 # "for coordinate data", so I suppose that means no 561 # fancy thing here, just get extremas of all coord 562 # points (on and off). As such, this branch is 563 # disabled. 564 565 # Collect on-curve points 566 onCurveCoords = [coords[j] for j in range(len(coords)) 567 if flags[j] & flagOnCurve] 568 # Add implicit on-curve points 569 start = 0 570 for end in endPts: 571 last = end 572 for j in range(start, end + 1): 573 if not ((flags[j] | flags[last]) & flagOnCurve): 574 x = (coords[last][0] + coords[j][0]) / 2 575 y = (coords[last][1] + coords[j][1]) / 2 576 onCurveCoords.append((x,y)) 577 last = j 578 start = end + 1 579 # Add bounds for curves without an explicit extrema 580 start = 0 581 for end in endPts: 582 last = end 583 for j in range(start, end + 1): 584 if not (flags[j] & flagOnCurve): 585 next = j + 1 if j < end else start 586 bbox = calcBounds([coords[last], coords[next]]) 587 if not pointInRect(coords[j], bbox): 588 # Ouch! 589 warnings.warn("Outline has curve with implicit extrema.") 590 # Ouch! Find analytical curve bounds. 591 pthis = coords[j] 592 plast = coords[last] 593 if not (flags[last] & flagOnCurve): 594 plast = ((pthis[0]+plast[0])/2, (pthis[1]+plast[1])/2) 595 pnext = coords[next] 596 if not (flags[next] & flagOnCurve): 597 pnext = ((pthis[0]+pnext[0])/2, (pthis[1]+pnext[1])/2) 598 bbox = calcQuadraticBounds(plast, pthis, pnext) 599 onCurveCoords.append((bbox[0],bbox[1])) 600 onCurveCoords.append((bbox[2],bbox[3])) 601 last = j 602 start = end + 1 603 604 self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(onCurveCoords) 605 else: 606 self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(coords) 607 else: 608 self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0) 609 610 def isComposite(self): 611 """Can be called on compact or expanded glyph.""" 612 if hasattr(self, "data"): 613 return struct.unpack(">h", self.data[:2])[0] == -1 614 else: 615 return self.numberOfContours == -1 616 617 def __getitem__(self, componentIndex): 618 if not self.isComposite(): 619 raise ttLib.TTLibError("can't use glyph as sequence") 620 return self.components[componentIndex] 621 622 def getCoordinates(self, glyfTable): 623 if self.numberOfContours > 0: 624 return self.coordinates, self.endPtsOfContours, self.flags 625 elif self.isComposite(): 626 # it's a composite 627 allCoords = GlyphCoordinates() 628 allFlags = array.array("B") 629 allEndPts = [] 630 for compo in self.components: 631 g = glyfTable[compo.glyphName] 632 coordinates, endPts, flags = g.getCoordinates(glyfTable) 633 if hasattr(compo, "firstPt"): 634 # move according to two reference points 635 x1,y1 = allCoords[compo.firstPt] 636 x2,y2 = coordinates[compo.secondPt] 637 move = x1-x2, y1-y2 638 else: 639 move = compo.x, compo.y 640 641 coordinates = GlyphCoordinates(coordinates) 642 if not hasattr(compo, "transform"): 643 coordinates.translate(move) 644 else: 645 apple_way = compo.flags & SCALED_COMPONENT_OFFSET 646 ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET 647 assert not (apple_way and ms_way) 648 if not (apple_way or ms_way): 649 scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT # see top of this file 650 else: 651 scale_component_offset = apple_way 652 if scale_component_offset: 653 # the Apple way: first move, then scale (ie. scale the component offset) 654 coordinates.translate(move) 655 coordinates.transform(compo.transform) 656 else: 657 # the MS way: first scale, then move 658 coordinates.transform(compo.transform) 659 coordinates.translate(move) 660 offset = len(allCoords) 661 allEndPts.extend(e + offset for e in endPts) 662 allCoords.extend(coordinates) 663 allFlags.extend(flags) 664 return allCoords, allEndPts, allFlags 665 else: 666 return GlyphCoordinates(), [], array.array("B") 667 668 def getComponentNames(self, glyfTable): 669 if not hasattr(self, "data"): 670 if self.isComposite(): 671 return [c.glyphName for c in self.components] 672 else: 673 return [] 674 675 # Extract components without expanding glyph 676 677 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0: 678 return [] # Not composite 679 680 data = self.data 681 i = 10 682 components = [] 683 more = 1 684 while more: 685 flags, glyphID = struct.unpack(">HH", data[i:i+4]) 686 i += 4 687 flags = int(flags) 688 components.append(glyfTable.getGlyphName(int(glyphID))) 689 690 if flags & ARG_1_AND_2_ARE_WORDS: i += 4 691 else: i += 2 692 if flags & WE_HAVE_A_SCALE: i += 2 693 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4 694 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8 695 more = flags & MORE_COMPONENTS 696 697 return components 698 699 def removeHinting(self): 700 if not hasattr(self, "data"): 701 self.program = ttProgram.Program() 702 self.program.fromBytecode([]) 703 return 704 705 # Remove instructions without expanding glyph 706 707 if not self.data: 708 return 709 numContours = struct.unpack(">h", self.data[:2])[0] 710 data = array.array("B", self.data) 711 i = 10 712 if numContours >= 0: 713 i += 2 * numContours # endPtsOfContours 714 nCoordinates = ((data[i-2] << 8) | data[i-1]) + 1 715 instructionLen = (data[i] << 8) | data[i+1] 716 # Zero it 717 data[i] = data [i+1] = 0 718 i += 2 719 if instructionLen: 720 # Splice it out 721 data = data[:i] + data[i+instructionLen:] 722 if instructionLen % 4: 723 # We now have to go ahead and drop 724 # the old padding. Otherwise with 725 # padding we have to add, we may 726 # end up with more than 3 bytes of 727 # padding. 728 coordBytes = 0 729 j = 0 730 while True: 731 flag = data[i] 732 i = i + 1 733 repeat = 1 734 if flag & flagRepeat: 735 repeat = data[i] + 1 736 i = i + 1 737 xBytes = yBytes = 0 738 if flag & flagXShort: 739 xBytes = 1 740 elif not (flag & flagXsame): 741 xBytes = 2 742 if flag & flagYShort: 743 yBytes = 1 744 elif not (flag & flagYsame): 745 yBytes = 2 746 coordBytes += (xBytes + yBytes) * repeat 747 j += repeat 748 if j >= nCoordinates: 749 break 750 assert j == nCoordinates, "bad glyph flags" 751 data = data[:i + coordBytes] 752 else: 753 more = 1 754 while more: 755 flags =(data[i] << 8) | data[i+1] 756 # Turn instruction flag off 757 flags &= ~WE_HAVE_INSTRUCTIONS 758 data[i+0] = flags >> 8 759 data[i+1] = flags & 0xFF 760 i += 4 761 flags = int(flags) 762 763 if flags & ARG_1_AND_2_ARE_WORDS: i += 4 764 else: i += 2 765 if flags & WE_HAVE_A_SCALE: i += 2 766 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4 767 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8 768 more = flags & MORE_COMPONENTS 769 770 # Cut off 771 data = data[:i] 772 773 data = data.tostring() 774 775 if len(data) % 4: 776 # add pad bytes 777 nPadBytes = 4 - (len(data) % 4) 778 data = data + b"\0" * nPadBytes 779 780 self.data = data 781 782 def __ne__(self, other): 783 return not self.__eq__(other) 784 def __eq__(self, other): 785 if type(self) != type(other): 786 return NotImplemented 787 return self.__dict__ == other.__dict__ 788 789 790 class GlyphComponent(object): 791 792 def __init__(self): 793 pass 794 795 def getComponentInfo(self): 796 """Return the base glyph name and a transform.""" 797 # XXX Ignoring self.firstPt & self.lastpt for now: I need to implement 798 # something equivalent in fontTools.objects.glyph (I'd rather not 799 # convert it to an absolute offset, since it is valuable information). 800 # This method will now raise "AttributeError: x" on glyphs that use 801 # this TT feature. 802 if hasattr(self, "transform"): 803 [[xx, xy], [yx, yy]] = self.transform 804 trans = (xx, xy, yx, yy, self.x, self.y) 805 else: 806 trans = (1, 0, 0, 1, self.x, self.y) 807 return self.glyphName, trans 808 809 def decompile(self, data, glyfTable): 810 flags, glyphID = struct.unpack(">HH", data[:4]) 811 self.flags = int(flags) 812 glyphID = int(glyphID) 813 self.glyphName = glyfTable.getGlyphName(int(glyphID)) 814 #print ">>", reprflag(self.flags) 815 data = data[4:] 816 817 if self.flags & ARG_1_AND_2_ARE_WORDS: 818 if self.flags & ARGS_ARE_XY_VALUES: 819 self.x, self.y = struct.unpack(">hh", data[:4]) 820 else: 821 x, y = struct.unpack(">HH", data[:4]) 822 self.firstPt, self.secondPt = int(x), int(y) 823 data = data[4:] 824 else: 825 if self.flags & ARGS_ARE_XY_VALUES: 826 self.x, self.y = struct.unpack(">bb", data[:2]) 827 else: 828 x, y = struct.unpack(">BB", data[:2]) 829 self.firstPt, self.secondPt = int(x), int(y) 830 data = data[2:] 831 832 if self.flags & WE_HAVE_A_SCALE: 833 scale, = struct.unpack(">h", data[:2]) 834 self.transform = [[fi2fl(scale,14), 0], [0, fi2fl(scale,14)]] # fixed 2.14 835 data = data[2:] 836 elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE: 837 xscale, yscale = struct.unpack(">hh", data[:4]) 838 self.transform = [[fi2fl(xscale,14), 0], [0, fi2fl(yscale,14)]] # fixed 2.14 839 data = data[4:] 840 elif self.flags & WE_HAVE_A_TWO_BY_TWO: 841 (xscale, scale01, 842 scale10, yscale) = struct.unpack(">hhhh", data[:8]) 843 self.transform = [[fi2fl(xscale,14), fi2fl(scale01,14)], 844 [fi2fl(scale10,14), fi2fl(yscale,14)]] # fixed 2.14 845 data = data[8:] 846 more = self.flags & MORE_COMPONENTS 847 haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS 848 self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS | 849 SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET | 850 NON_OVERLAPPING) 851 return more, haveInstructions, data 852 853 def compile(self, more, haveInstructions, glyfTable): 854 data = b"" 855 856 # reset all flags we will calculate ourselves 857 flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS | 858 SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET | 859 NON_OVERLAPPING) 860 if more: 861 flags = flags | MORE_COMPONENTS 862 if haveInstructions: 863 flags = flags | WE_HAVE_INSTRUCTIONS 864 865 if hasattr(self, "firstPt"): 866 if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255): 867 data = data + struct.pack(">BB", self.firstPt, self.secondPt) 868 else: 869 data = data + struct.pack(">HH", self.firstPt, self.secondPt) 870 flags = flags | ARG_1_AND_2_ARE_WORDS 871 else: 872 flags = flags | ARGS_ARE_XY_VALUES 873 if (-128 <= self.x <= 127) and (-128 <= self.y <= 127): 874 data = data + struct.pack(">bb", self.x, self.y) 875 else: 876 data = data + struct.pack(">hh", self.x, self.y) 877 flags = flags | ARG_1_AND_2_ARE_WORDS 878 879 if hasattr(self, "transform"): 880 transform = [[fl2fi(x,14) for x in row] for row in self.transform] 881 if transform[0][1] or transform[1][0]: 882 flags = flags | WE_HAVE_A_TWO_BY_TWO 883 data = data + struct.pack(">hhhh", 884 transform[0][0], transform[0][1], 885 transform[1][0], transform[1][1]) 886 elif transform[0][0] != transform[1][1]: 887 flags = flags | WE_HAVE_AN_X_AND_Y_SCALE 888 data = data + struct.pack(">hh", 889 transform[0][0], transform[1][1]) 890 else: 891 flags = flags | WE_HAVE_A_SCALE 892 data = data + struct.pack(">h", 893 transform[0][0]) 894 895 glyphID = glyfTable.getGlyphID(self.glyphName) 896 return struct.pack(">HH", flags, glyphID) + data 897 898 def toXML(self, writer, ttFont): 899 attrs = [("glyphName", self.glyphName)] 900 if not hasattr(self, "firstPt"): 901 attrs = attrs + [("x", self.x), ("y", self.y)] 902 else: 903 attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)] 904 905 if hasattr(self, "transform"): 906 transform = self.transform 907 if transform[0][1] or transform[1][0]: 908 attrs = attrs + [ 909 ("scalex", transform[0][0]), ("scale01", transform[0][1]), 910 ("scale10", transform[1][0]), ("scaley", transform[1][1]), 911 ] 912 elif transform[0][0] != transform[1][1]: 913 attrs = attrs + [ 914 ("scalex", transform[0][0]), ("scaley", transform[1][1]), 915 ] 916 else: 917 attrs = attrs + [("scale", transform[0][0])] 918 attrs = attrs + [("flags", hex(self.flags))] 919 writer.simpletag("component", attrs) 920 writer.newline() 921 922 def fromXML(self, name, attrs, content, ttFont): 923 self.glyphName = attrs["glyphName"] 924 if "firstPt" in attrs: 925 self.firstPt = safeEval(attrs["firstPt"]) 926 self.secondPt = safeEval(attrs["secondPt"]) 927 else: 928 self.x = safeEval(attrs["x"]) 929 self.y = safeEval(attrs["y"]) 930 if "scale01" in attrs: 931 scalex = safeEval(attrs["scalex"]) 932 scale01 = safeEval(attrs["scale01"]) 933 scale10 = safeEval(attrs["scale10"]) 934 scaley = safeEval(attrs["scaley"]) 935 self.transform = [[scalex, scale01], [scale10, scaley]] 936 elif "scalex" in attrs: 937 scalex = safeEval(attrs["scalex"]) 938 scaley = safeEval(attrs["scaley"]) 939 self.transform = [[scalex, 0], [0, scaley]] 940 elif "scale" in attrs: 941 scale = safeEval(attrs["scale"]) 942 self.transform = [[scale, 0], [0, scale]] 943 self.flags = safeEval(attrs["flags"]) 944 945 def __ne__(self, other): 946 return not self.__eq__(other) 947 def __eq__(self, other): 948 if type(self) != type(other): 949 return NotImplemented 950 return self.__dict__ == other.__dict__ 951 952 class GlyphCoordinates(object): 953 954 def __init__(self, iterable=[]): 955 self._a = array.array("h") 956 self.extend(iterable) 957 958 def isFloat(self): 959 return self._a.typecode == 'f' 960 961 def _ensureFloat(self): 962 if self.isFloat(): 963 return 964 self._a = array.array("f", self._a) 965 966 def _checkFloat(self, p): 967 if any(isinstance(v, float) for v in p): 968 p = [int(v) if int(v) == v else v for v in p] 969 if any(isinstance(v, float) for v in p): 970 self._ensureFloat() 971 return p 972 973 @staticmethod 974 def zeros(count): 975 return GlyphCoordinates([(0,0)] * count) 976 977 def copy(self): 978 c = GlyphCoordinates() 979 c._a.extend(self._a) 980 return c 981 982 def __len__(self): 983 return len(self._a) // 2 984 985 def __getitem__(self, k): 986 if isinstance(k, slice): 987 indices = range(*k.indices(len(self))) 988 return [self[i] for i in indices] 989 return self._a[2*k],self._a[2*k+1] 990 991 def __setitem__(self, k, v): 992 if isinstance(k, slice): 993 indices = range(*k.indices(len(self))) 994 # XXX This only works if len(v) == len(indices) 995 # TODO Implement __delitem__ 996 for j,i in enumerate(indices): 997 self[i] = v[j] 998 return 999 v = self._checkFloat(v) 1000 self._a[2*k],self._a[2*k+1] = v 1001 1002 def __repr__(self): 1003 return 'GlyphCoordinates(['+','.join(str(c) for c in self)+'])' 1004 1005 def append(self, p): 1006 p = self._checkFloat(p) 1007 self._a.extend(tuple(p)) 1008 1009 def extend(self, iterable): 1010 for p in iterable: 1011 p = self._checkFloat(p) 1012 self._a.extend(p) 1013 1014 def relativeToAbsolute(self): 1015 a = self._a 1016 x,y = 0,0 1017 for i in range(len(a) // 2): 1018 a[2*i ] = x = a[2*i ] + x 1019 a[2*i+1] = y = a[2*i+1] + y 1020 1021 def absoluteToRelative(self): 1022 a = self._a 1023 x,y = 0,0 1024 for i in range(len(a) // 2): 1025 dx = a[2*i ] - x 1026 dy = a[2*i+1] - y 1027 x = a[2*i ] 1028 y = a[2*i+1] 1029 a[2*i ] = dx 1030 a[2*i+1] = dy 1031 1032 def translate(self, p): 1033 (x,y) = p 1034 a = self._a 1035 for i in range(len(a) // 2): 1036 a[2*i ] += x 1037 a[2*i+1] += y 1038 1039 def transform(self, t): 1040 a = self._a 1041 for i in range(len(a) // 2): 1042 x = a[2*i ] 1043 y = a[2*i+1] 1044 px = x * t[0][0] + y * t[1][0] 1045 py = x * t[0][1] + y * t[1][1] 1046 self[i] = (px, py) 1047 1048 def __ne__(self, other): 1049 return not self.__eq__(other) 1050 def __eq__(self, other): 1051 if type(self) != type(other): 1052 return NotImplemented 1053 return self._a == other._a 1054 1055 1056 def reprflag(flag): 1057 bin = "" 1058 if isinstance(flag, str): 1059 flag = byteord(flag) 1060 while flag: 1061 if flag & 0x01: 1062 bin = "1" + bin 1063 else: 1064 bin = "0" + bin 1065 flag = flag >> 1 1066 bin = (14 - len(bin)) * "0" + bin 1067 return bin 1068 1069