"""Extend the Python codecs module with a few encodings that are used in OpenType (name table) but missing from Python. See https://github.com/fonttools/fonttools/issues/236 for details.""" import codecs import encodings class ExtendCodec(codecs.Codec): def __init__(self, name, base_encoding, mapping): self.name = name self.base_encoding = base_encoding self.mapping = mapping self.reverse = {v: k for k, v in mapping.items()} self.max_len = max(len(v) for v in mapping.values()) self.info = codecs.CodecInfo( name=self.name, encode=self.encode, decode=self.decode ) codecs.register_error(name, self.error) def _map(self, mapper, output_type, exc_type, input, errors): base_error_handler = codecs.lookup_error(errors) length = len(input) out = output_type() while input: # first try to use self.error as the error handler try: part = mapper(input, self.base_encoding, errors=self.name) out += part break # All converted except exc_type as e: # else convert the correct part, handle error as requested and continue out += mapper(input[: e.start], self.base_encoding, self.name) replacement, pos = base_error_handler(e) out += replacement input = input[pos:] return out, length def encode(self, input, errors="strict"): return self._map(codecs.encode, bytes, UnicodeEncodeError, input, errors) def decode(self, input, errors="strict"): return self._map(codecs.decode, str, UnicodeDecodeError, input, errors) def error(self, e): if isinstance(e, UnicodeDecodeError): for end in range(e.start + 1, e.end + 1): s = e.object[e.start : end] if s in self.mapping: return self.mapping[s], end elif isinstance(e, UnicodeEncodeError): for end in range(e.start + 1, e.start + self.max_len + 1): s = e.object[e.start : end] if s in self.reverse: return self.reverse[s], end e.encoding = self.name raise e _extended_encodings = { "x_mac_japanese_ttx": ( "shift_jis", { b"\xFC": chr(0x007C), b"\x7E": chr(0x007E), b"\x80": chr(0x005C), b"\xA0": chr(0x00A0), b"\xFD": chr(0x00A9), b"\xFE": chr(0x2122), b"\xFF": chr(0x2026), }, ), "x_mac_trad_chinese_ttx": ( "big5", { b"\x80": chr(0x005C), b"\xA0": chr(0x00A0), b"\xFD": chr(0x00A9), b"\xFE": chr(0x2122), b"\xFF": chr(0x2026), }, ), "x_mac_korean_ttx": ( "euc_kr", { b"\x80": chr(0x00A0), b"\x81": chr(0x20A9), b"\x82": chr(0x2014), b"\x83": chr(0x00A9), b"\xFE": chr(0x2122), b"\xFF": chr(0x2026), }, ), "x_mac_simp_chinese_ttx": ( "gb2312", { b"\x80": chr(0x00FC), b"\xA0": chr(0x00A0), b"\xFD": chr(0x00A9), b"\xFE": chr(0x2122), b"\xFF": chr(0x2026), }, ), } _cache = {} def search_function(name): name = encodings.normalize_encoding(name) # Rather undocumented... if name in _extended_encodings: if name not in _cache: base_encoding, mapping = _extended_encodings[name] assert name[-4:] == "_ttx" # Python 2 didn't have any of the encodings that we are implementing # in this file. Python 3 added aliases for the East Asian ones, mapping # them "temporarily" to the same base encoding as us, with a comment # suggesting that full implementation will appear some time later. # As such, try the Python version of the x_mac_... first, if that is found, # use *that* as our base encoding. This would make our encoding upgrade # to the full encoding when and if Python finally implements that. # http://bugs.python.org/issue24041 base_encodings = [name[:-4], base_encoding] for base_encoding in base_encodings: try: codecs.lookup(base_encoding) except LookupError: continue _cache[name] = ExtendCodec(name, base_encoding, mapping) break return _cache[name].info return None codecs.register(search_function)