def compileActions(font, states): result, actions, actionIndex = b"", set(), {} for state in states: for _glyphClass, trans in state.Transitions.items(): actions.add(trans.compileLigActions()) # Sort the compiled actions in decreasing order of # length, so that the longer sequence come before the # shorter ones. For each compiled action ABCD, its # suffixes BCD, CD, and D do not be encoded separately # (in case they occur); instead, we can just store an # index that points into the middle of the longer # sequence. Every compiled AAT ligature sequence is # terminated with an end-of-sequence flag, which can # only be set on the last element of the sequence. # Therefore, it is sufficient to consider just the # suffixes. for a in sorted(actions, key=lambda x:(-len(x), x)): if a not in actionIndex: for i in range(0, len(a), 4): suffix = a[i:] suffixIndex = (len(result) + i) // 4 actionIndex.setdefault( suffix, suffixIndex) result += a result = pad(result, 4) return (result, actionIndex)
def setMacCreatorAndType(path, fileCreator, fileType): if xattr is not None: from fontTools.misc.textTools import pad if not all(len(s) == 4 for s in (fileCreator, fileType)): raise TypeError('arg must be string of 4 chars') finderInfo = pad(bytesjoin([fileType, fileCreator]), 32) xattr.setxattr(path, 'com.apple.FinderInfo', finderInfo) if MacOS is not None: MacOS.SetCreatorAndType(path, fileCreator, fileType)
def _writeFlavorData(self): """Write metadata and/or private data using appropiate padding.""" compressedMetaData = self.compressedMetaData privData = self.flavorData.privData if compressedMetaData and privData: compressedMetaData = pad(compressedMetaData, size=4) if compressedMetaData: self.file.seek(self.metaOffset) assert self.file.tell() == self.metaOffset self.file.write(compressedMetaData) if privData: self.file.seek(self.privOffset) assert self.file.tell() == self.privOffset self.file.write(privData)
def close(self): """ All tags must have been specified. Now write the table data and directory. """ if len(self.tables) != self.numTables: raise TTLibError("wrong number of tables; expected %d, found %d" % (self.numTables, len(self.tables))) if self.sfntVersion in ("\x00\x01\x00\x00", "true"): isTrueType = True elif self.sfntVersion == "OTTO": isTrueType = False else: raise TTLibError("Not a TrueType or OpenType font (bad sfntVersion)") # The WOFF2 spec no longer requires the glyph offsets to be 4-byte aligned. # However, the reference WOFF2 implementation still fails to reconstruct # 'unpadded' glyf tables, therefore we need to 'normalise' them. # See: # https://github.com/khaledhosny/ots/issues/60 # https://github.com/google/woff2/issues/15 if ( isTrueType and "glyf" in self.flavorData.transformedTables and "glyf" in self.tables ): self._normaliseGlyfAndLoca(padding=4) self._setHeadTransformFlag() # To pass the legacy OpenType Sanitiser currently included in browsers, # we must sort the table directory and data alphabetically by tag. # See: # https://github.com/google/woff2/pull/3 # https://lists.w3.org/Archives/Public/public-webfonts-wg/2015Mar/0000.html # TODO(user): remove to match spec once browsers are on newer OTS self.tables = OrderedDict(sorted(self.tables.items())) self.totalSfntSize = self._calcSFNTChecksumsLengthsAndOffsets() fontData = self._transformTables() compressedFont = brotli.compress(fontData, mode=brotli.MODE_FONT) self.totalCompressedSize = len(compressedFont) self.length = self._calcTotalSize() self.majorVersion, self.minorVersion = self._getVersion() self.reserved = 0 directory = self._packTableDirectory() self.file.seek(0) self.file.write(pad(directory + compressedFont, size=4)) self._writeFlavorData()
def close(self): """ All tags must have been specified. Now write the table data and directory. """ if len(self.tables) != self.numTables: raise TTLibError("wrong number of tables; expected %d, found %d" % (self.numTables, len(self.tables))) if self.sfntVersion in ("\x00\x01\x00\x00", "true"): isTrueType = True elif self.sfntVersion == "OTTO": isTrueType = False else: raise TTLibError("Not a TrueType or OpenType font (bad sfntVersion)") # The WOFF2 spec no longer requires the glyph offsets to be 4-byte aligned. # However, the reference WOFF2 implementation still fails to reconstruct # 'unpadded' glyf tables, therefore we need to 'normalise' them. # See: # https://github.com/khaledhosny/ots/issues/60 # https://github.com/google/woff2/issues/15 if isTrueType: self._normaliseGlyfAndLoca(padding=4) self._setHeadTransformFlag() # To pass the legacy OpenType Sanitiser currently included in browsers, # we must sort the table directory and data alphabetically by tag. # See: # https://github.com/google/woff2/pull/3 # https://lists.w3.org/Archives/Public/public-webfonts-wg/2015Mar/0000.html # TODO(user): remove to match spec once browsers are on newer OTS self.tables = OrderedDict(sorted(self.tables.items())) self.totalSfntSize = self._calcSFNTChecksumsLengthsAndOffsets() fontData = self._transformTables() compressedFont = brotli.compress(fontData, mode=brotli.MODE_FONT) self.totalCompressedSize = len(compressedFont) self.length = self._calcTotalSize() self.majorVersion, self.minorVersion = self._getVersion() self.reserved = 0 directory = self._packTableDirectory() self.file.seek(0) self.file.write(pad(directory + compressedFont, size=4)) self._writeFlavorData()
def compile(self, ttFont): if not hasattr(self, "glyphOrder"): self.glyphOrder = ttFont.getGlyphOrder() padding = self.padding assert padding in (0, 1, 2, 4) locations = [] currentLocation = 0 dataList = [] recalcBBoxes = ttFont.recalcBBoxes for glyphName in self.glyphOrder: glyph = self.glyphs[glyphName] glyphData = glyph.compile(self, recalcBBoxes) if padding > 1: glyphData = pad(glyphData, size=padding) locations.append(currentLocation) currentLocation = currentLocation + len(glyphData) dataList.append(glyphData) locations.append(currentLocation) if padding == 1 and currentLocation < 0x20000: # See if we can pad any odd-lengthed glyphs to allow loca # table to use the short offsets. indices = [i for i,glyphData in enumerate(dataList) if len(glyphData) % 2 == 1] if indices and currentLocation + len(indices) < 0x20000: # It fits. Do it. for i in indices: dataList[i] += b'\0' currentLocation = 0 for i,glyphData in enumerate(dataList): locations[i] = currentLocation currentLocation += len(glyphData) locations[len(dataList)] = currentLocation data = b''.join(dataList) if 'loca' in ttFont: ttFont['loca'].set(locations) if 'maxp' in ttFont: ttFont['maxp'].numGlyphs = len(self.glyphs) if not data: # As a special case when all glyph in the font are empty, add a zero byte # to the table, so that OTS doesn’t reject it, and to make the table work # on Windows as well. # See https://github.com/khaledhosny/ots/issues/52 data = b"\0" return data
def setMacCreatorAndType(path, fileCreator, fileType): """Set file creator and file type codes for a path. Note that if the ``xattr`` module is not installed, no action is taken but no error is raised. Args: path (str): A file path. fileCreator: A four-character file creator tag. fileType: A four-character file type tag. """ if xattr is not None: from fontTools.misc.textTools import pad if not all(len(s) == 4 for s in (fileCreator, fileType)): raise TypeError('arg must be string of 4 chars') finderInfo = pad(bytesjoin([fileType, fileCreator]), 32) xattr.setxattr(path, 'com.apple.FinderInfo', finderInfo)
def compile(self, ttFont): if not hasattr(self, "glyphOrder"): self.glyphOrder = ttFont.getGlyphOrder() padding = self.padding assert padding in (0, 1, 2, 4) locations = [] currentLocation = 0 dataList = [] recalcBBoxes = ttFont.recalcBBoxes for glyphName in self.glyphOrder: glyph = self.glyphs[glyphName] glyphData = glyph.compile(self, recalcBBoxes) if padding > 1: glyphData = pad(glyphData, size=padding) locations.append(currentLocation) currentLocation = currentLocation + len(glyphData) dataList.append(glyphData) locations.append(currentLocation) if padding == 1 and currentLocation < 0x20000: # See if we can pad any odd-lengthed glyphs to allow loca # table to use the short offsets. indices = [ i for i, glyphData in enumerate(dataList) if len(glyphData) % 2 == 1 ] if indices and currentLocation + len(indices) < 0x20000: # It fits. Do it. for i in indices: dataList[i] += b'\0' currentLocation = 0 for i, glyphData in enumerate(dataList): locations[i] = currentLocation currentLocation += len(glyphData) locations[len(dataList)] = currentLocation data = bytesjoin(dataList) if 'loca' in ttFont: ttFont['loca'].set(locations) if 'maxp' in ttFont: ttFont['maxp'].numGlyphs = len(self.glyphs) return data
def compile(self, ttFont): if not hasattr(self, "glyphOrder"): self.glyphOrder = ttFont.getGlyphOrder() padding = self.padding assert padding in (0, 1, 2, 4) locations = [] currentLocation = 0 dataList = [] recalcBBoxes = ttFont.recalcBBoxes for glyphName in self.glyphOrder: glyph = self.glyphs[glyphName] glyphData = glyph.compile(self, recalcBBoxes) if padding > 1: glyphData = pad(glyphData, size=padding) locations.append(currentLocation) currentLocation = currentLocation + len(glyphData) dataList.append(glyphData) locations.append(currentLocation) if padding == 1 and currentLocation < 0x20000: # See if we can pad any odd-lengthed glyphs to allow loca # table to use the short offsets. indices = [i for i,glyphData in enumerate(dataList) if len(glyphData) % 2 == 1] if indices and currentLocation + len(indices) < 0x20000: # It fits. Do it. for i in indices: dataList[i] += b'\0' currentLocation = 0 for i,glyphData in enumerate(dataList): locations[i] = currentLocation currentLocation += len(glyphData) locations[len(dataList)] = currentLocation data = bytesjoin(dataList) if 'loca' in ttFont: ttFont['loca'].set(locations) if 'maxp' in ttFont: ttFont['maxp'].numGlyphs = len(self.glyphs) return data
def test_pad(): assert len(pad(b'abcd', 4)) == 4 assert len(pad(b'abcde', 2)) == 6 assert len(pad(b'abcde', 4)) == 8 assert pad(b'abcdef', 4) == b'abcdef\x00\x00' assert pad(b'abcdef', 1) == b'abcdef'