def encodeFloat(f): # For CFF only, used in cffLib if f == 0.0: # 0.0 == +0.0 == -0.0 return realZeroBytes # Note: 14 decimal digits seems to be the limitation for CFF real numbers # in macOS. However, we use 8 here to match the implementation of AFDKO. s = "%.8G" % f if s[:2] == "0.": s = s[1:] elif s[:3] == "-0.": s = "-" + s[2:] nibbles = [] while s: c = s[0] s = s[1:] if c == "E": c2 = s[:1] if c2 == "-": s = s[1:] c = "E-" elif c2 == "+": s = s[1:] nibbles.append(realNibblesDict[c]) nibbles.append(0xf) if len(nibbles) % 2: nibbles.append(0xf) d = bytechr(30) for i in range(0, len(nibbles), 2): d = d + bytechr(nibbles[i] << 4 | nibbles[i + 1]) return d
def writePFB(path, data): chunks = findEncryptedChunks(data) with open(path, "wb") as f: for isEncrypted, chunk in chunks: if isEncrypted: code = 2 else: code = 1 f.write(bytechr(128) + bytechr(code)) f.write(longToString(len(chunk))) f.write(chunk) f.write(bytechr(128) + bytechr(3))
def compile(self, ttFont): d = self.__dict__.copy() d["nameLength"] = bytechr(len(self.baseGlyphName)) d["uniqueName"] = self.compilecompileUniqueName(self.uniqueName, 28) METAMD5List = eval(self.METAMD5) d["METAMD5"] = b"" for val in METAMD5List: d["METAMD5"] += bytechr(val) assert (len(d["METAMD5"]) == 16 ), "Failed to pack 16 byte MD5 hash in SING table" data = sstruct.pack(SINGFormat, d) data = data + tobytes(self.baseGlyphName) return data
def compile(self, ttFont): self.version = 0 numGlyphs = ttFont['maxp'].numGlyphs glyphOrder = ttFont.getGlyphOrder() self.recordSize = 4 * ((2 + numGlyphs + 3) // 4) pad = (self.recordSize - 2 - numGlyphs) * b"\0" self.numRecords = len(self.hdmx) data = sstruct.pack(hdmxHeaderFormat, self) items = sorted(self.hdmx.items()) for ppem, widths in items: data = data + bytechr(ppem) + bytechr(max(widths.values())) for glyphID in range(len(glyphOrder)): width = widths[glyphOrder[glyphID]] data = data + bytechr(width) data = data + pad return data
def fromXML(self, name, attrs, content): from fontTools.misc.textTools import binary2num, readHex if attrs.get("raw"): self.setBytecode(readHex(content)) return content = strjoin(content) content = content.split() program = [] end = len(content) i = 0 while i < end: token = content[i] i = i + 1 try: token = int(token) except ValueError: try: token = strToFixedToFloat(token, precisionBits=16) except ValueError: program.append(token) if token in ('hintmask', 'cntrmask'): mask = content[i] maskBytes = b"" for j in range(0, len(mask), 8): maskBytes = maskBytes + bytechr( binary2num(mask[j:j + 8])) program.append(maskBytes) i = i + 1 else: program.append(token) else: program.append(token) self.setProgram(program)
def toUnicode(self, errors='strict'): """ If self.string is a Unicode string, return it; otherwise try decoding the bytes in self.string to a Unicode string using the encoding of this entry as returned by self.getEncoding(); Note that self.getEncoding() returns 'ascii' if the encoding is unknown to the library. Certain heuristics are performed to recover data from bytes that are ill-formed in the chosen encoding, or that otherwise look misencoded (mostly around bad UTF-16BE encoded bytes, or bytes that look like UTF-16BE but marked otherwise). If the bytes are ill-formed and the heuristics fail, the error is handled according to the errors parameter to this function, which is passed to the underlying decode() function; by default it throws a UnicodeDecodeError exception. Note: The mentioned heuristics mean that roundtripping a font to XML and back to binary might recover some misencoded data whereas just loading the font and saving it back will not change them. """ def isascii(b): return (b >= 0x20 and b <= 0x7E) or b in [0x09, 0x0A, 0x0D] encoding = self.getEncoding() string = self.string if isinstance( string, bytes) and encoding == 'utf_16_be' and len(string) % 2 == 1: # Recover badly encoded UTF-16 strings that have an odd number of bytes: # - If the last byte is zero, drop it. Otherwise, # - If all the odd bytes are zero and all the even bytes are ASCII, # prepend one zero byte. Otherwise, # - If first byte is zero and all other bytes are ASCII, insert zero # bytes between consecutive ASCII bytes. # # (Yes, I've seen all of these in the wild... sigh) if byteord(string[-1]) == 0: string = string[:-1] elif all( byteord(b) == 0 if i % 2 else isascii(byteord(b)) for i, b in enumerate(string)): string = b'\0' + string elif byteord(string[0]) == 0 and all( isascii(byteord(b)) for b in string[1:]): string = bytesjoin(b'\0' + bytechr(byteord(b)) for b in string[1:]) string = tostr(string, encoding=encoding, errors=errors) # If decoded strings still looks like UTF-16BE, it suggests a double-encoding. # Fix it up. if all( ord(c) == 0 if i % 2 == 0 else isascii(ord(c)) for i, c in enumerate(string)): # If string claims to be Mac encoding, but looks like UTF-16BE with ASCII text, # narrow it down. string = ''.join(c for c in string[1::2]) return string
def toString(self): data = bytechr(self.flags) if (self.flags & 0x3F) == 0x3F: data += struct.pack('>4s', self.tag.tobytes()) data += packBase128(self.origLength) if self.transformed: data += packBase128(self.length) return data
def encodeInt(value, fourByteOp=fourByteOp, bytechr=bytechr, pack=struct.pack, unpack=struct.unpack): if -107 <= value <= 107: code = bytechr(value + 139) elif 108 <= value <= 1131: value = value - 108 code = bytechr((value >> 8) + 247) + bytechr(value & 0xFF) elif -1131 <= value <= -108: value = -value - 108 code = bytechr((value >> 8) + 251) + bytechr(value & 0xFF) elif fourByteOp is None: # T2 only supports 2 byte ints if -32768 <= value <= 32767: code = bytechr(28) + pack(">h", value) else: # Backwards compatible hack: due to a previous bug in FontTools, # 16.16 fixed numbers were written out as 4-byte ints. When # these numbers were small, they were wrongly written back as # small ints instead of 4-byte ints, breaking round-tripping. # This here workaround doesn't do it any better, since we can't # distinguish anymore between small ints that were supposed to # be small fixed numbers and small ints that were just small # ints. Hence the warning. log.warning( "4-byte T2 number got passed to the " "IntType handler. This should happen only when reading in " "old XML files.\n") code = bytechr(255) + pack(">l", value) else: code = fourByteOp + pack(">l", value) return code
def test_unsupportedLookupType(self): data = bytesjoin([ MORX_NONCONTEXTUAL_DATA[:67], bytechr(66), MORX_NONCONTEXTUAL_DATA[69:] ]) with self.assertRaisesRegex(AssertionError, r"unsupported 'morx' lookup type 66"): morx = newTable('morx') morx.decompile(data, FakeFont(['.notdef']))
def _reverseBytes(data): if len(data) != 1: return bytesjoin(map(_reverseBytes, data)) byte = byteord(data) result = 0 for i in range(8): result = result << 1 result |= byte & 1 byte = byte >> 1 return bytechr(result)
def getnexttoken( self, # localize some stuff, for performance len=len, ps_special=ps_special, stringmatch=stringRE.match, hexstringmatch=hexstringRE.match, commentmatch=commentRE.match, endmatch=endofthingRE.match): self.skipwhite() if self.pos >= self.len: return None, None pos = self.pos buf = self.buf char = bytechr(byteord(buf[pos])) if char in ps_special: if char in b'{}[]': tokentype = 'do_special' token = char elif char == b'%': tokentype = 'do_comment' _, nextpos = commentmatch(buf, pos).span() token = buf[pos:nextpos] elif char == b'(': tokentype = 'do_string' m = stringmatch(buf, pos) if m is None: raise PSTokenError('bad string at character %d' % pos) _, nextpos = m.span() token = buf[pos:nextpos] elif char == b'<': tokentype = 'do_hexstring' m = hexstringmatch(buf, pos) if m is None: raise PSTokenError('bad hexstring at character %d' % pos) _, nextpos = m.span() token = buf[pos:nextpos] else: raise PSTokenError('bad token at character %d' % pos) else: if char == b'/': tokentype = 'do_literal' m = endmatch(buf, pos + 1) else: tokentype = '' m = endmatch(buf, pos) if m is None: raise PSTokenError('bad token at character %d' % pos) _, nextpos = m.span() token = buf[pos:nextpos] self.pos = pos + len(token) token = tostr(token, encoding=self.encoding) return tokentype, token
def _binary2data(binary): byteList = [] for bitLoc in range(0, len(binary), 8): byteString = binary[bitLoc:bitLoc + 8] curByte = 0 for curBit in reversed(byteString): curByte = curByte << 1 if curBit == '1': curByte |= 1 byteList.append(bytechr(curByte)) return bytesjoin(byteList)
def test_ReservedCoverageFlags(self): # 8A BC DE = TextDirection=Vertical, Reserved=0xABCDE # Note that the lower 4 bits of the first byte are already # part of the Reserved value. We test the full round-trip # to encoding and decoding is quite hairy. data = bytesjoin([ MORX_REARRANGEMENT_DATA[:28], bytechr(0x8A), bytechr(0xBC), bytechr(0xDE), MORX_REARRANGEMENT_DATA[31:] ]) table = newTable('morx') table.decompile(data, self.font) subtable = table.table.MorphChain[0].MorphSubtable[0] self.assertEqual(subtable.Reserved, 0xABCDE) xml = getXML(table.toXML) self.assertIn(' <Reserved value="0xabcde"/>', xml) table2 = newTable('morx') for name, attrs, content in parseXML(xml): table2.fromXML(name, attrs, content, font=self.font) self.assertEqual(hexStr(table2.compile(self.font)[28:31]), "8abcde")
def writeLWFN(path, data): # Res.FSpCreateResFile was deprecated in OS X 10.5 Res.FSpCreateResFile(path, "just", "LWFN", 0) resRef = Res.FSOpenResFile(path, 2) # write-only try: Res.UseResFile(resRef) resID = 501 chunks = findEncryptedChunks(data) for isEncrypted, chunk in chunks: if isEncrypted: code = 2 else: code = 1 while chunk: res = Res.Resource( bytechr(code) + '\0' + chunk[:LWFNCHUNKSIZE - 2]) res.AddResource('POST', resID, '') chunk = chunk[LWFNCHUNKSIZE - 2:] resID = resID + 1 res = Res.Resource(bytechr(5) + '\0') res.AddResource('POST', resID, '') finally: Res.CloseResFile(resRef)
def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False): if metrics is None: metrics = self.metrics assert 0 <= row and row < metrics.height, "Illegal row access in bitmap" # Loop through each byte. This can cover two bytes in the original data or # a single byte if things happen to be aligned. The very last entry might # not be aligned so take care to trim the binary data to size and pad with # zeros in the row data. Bit aligned data is somewhat tricky. # # Example of data cut. Data cut represented in x's. # '|' represents byte boundary. # data = ...0XX|XXXXXX00|000... => XXXXXXXX # or # data = ...0XX|XXXX0000|000... => XXXXXX00 # or # data = ...000|XXXXXXXX|000... => XXXXXXXX # or # data = ...000|00XXXX00|000... => XXXX0000 # dataList = [] bitRange = self._getBitRange(row, bitDepth, metrics) stepRange = bitRange + (8, ) for curBit in range(*stepRange): endBit = min(curBit + 8, bitRange[1]) numBits = endBit - curBit cutPoint = curBit % 8 firstByteLoc = curBit // 8 secondByteLoc = endBit // 8 if firstByteLoc < secondByteLoc: numBitsCut = 8 - cutPoint else: numBitsCut = endBit - curBit curByte = _reverseBytes(self.imageData[firstByteLoc]) firstHalf = byteord(curByte) >> cutPoint firstHalf = ((1 << numBitsCut) - 1) & firstHalf newByte = firstHalf if firstByteLoc < secondByteLoc and secondByteLoc < len( self.imageData): curByte = _reverseBytes(self.imageData[secondByteLoc]) secondHalf = byteord(curByte) << numBitsCut newByte = (firstHalf | secondHalf) & ((1 << numBits) - 1) dataList.append(bytechr(newByte)) # The way the data is kept is opposite the algorithm used. data = bytesjoin(dataList) if not reverseBytes: data = _reverseBytes(data) return data
def compile(self, isCFF2=False): if self.bytecode is not None: return opcodes = self.opcodes program = self.program if isCFF2: # If present, remove return and endchar operators. if program and program[-1] in ("return", "endchar"): program = program[:-1] elif program and not isinstance(program[-1], str): raise CharStringCompileError( "T2CharString or Subr has items on the stack after last operator." ) bytecode = [] encodeInt = self.getIntEncoder() encodeFixed = self.getFixedEncoder() i = 0 end = len(program) while i < end: token = program[i] i = i + 1 if isinstance(token, str): try: bytecode.extend(bytechr(b) for b in opcodes[token]) except KeyError: raise CharStringCompileError("illegal operator: %s" % token) if token in ('hintmask', 'cntrmask'): bytecode.append(program[i]) # hint mask i = i + 1 elif isinstance(token, int): bytecode.append(encodeInt(token)) elif isinstance(token, float): bytecode.append(encodeFixed(token)) else: assert 0, "unsupported type: %s" % type(token) try: bytecode = bytesjoin(bytecode) except TypeError: log.error(bytecode) raise self.setBytecode(bytecode)
def readPFB(path, onlyHeader=False): """reads a PFB font file, returns raw data""" data = [] with open(path, "rb") as f: while True: if f.read(1) != bytechr(128): raise T1Error('corrupt PFB file') code = byteord(f.read(1)) if code in [1, 2]: chunklen = stringToLong(f.read(4)) chunk = f.read(chunklen) assert len(chunk) == chunklen data.append(chunk) elif code == 3: break else: raise T1Error('bad chunk code: ' + repr(code)) if onlyHeader: break data = bytesjoin(data) assertType1(data) return data
def checkFlags(self, flags, textDirection, processingOrder, checkCompile=True): data = bytesjoin([ MORX_REARRANGEMENT_DATA[:28], bytechr(flags << 4), MORX_REARRANGEMENT_DATA[29:] ]) xml = [] for line in MORX_REARRANGEMENT_XML: if line.startswith(' <TextDirection '): line = ' <TextDirection value="%s"/>' % textDirection elif line.startswith(' <ProcessingOrder '): line = ' <ProcessingOrder value="%s"/>' % processingOrder xml.append(line) table1 = newTable('morx') table1.decompile(data, self.font) self.assertEqual(getXML(table1.toXML), xml) if checkCompile: table2 = newTable('morx') for name, attrs, content in parseXML(xml): table2.fromXML(name, attrs, content, font=self.font) self.assertEqual(hexStr(table2.compile(self.font)), hexStr(data))
def _encryptChar(plain, R): plain = byteord(plain) cipher = ((plain ^ (R >> 8))) & 0xFF R = ((cipher + R) * 52845 + 22719) & 0xFFFF return bytechr(cipher), R
encodeIntCFF = getIntEncoder("cff") encodeIntT1 = getIntEncoder("t1") encodeIntT2 = getIntEncoder("t2") def encodeFixed(f, pack=struct.pack): """For T2 only""" value = floatToFixed(f, precisionBits=16) if value & 0xFFFF == 0: # check if the fractional part is zero return encodeIntT2(value >> 16) # encode only the integer part else: return b"\xff" + pack(">l", value) # encode the entire fixed point value realZeroBytes = bytechr(30) + bytechr(0xf) def encodeFloat(f): # For CFF only, used in cffLib if f == 0.0: # 0.0 == +0.0 == -0.0 return realZeroBytes # Note: 14 decimal digits seems to be the limitation for CFF real numbers # in macOS. However, we use 8 here to match the implementation of AFDKO. s = "%.8G" % f if s[:2] == "0.": s = s[1:] elif s[:3] == "-0.": s = "-" + s[2:] nibbles = [] while s:
def packPStrings(strings): data = b"" for s in strings: data = data + bytechr(len(s)) + tobytes(s, encoding="latin1") return data
def longToString(long): s = b"" for i in range(4): s += bytechr((long & (0xff << (i * 8))) >> i * 8) return s
def _decryptChar(cipher, R): cipher = byteord(cipher) plain = ((cipher ^ (R >> 8))) & 0xFF R = ((cipher + R) * 52845 + 22719) & 0xFFFF return bytechr(plain), R