# Source code for fontTools.ttLib.tables._n_a_m_e

    # -*- coding: utf-8 -*-
    from fontTools.misc import sstruct
    from fontTools.misc.textTools import bytechr, byteord, bytesjoin, strjoin, tobytes, tostr, safeEval
    from fontTools.misc.encodingTools import getEncoding
    from fontTools.ttLib import newTable
    from . import DefaultTable
    import struct
    import logging

    log = logging.getLogger(__name__)

    nameRecordFormat = """
    		>	# big endian
    		platformID:	H
    		platEncID:	H
    		langID:		H
    		nameID:		H
    		length:		H
    		offset:		H
    """

    nameRecordSize = sstruct.calcsize(nameRecordFormat)

    class table__n_a_m_e(DefaultTable.DefaultTable):
    	dependencies = ["ltag"]

    	def decompile(self, data, ttFont):
    		format, n, stringOffset = struct.unpack(b">HHH", data[:6])
    		expectedStringOffset = 6 + n * nameRecordSize
    		if stringOffset != expectedStringOffset:
    			log.error(
    				"'name' table stringOffset incorrect. Expected: %s; Actual: %s",
    				expectedStringOffset, stringOffset)
    		stringData = data[stringOffset:]
    		data = data[6:]
    		self.names = []
    		for i in range(n):
    			if len(data) < 12:
    				log.error('skipping malformed name record #%d', i)
    				continue
    			name, data = sstruct.unpack2(nameRecordFormat, data, NameRecord())
    			name.string = stringData[name.offset:name.offset+name.length]
    			if name.offset + name.length > len(stringData):
    				log.error('skipping malformed name record #%d', i)
    				continue
    			assert len(name.string) == name.length
    			#if (name.platEncID, name.platformID) in ((0, 0), (1, 3)):
    			#	if len(name.string) % 2:
    			#		print "2-byte string doesn't have even length!"
    			#		print name.__dict__
    			del name.offset, name.length
    			self.names.append(name)

    	def compile(self, ttFont):
    		if not hasattr(self, "names"):
    			# only happens when there are NO name table entries read
    			# from the TTX file
    			self.names = []
    		names = self.names
    		names.sort() # sort according to the spec; see NameRecord.__lt__()
    		stringData = b""
    		format = 0
    		n = len(names)
    		stringOffset = 6 + n * sstruct.calcsize(nameRecordFormat)
    		data = struct.pack(b">HHH", format, n, stringOffset)
    		lastoffset = 0
    		done = {}  # remember the data so we can reuse the "pointers"
    		for name in names:
    			string = name.toBytes()
    			if string in done:
    				name.offset, name.length = done[string]
    			else:
    				name.offset, name.length = done[string] = len(stringData), len(string)
    				stringData = bytesjoin([stringData, string])
    			data = data + sstruct.pack(nameRecordFormat, name)
    		return data + stringData

    	def toXML(self, writer, ttFont):
    		for name in self.names:
    			name.toXML(writer, ttFont)

    	def fromXML(self, name, attrs, content, ttFont):
    		if name != "namerecord":
    			return # ignore unknown tags
    		if not hasattr(self, "names"):
    			self.names = []
    		name = NameRecord()
    		self.names.append(name)
    		name.fromXML(name, attrs, content, ttFont)

    	def getName(self, nameID, platformID, platEncID, langID=None):
    		for namerecord in self.names:
    			if (	namerecord.nameID == nameID and
    					namerecord.platformID == platformID and
    					namerecord.platEncID == platEncID):
    				if langID is None or namerecord.langID == langID:
    					return namerecord
    		return None # not found

    	def getDebugName(self, nameID):
    		englishName = someName = None
    		for name in self.names:
    			if name.nameID != nameID:
    				continue
    			try:
    				unistr = name.toUnicode()
    			except UnicodeDecodeError:
    				continue

    			someName = unistr
    			if (name.platformID, name.langID) in ((1, 0), (3, 0x409)):
    				englishName = unistr
    				break
    		if englishName:
    			return englishName
    		elif someName:
    			return someName
    		else:
    			return None

    	def getFirstDebugName(self, nameIDs):
    		for nameID in nameIDs:
    			name = self.getDebugName(nameID)
    			if name is not None:
    				return name
    		return None

    	def getBestFamilyName(self):
    		# 21 = WWS Family Name
    		# 16 = Typographic Family Name
    		# 1 = Family Name
    		return self.getFirstDebugName((21, 16, 1))

    	def getBestSubFamilyName(self):
    		# 22 = WWS SubFamily Name
    		# 17 = Typographic SubFamily Name
    		# 2 = SubFamily Name
    		return self.getFirstDebugName((22, 17, 2))

    	def getBestFullName(self):
    		# 4 = Full Name
    		# 6 = PostScript Name
    		for nameIDs in ((21, 22), (16, 17), (1, 2), (4, ), (6, )):
    			if len(nameIDs) == 2:
    				name_fam = self.getDebugName(nameIDs[0])
    				name_subfam = self.getDebugName(nameIDs[1])
    				if None in [name_fam, name_subfam]:
    					continue  # if any is None, skip
    				name = f"{name_fam} {name_subfam}"
    				if name_subfam.lower() == 'regular':
    					name = f"{name_fam}"
    				return name
    			else:
    				name = self.getDebugName(nameIDs[0])
    				if name is not None:
    					return name
    		return None

    	def setName(self, string, nameID, platformID, platEncID, langID):
    		""" Set the 'string' for the name record identified by 'nameID', 'platformID',
    		'platEncID' and 'langID'. If a record with that nameID doesn't exist, create it
    		and append to the name table.

    		'string' can be of type `str` (`unicode` in PY2) or `bytes`. In the latter case,
    		it is assumed to be already encoded with the correct plaform-specific encoding
    		identified by the (platformID, platEncID, langID) triplet. A warning is issued
    		to prevent unexpected results.
    		"""
    		if not hasattr(self, 'names'):
    			self.names = []
    		if not isinstance(string, str):
    			if isinstance(string, bytes):
    				log.warning(
    					"name string is bytes, ensure it's correctly encoded: %r", string)
    			else:
    				raise TypeError(
    					"expected unicode or bytes, found %s: %r" % (
    						type(string).__name__, string))
    		namerecord = self.getName(nameID, platformID, platEncID, langID)
    		if namerecord:
    			namerecord.string = string
    		else:
    			self.names.append(makeName(string, nameID, platformID, platEncID, langID))

    	def removeNames(self, nameID=None, platformID=None, platEncID=None, langID=None):
    		"""Remove any name records identified by the given combination of 'nameID',
    		'platformID', 'platEncID' and 'langID'.
    		"""
    		args = {
    			argName: argValue
    			for argName, argValue in (
    				("nameID", nameID),
    				("platformID", platformID),
    				("platEncID", platEncID),
    				("langID", langID),
    			)
    			if argValue is not None
    		}
    		if not args:
    			# no arguments, nothing to do
    			return
    		self.names = [
    			rec for rec in self.names
    			if any(
    				argValue != getattr(rec, argName)
    				for argName, argValue in args.items()
    			)
    		]

    	def _findUnusedNameID(self, minNameID=256):
    		"""Finds an unused name id.

    		The nameID is assigned in the range between 'minNameID' and 32767 (inclusive),
    		following the last nameID in the name table.
    		"""
    		names = getattr(self, 'names', [])
    		nameID = 1 + max([n.nameID for n in names] + [minNameID - 1])
    		if nameID > 32767:
    			raise ValueError("nameID must be less than 32768")
    		return nameID

    	def findMultilingualName(self, names, windows=True, mac=True, minNameID=0):
    		"""Return the name ID of an existing multilingual name that
    		matches the 'names' dictionary, or None if not found.

    		'names' is a dictionary with the name in multiple languages,
    		such as {'en': 'Pale', 'de': 'Blaß', 'de-CH': 'Blass'}.
    		The keys can be arbitrary IETF BCP 47 language codes;
    		the values are Unicode strings.

    		If 'windows' is True, the returned name ID is guaranteed
    		exist for all requested languages for platformID=3 and
    		platEncID=1.
    		If 'mac' is True, the returned name ID is guaranteed to exist
    		for all requested languages for platformID=1 and platEncID=0.

    		The returned name ID will not be less than the 'minNameID'
    		argument.
    		"""
    		# Gather the set of requested
    		#   (string, platformID, platEncID, langID)
    		# tuples
    		reqNameSet = set()
    		for lang, name in sorted(names.items()):
    			if windows:
    				windowsName = _makeWindowsName(name, None, lang)
    				if windowsName is not None:
    					reqNameSet.add((windowsName.string,
    					                windowsName.platformID,
    					                windowsName.platEncID,
    					                windowsName.langID))
    			if mac:
    				macName = _makeMacName(name, None, lang)
    				if macName is not None:
    					reqNameSet.add((macName.string,
    				                    macName.platformID,
    				                    macName.platEncID,
    				                    macName.langID))

    		# Collect matching name IDs
    		matchingNames = dict()
    		for name in self.names:
    			try:
    				key = (name.toUnicode(), name.platformID,
    				       name.platEncID, name.langID)
    			except UnicodeDecodeError:
    				continue
    			if key in reqNameSet and name.nameID >= minNameID:
    				nameSet = matchingNames.setdefault(name.nameID, set())
    				nameSet.add(key)

    		# Return the first name ID that defines all requested strings
    		for nameID, nameSet in sorted(matchingNames.items()):
    			if nameSet == reqNameSet:
    				return nameID

    		return None  # not found

    	def addMultilingualName(self, names, ttFont=None, nameID=None,
    	                        windows=True, mac=True, minNameID=0):
    		"""Add a multilingual name, returning its name ID

    		'names' is a dictionary with the name in multiple languages,
    		such as {'en': 'Pale', 'de': 'Blaß', 'de-CH': 'Blass'}.
    		The keys can be arbitrary IETF BCP 47 language codes;
    		the values are Unicode strings.

    		'ttFont' is the TTFont to which the names are added, or None.
    		If present, the font's 'ltag' table can get populated
    		to store exotic language codes, which allows encoding
    		names that otherwise cannot get encoded at all.

    		'nameID' is the name ID to be used, or None to let the library
    		find an existing set of name records that match, or pick an
    		unused name ID.

    		If 'windows' is True, a platformID=3 name record will be added.
    		If 'mac' is True, a platformID=1 name record will be added.

    		If the 'nameID' argument is None, the created nameID will not
    		be less than the 'minNameID' argument.
    		"""
    		if not hasattr(self, 'names'):
    			self.names = []
    		if nameID is None:
    			# Reuse nameID if possible
    			nameID = self.findMultilingualName(
    				names, windows=windows, mac=mac, minNameID=minNameID)
    			if nameID is not None:
    				return nameID
    			nameID = self._findUnusedNameID()
    		# TODO: Should minimize BCP 47 language codes.
    		# https://github.com/fonttools/fonttools/issues/930
    		for lang, name in sorted(names.items()):
    			if windows:
    				windowsName = _makeWindowsName(name, nameID, lang)
    				if windowsName is not None:
    					self.names.append(windowsName)
    				else:
    					# We cannot not make a Windows name: make sure we add a
    					# Mac name as a fallback. This can happen for exotic
    					# BCP47 language tags that have no Windows language code.
    					mac = True
    			if mac:
    				macName = _makeMacName(name, nameID, lang, ttFont)
    				if macName is not None:
    					self.names.append(macName)
    		return nameID

    	def addName(self, string, platforms=((1, 0, 0), (3, 1, 0x409)), minNameID=255):
    		""" Add a new name record containing 'string' for each (platformID, platEncID,
    		langID) tuple specified in the 'platforms' list.

    		The nameID is assigned in the range between 'minNameID'+1 and 32767 (inclusive),
    		following the last nameID in the name table.
    		If no 'platforms' are specified, two English name records are added, one for the
    		Macintosh (platformID=0), and one for the Windows platform (3).

    		The 'string' must be a Unicode string, so it can be encoded with different,
    		platform-specific encodings.

    		Return the new nameID.
    		"""
    		assert len(platforms) > 0, \
    			"'platforms' must contain at least one (platformID, platEncID, langID) tuple"
    		if not hasattr(self, 'names'):
    			self.names = []
    		if not isinstance(string, str):
    			raise TypeError(
    				"expected str, found %s: %r" % (type(string).__name__, string))
    		nameID = self._findUnusedNameID(minNameID + 1)
    		for platformID, platEncID, langID in platforms:
    			self.names.append(makeName(string, nameID, platformID, platEncID, langID))
    		return nameID

    def makeName(string, nameID, platformID, platEncID, langID):
    	name = NameRecord()
    	name.string, name.nameID, name.platformID, name.platEncID, name.langID = (
    		string, nameID, platformID, platEncID, langID)
    	return name

    def _makeWindowsName(name, nameID, language):
    	"""Create a NameRecord for the Microsoft Windows platform

    	'language' is an arbitrary IETF BCP 47 language identifier such
    	as 'en', 'de-CH', 'de-AT-1901', or 'fa-Latn'. If Microsoft Windows
    	does not support the desired language, the result will be None.
    	Future versions of fonttools might return a NameRecord for the
    	OpenType 'name' table format 1, but this is not implemented yet.
    	"""
    	langID = _WINDOWS_LANGUAGE_CODES.get(language.lower())
    	if langID is not None:
    		return makeName(name, nameID, 3, 1, langID)
    	else:
    		log.warning("cannot add Windows name in language %s "
    		            "because fonttools does not yet support "
    		            "name table format 1" % language)
    		return None

    def _makeMacName(name, nameID, language, font=None):
    	"""Create a NameRecord for Apple platforms

    	'language' is an arbitrary IETF BCP 47 language identifier such
    	as 'en', 'de-CH', 'de-AT-1901', or 'fa-Latn'. When possible, we
    	create a Macintosh NameRecord that is understood by old applications
    	(platform ID 1 and an old-style Macintosh language enum). If this
    	is not possible, we create a Unicode NameRecord (platform ID 0)
    	whose language points to the font’s 'ltag' table. The latter
    	can encode any string in any language, but legacy applications
    	might not recognize the format (in which case they will ignore
    	those names).

    	'font' should be the TTFont for which you want to create a name.
    	If 'font' is None, we only return NameRecords for legacy Macintosh;
    	in that case, the result will be None for names that need to
    	be encoded with an 'ltag' table.

    	See the section “The language identifier” in Apple’s specification:
    	https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
    	"""
    	macLang = _MAC_LANGUAGE_CODES.get(language.lower())
    	macScript = _MAC_LANGUAGE_TO_SCRIPT.get(macLang)
    	if macLang is not None and macScript is not None:
    		encoding = getEncoding(1, macScript, macLang, default="ascii")
    		# Check if we can actually encode this name. If we can't,
    		# for example because we have no support for the legacy
    		# encoding, or because the name string contains Unicode
    		# characters that the legacy encoding cannot represent,
    		# we fall back to encoding the name in Unicode and put
    		# the language tag into the ltag table.
    		try:
    			_ = tobytes(name, encoding, errors="strict")
    			return makeName(name, nameID, 1, macScript, macLang)
    		except UnicodeEncodeError:
    			pass
    	if font is not None:
    		ltag = font.tables.get("ltag")
    		if ltag is None:
    			ltag = font["ltag"] = newTable("ltag")
    		# 0 = Unicode; 4 = “Unicode 2.0 or later semantics (non-BMP characters allowed)”
    		# “The preferred platform-specific code for Unicode would be 3 or 4.”
    		# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
    		return makeName(name, nameID, 0, 4, ltag.addTag(language))
    	else:
    		log.warning("cannot store language %s into 'ltag' table "
    		            "without having access to the TTFont object" %
    		            language)
    		return None

    class NameRecord(object):

    	def getEncoding(self, default='ascii'):
    		"""Returns the Python encoding name for this name entry based on its platformID,
    		platEncID, and langID.  If encoding for these values is not known, by default
    		'ascii' is returned.  That can be overriden by passing a value to the default
    		argument.
    		"""
    		return getEncoding(self.platformID, self.platEncID, self.langID, default)

    	def encodingIsUnicodeCompatible(self):
    		return self.getEncoding(None) in ['utf_16_be', 'ucs2be', 'ascii', 'latin1']

    	def __str__(self):
    		return self.toStr(errors='backslashreplace')

    	def isUnicode(self):
    		return (self.platformID == 0 or
    			(self.platformID == 3 and self.platEncID in [0, 1, 10]))

    	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 toBytes(self, errors='strict'):
    		""" If self.string is a bytes object, return it; otherwise try encoding
    		the Unicode string in self.string to bytes 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.

    		If the Unicode string cannot be encoded to bytes in the chosen encoding,
    		the error is handled according to the errors parameter to this function,
    		which is passed to the underlying encode() function; by default it throws a
    		UnicodeEncodeError exception.
    		"""
    		return tobytes(self.string, encoding=self.getEncoding(), errors=errors)

    	toStr = toUnicode

    	def toXML(self, writer, ttFont):
    		try:
    			unistr = self.toUnicode()
    		except UnicodeDecodeError:
    			unistr = None
    		attrs = [
    				("nameID", self.nameID),
    				("platformID", self.platformID),
    				("platEncID", self.platEncID),
    				("langID", hex(self.langID)),
    			]

    		if unistr is None or not self.encodingIsUnicodeCompatible():
    			attrs.append(("unicode", unistr is not None))

    		writer.begintag("namerecord", attrs)
    		writer.newline()
    		if unistr is not None:
    			writer.write(unistr)
    		else:
    			writer.write8bit(self.string)
    		writer.newline()
    		writer.endtag("namerecord")
    		writer.newline()

    	def fromXML(self, name, attrs, content, ttFont):
    		self.nameID = safeEval(attrs["nameID"])
    		self.platformID = safeEval(attrs["platformID"])
    		self.platEncID = safeEval(attrs["platEncID"])
    		self.langID =  safeEval(attrs["langID"])
    		s = strjoin(content).strip()
    		encoding = self.getEncoding()
    		if self.encodingIsUnicodeCompatible() or safeEval(attrs.get("unicode", "False")):
    			self.string = s.encode(encoding)
    		else:
    			# This is the inverse of write8bit...
    			self.string = s.encode("latin1")

    	def __lt__(self, other):
    		if type(self) != type(other):
    			return NotImplemented

    		try:
    			# implemented so that list.sort() sorts according to the spec.
    			selfTuple = (
    				self.platformID,
    				self.platEncID,
    				self.langID,
    				self.nameID,
    				self.toBytes(),
    			)
    			otherTuple = (
    				other.platformID,
    				other.platEncID,
    				other.langID,
    				other.nameID,
    				other.toBytes(),
    			)
    			return selfTuple < otherTuple
    		except (UnicodeEncodeError, AttributeError):
    			# This can only happen for
    			# 1) an object that is not a NameRecord, or
    			# 2) an unlikely incomplete NameRecord object which has not been
    			#    fully populated, or
    			# 3) when all IDs are identical but the strings can't be encoded
    			#    for their platform encoding.
    			# In all cases it is best to return NotImplemented.
    			return NotImplemented

    	def __repr__(self):
    		return "<NameRecord NameID=%d; PlatformID=%d; LanguageID=%d>" % (
    				self.nameID, self.platformID, self.langID)

    # Windows language ID → IETF BCP-47 language tag
    #
    # While Microsoft indicates a region/country for all its language
    # IDs, we follow Unicode practice by omitting “most likely subtags”
    # as per Unicode CLDR. For example, English is simply “en” and not
    # “en-Latn” because according to Unicode, the default script
    # for English is Latin.
    #
    # http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html
    # http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
    _WINDOWS_LANGUAGES = {
        0x0436: 'af',
        0x041C: 'sq',
        0x0484: 'gsw',
        0x045E: 'am',
        0x1401: 'ar-DZ',
        0x3C01: 'ar-BH',
        0x0C01: 'ar',
        0x0801: 'ar-IQ',
        0x2C01: 'ar-JO',
        0x3401: 'ar-KW',
        0x3001: 'ar-LB',
        0x1001: 'ar-LY',
        0x1801: 'ary',
        0x2001: 'ar-OM',
        0x4001: 'ar-QA',
        0x0401: 'ar-SA',
        0x2801: 'ar-SY',
        0x1C01: 'aeb',
        0x3801: 'ar-AE',
        0x2401: 'ar-YE',
        0x042B: 'hy',
        0x044D: 'as',
        0x082C: 'az-Cyrl',
        0x042C: 'az',
        0x046D: 'ba',
        0x042D: 'eu',
        0x0423: 'be',
        0x0845: 'bn',
        0x0445: 'bn-IN',
        0x201A: 'bs-Cyrl',
        0x141A: 'bs',
        0x047E: 'br',
        0x0402: 'bg',
        0x0403: 'ca',
        0x0C04: 'zh-HK',
        0x1404: 'zh-MO',
        0x0804: 'zh',
        0x1004: 'zh-SG',
        0x0404: 'zh-TW',
        0x0483: 'co',
        0x041A: 'hr',
        0x101A: 'hr-BA',
        0x0405: 'cs',
        0x0406: 'da',
        0x048C: 'prs',
        0x0465: 'dv',
        0x0813: 'nl-BE',
        0x0413: 'nl',
        0x0C09: 'en-AU',
        0x2809: 'en-BZ',
        0x1009: 'en-CA',
        0x2409: 'en-029',
        0x4009: 'en-IN',
        0x1809: 'en-IE',
        0x2009: 'en-JM',
        0x4409: 'en-MY',
        0x1409: 'en-NZ',
        0x3409: 'en-PH',
        0x4809: 'en-SG',
        0x1C09: 'en-ZA',
        0x2C09: 'en-TT',
        0x0809: 'en-GB',
        0x0409: 'en',
        0x3009: 'en-ZW',
        0x0425: 'et',
        0x0438: 'fo',
        0x0464: 'fil',
        0x040B: 'fi',
        0x080C: 'fr-BE',
        0x0C0C: 'fr-CA',
        0x040C: 'fr',
        0x140C: 'fr-LU',
        0x180C: 'fr-MC',
        0x100C: 'fr-CH',
        0x0462: 'fy',
        0x0456: 'gl',
        0x0437: 'ka',
        0x0C07: 'de-AT',
        0x0407: 'de',
        0x1407: 'de-LI',
        0x1007: 'de-LU',
        0x0807: 'de-CH',
        0x0408: 'el',
        0x046F: 'kl',
        0x0447: 'gu',
        0x0468: 'ha',
        0x040D: 'he',
        0x0439: 'hi',
        0x040E: 'hu',
        0x040F: 'is',
        0x0470: 'ig',
        0x0421: 'id',
        0x045D: 'iu',
        0x085D: 'iu-Latn',
        0x083C: 'ga',
        0x0434: 'xh',
        0x0435: 'zu',
        0x0410: 'it',
        0x0810: 'it-CH',
        0x0411: 'ja',
        0x044B: 'kn',
        0x043F: 'kk',
        0x0453: 'km',
        0x0486: 'quc',
        0x0487: 'rw',
        0x0441: 'sw',
        0x0457: 'kok',
        0x0412: 'ko',
        0x0440: 'ky',
        0x0454: 'lo',
        0x0426: 'lv',
        0x0427: 'lt',
        0x082E: 'dsb',
        0x046E: 'lb',
        0x042F: 'mk',
        0x083E: 'ms-BN',
        0x043E: 'ms',
        0x044C: 'ml',
        0x043A: 'mt',
        0x0481: 'mi',
        0x047A: 'arn',
        0x044E: 'mr',
        0x047C: 'moh',
        0x0450: 'mn',
        0x0850: 'mn-CN',
        0x0461: 'ne',
        0x0414: 'nb',
        0x0814: 'nn',
        0x0482: 'oc',
        0x0448: 'or',
        0x0463: 'ps',
        0x0415: 'pl',
        0x0416: 'pt',
        0x0816: 'pt-PT',
        0x0446: 'pa',
        0x046B: 'qu-BO',
        0x086B: 'qu-EC',
        0x0C6B: 'qu',
        0x0418: 'ro',
        0x0417: 'rm',
        0x0419: 'ru',
        0x243B: 'smn',
        0x103B: 'smj-NO',
        0x143B: 'smj',
        0x0C3B: 'se-FI',
        0x043B: 'se',
        0x083B: 'se-SE',
        0x203B: 'sms',
        0x183B: 'sma-NO',
        0x1C3B: 'sms',
        0x044F: 'sa',
        0x1C1A: 'sr-Cyrl-BA',
        0x0C1A: 'sr',
        0x181A: 'sr-Latn-BA',
        0x081A: 'sr-Latn',
        0x046C: 'nso',
        0x0432: 'tn',
        0x045B: 'si',
        0x041B: 'sk',
        0x0424: 'sl',
        0x2C0A: 'es-AR',
        0x400A: 'es-BO',
        0x340A: 'es-CL',
        0x240A: 'es-CO',
        0x140A: 'es-CR',
        0x1C0A: 'es-DO',
        0x300A: 'es-EC',
        0x440A: 'es-SV',
        0x100A: 'es-GT',
        0x480A: 'es-HN',
        0x080A: 'es-MX',
        0x4C0A: 'es-NI',
        0x180A: 'es-PA',
        0x3C0A: 'es-PY',
        0x280A: 'es-PE',
        0x500A: 'es-PR',

        # Microsoft has defined two different language codes for
        # “Spanish with modern sorting” and “Spanish with traditional
        # sorting”. This makes sense for collation APIs, and it would be
        # possible to express this in BCP 47 language tags via Unicode
        # extensions (eg., “es-u-co-trad” is “Spanish with traditional
        # sorting”). However, for storing names in fonts, this distinction
        # does not make sense, so we use “es” in both cases.
        0x0C0A: 'es',
        0x040A: 'es',

        0x540A: 'es-US',
        0x380A: 'es-UY',
        0x200A: 'es-VE',
        0x081D: 'sv-FI',
        0x041D: 'sv',
        0x045A: 'syr',
        0x0428: 'tg',
        0x085F: 'tzm',
        0x0449: 'ta',
        0x0444: 'tt',
        0x044A: 'te',
        0x041E: 'th',
        0x0451: 'bo',
        0x041F: 'tr',
        0x0442: 'tk',
        0x0480: 'ug',
        0x0422: 'uk',
        0x042E: 'hsb',
        0x0420: 'ur',
        0x0843: 'uz-Cyrl',
        0x0443: 'uz',
        0x042A: 'vi',
        0x0452: 'cy',
        0x0488: 'wo',
        0x0485: 'sah',
        0x0478: 'ii',
        0x046A: 'yo',
    }

    _MAC_LANGUAGES = {
        0: 'en',
        1: 'fr',
        2: 'de',
        3: 'it',
        4: 'nl',
        5: 'sv',
        6: 'es',
        7: 'da',
        8: 'pt',
        9: 'no',
        10: 'he',
        11: 'ja',
        12: 'ar',
        13: 'fi',
        14: 'el',
        15: 'is',
        16: 'mt',
        17: 'tr',
        18: 'hr',
        19: 'zh-Hant',
        20: 'ur',
        21: 'hi',
        22: 'th',
        23: 'ko',
        24: 'lt',
        25: 'pl',
        26: 'hu',
        27: 'es',
        28: 'lv',
        29: 'se',
        30: 'fo',
        31: 'fa',
        32: 'ru',
        33: 'zh',
        34: 'nl-BE',
        35: 'ga',
        36: 'sq',
        37: 'ro',
        38: 'cz',
        39: 'sk',
        40: 'sl',
        41: 'yi',
        42: 'sr',
        43: 'mk',
        44: 'bg',
        45: 'uk',
        46: 'be',
        47: 'uz',
        48: 'kk',
        49: 'az-Cyrl',
        50: 'az-Arab',
        51: 'hy',
        52: 'ka',
        53: 'mo',
        54: 'ky',
        55: 'tg',
        56: 'tk',
        57: 'mn-CN',
        58: 'mn',
        59: 'ps',
        60: 'ks',
        61: 'ku',
        62: 'sd',
        63: 'bo',
        64: 'ne',
        65: 'sa',
        66: 'mr',
        67: 'bn',
        68: 'as',
        69: 'gu',
        70: 'pa',
        71: 'or',
        72: 'ml',
        73: 'kn',
        74: 'ta',
        75: 'te',
        76: 'si',
        77: 'my',
        78: 'km',
        79: 'lo',
        80: 'vi',
        81: 'id',
        82: 'tl',
        83: 'ms',
        84: 'ms-Arab',
        85: 'am',
        86: 'ti',
        87: 'om',
        88: 'so',
        89: 'sw',
        90: 'rw',
        91: 'rn',
        92: 'ny',
        93: 'mg',
        94: 'eo',
        128: 'cy',
        129: 'eu',
        130: 'ca',
        131: 'la',
        132: 'qu',
        133: 'gn',
        134: 'ay',
        135: 'tt',
        136: 'ug',
        137: 'dz',
        138: 'jv',
        139: 'su',
        140: 'gl',
        141: 'af',
        142: 'br',
        143: 'iu',
        144: 'gd',
        145: 'gv',
        146: 'ga',
        147: 'to',
        148: 'el-polyton',
        149: 'kl',
        150: 'az',
        151: 'nn',
    }

    _WINDOWS_LANGUAGE_CODES = {lang.lower(): code for code, lang in _WINDOWS_LANGUAGES.items()}
    _MAC_LANGUAGE_CODES = {lang.lower(): code for code, lang in _MAC_LANGUAGES.items()}

    # MacOS language ID → MacOS script ID
    #
    # Note that the script ID is not sufficient to determine what encoding
    # to use in TrueType files. For some languages, MacOS used a modification
    # of a mainstream script. For example, an Icelandic name would be stored
    # with smRoman in the TrueType naming table, but the actual encoding
    # is a special Icelandic version of the normal Macintosh Roman encoding.
    # As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal
    # Syllables but MacOS had run out of available script codes, so this was
    # done as a (pretty radical) “modification” of Ethiopic.
    #
    # http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt
    _MAC_LANGUAGE_TO_SCRIPT = {
        0: 0,  # langEnglish → smRoman
        1: 0,  # langFrench → smRoman
        2: 0,  # langGerman → smRoman
        3: 0,  # langItalian → smRoman
        4: 0,  # langDutch → smRoman
        5: 0,  # langSwedish → smRoman
        6: 0,  # langSpanish → smRoman
        7: 0,  # langDanish → smRoman
        8: 0,  # langPortuguese → smRoman
        9: 0,  # langNorwegian → smRoman
        10: 5,  # langHebrew → smHebrew
        11: 1,  # langJapanese → smJapanese
        12: 4,  # langArabic → smArabic
        13: 0,  # langFinnish → smRoman
        14: 6,  # langGreek → smGreek
        15: 0,  # langIcelandic → smRoman (modified)
        16: 0,  # langMaltese → smRoman
        17: 0,  # langTurkish → smRoman (modified)
        18: 0,  # langCroatian → smRoman (modified)
        19: 2,  # langTradChinese → smTradChinese
        20: 4,  # langUrdu → smArabic
        21: 9,  # langHindi → smDevanagari
        22: 21,  # langThai → smThai
        23: 3,  # langKorean → smKorean
        24: 29,  # langLithuanian → smCentralEuroRoman
        25: 29,  # langPolish → smCentralEuroRoman
        26: 29,  # langHungarian → smCentralEuroRoman
        27: 29,  # langEstonian → smCentralEuroRoman
        28: 29,  # langLatvian → smCentralEuroRoman
        29: 0,  # langSami → smRoman
        30: 0,  # langFaroese → smRoman (modified)
        31: 4,  # langFarsi → smArabic (modified)
        32: 7,  # langRussian → smCyrillic
        33: 25,  # langSimpChinese → smSimpChinese
        34: 0,  # langFlemish → smRoman
        35: 0,  # langIrishGaelic → smRoman (modified)
        36: 0,  # langAlbanian → smRoman
        37: 0,  # langRomanian → smRoman (modified)
        38: 29,  # langCzech → smCentralEuroRoman
        39: 29,  # langSlovak → smCentralEuroRoman
        40: 0,  # langSlovenian → smRoman (modified)
        41: 5,  # langYiddish → smHebrew
        42: 7,  # langSerbian → smCyrillic
        43: 7,  # langMacedonian → smCyrillic
        44: 7,  # langBulgarian → smCyrillic
        45: 7,  # langUkrainian → smCyrillic (modified)
        46: 7,  # langByelorussian → smCyrillic
        47: 7,  # langUzbek → smCyrillic
        48: 7,  # langKazakh → smCyrillic
        49: 7,  # langAzerbaijani → smCyrillic
        50: 4,  # langAzerbaijanAr → smArabic
        51: 24,  # langArmenian → smArmenian
        52: 23,  # langGeorgian → smGeorgian
        53: 7,  # langMoldavian → smCyrillic
        54: 7,  # langKirghiz → smCyrillic
        55: 7,  # langTajiki → smCyrillic
        56: 7,  # langTurkmen → smCyrillic
        57: 27,  # langMongolian → smMongolian
        58: 7,  # langMongolianCyr → smCyrillic
        59: 4,  # langPashto → smArabic
        60: 4,  # langKurdish → smArabic
        61: 4,  # langKashmiri → smArabic
        62: 4,  # langSindhi → smArabic
        63: 26,  # langTibetan → smTibetan
        64: 9,  # langNepali → smDevanagari
        65: 9,  # langSanskrit → smDevanagari
        66: 9,  # langMarathi → smDevanagari
        67: 13,  # langBengali → smBengali
        68: 13,  # langAssamese → smBengali
        69: 11,  # langGujarati → smGujarati
        70: 10,  # langPunjabi → smGurmukhi
        71: 12,  # langOriya → smOriya
        72: 17,  # langMalayalam → smMalayalam
        73: 16,  # langKannada → smKannada
        74: 14,  # langTamil → smTamil
        75: 15,  # langTelugu → smTelugu
        76: 18,  # langSinhalese → smSinhalese
        77: 19,  # langBurmese → smBurmese
        78: 20,  # langKhmer → smKhmer
        79: 22,  # langLao → smLao
        80: 30,  # langVietnamese → smVietnamese
        81: 0,  # langIndonesian → smRoman
        82: 0,  # langTagalog → smRoman
        83: 0,  # langMalayRoman → smRoman
        84: 4,  # langMalayArabic → smArabic
        85: 28,  # langAmharic → smEthiopic
        86: 28,  # langTigrinya → smEthiopic
        87: 28,  # langOromo → smEthiopic
        88: 0,  # langSomali → smRoman
        89: 0,  # langSwahili → smRoman
        90: 0,  # langKinyarwanda → smRoman
        91: 0,  # langRundi → smRoman
        92: 0,  # langNyanja → smRoman
        93: 0,  # langMalagasy → smRoman
        94: 0,  # langEsperanto → smRoman
        128: 0,  # langWelsh → smRoman (modified)
        129: 0,  # langBasque → smRoman
        130: 0,  # langCatalan → smRoman
        131: 0,  # langLatin → smRoman
        132: 0,  # langQuechua → smRoman
        133: 0,  # langGuarani → smRoman
        134: 0,  # langAymara → smRoman
        135: 7,  # langTatar → smCyrillic
        136: 4,  # langUighur → smArabic
        137: 26,  # langDzongkha → smTibetan
        138: 0,  # langJavaneseRom → smRoman
        139: 0,  # langSundaneseRom → smRoman
        140: 0,  # langGalician → smRoman
        141: 0,  # langAfrikaans → smRoman
        142: 0,  # langBreton → smRoman (modified)
        143: 28,  # langInuktitut → smEthiopic (modified)
        144: 0,  # langScottishGaelic → smRoman (modified)
        145: 0,  # langManxGaelic → smRoman (modified)
        146: 0,  # langIrishGaelicScript → smRoman (modified)
        147: 0,  # langTongan → smRoman
        148: 6,  # langGreekAncient → smRoman
        149: 0,  # langGreenlandic → smRoman
        150: 0,  # langAzerbaijanRoman → smRoman
        151: 0,   # langNynorsk → smRoman
    }