1"""_g_l_y_f.py -- Converter classes for the 'glyf' table.""" 2 3 4from __future__ import print_function, division, absolute_import 5from fontTools.misc.py23 import * 6from fontTools.misc import sstruct 7from fontTools import ttLib 8from fontTools.misc.textTools import safeEval 9from fontTools.misc.arrayTools import calcBounds, calcIntBounds, pointInRect 10from fontTools.misc.bezierTools import calcQuadraticBounds 11from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi 12from . import DefaultTable 13from . import ttProgram 14import sys 15import struct 16import array 17import 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# 29SCALE_COMPONENT_OFFSET_DEFAULT = 0 # 0 == MS, 1 == Apple 30 31 32class 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 173glyphHeaderFormat = """ 174 > # big endian 175 numberOfContours: h 176 xMin: h 177 yMin: h 178 xMax: h 179 yMax: h 180""" 181 182# flags 183flagOnCurve = 0x01 184flagXShort = 0x02 185flagYShort = 0x04 186flagRepeat = 0x08 187flagXsame = 0x10 188flagYsame = 0x20 189flagReserved1 = 0x40 190flagReserved2 = 0x80 191 192 193ARG_1_AND_2_ARE_WORDS = 0x0001 # if set args are words otherwise they are bytes 194ARGS_ARE_XY_VALUES = 0x0002 # if set args are xy values, otherwise they are points 195ROUND_XY_TO_GRID = 0x0004 # for the xy values if above is true 196WE_HAVE_A_SCALE = 0x0008 # Sx = Sy, otherwise scale == 1.0 197NON_OVERLAPPING = 0x0010 # set to same value for all components (obsolete!) 198MORE_COMPONENTS = 0x0020 # indicates at least one more glyph after this one 199WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Sx, Sy 200WE_HAVE_A_TWO_BY_TWO = 0x0080 # t00, t01, t10, t11 201WE_HAVE_INSTRUCTIONS = 0x0100 # instructions follow 202USE_MY_METRICS = 0x0200 # apply these metrics to parent glyph 203OVERLAP_COMPOUND = 0x0400 # used by Apple in GX fonts 204SCALED_COMPONENT_OFFSET = 0x0800 # composite designed to have the component offset scaled (designed for Apple) 205UNSCALED_COMPONENT_OFFSET = 0x1000 # composite designed not to have the component offset scaled (designed for MS) 206 207 208class 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 790class 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 952class 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 1056def 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