fonttools/Lib/robofab/tools/nameTable.py

710 lines
16 KiB
Python
Raw Normal View History

from warnings import warn
warn("FontLab contains a bug that renders nameTable.py inoperable", Warning)
"""
XXX: FontLab 4.6 contains a bug that renders this module inoperable.
A simple wrapper around the not so simple OpenType
name table API in FontLab.
For more information about the name table see:
http://www.microsoft.com/typography/otspec/name.htm
The PID, EID, LID and NID arguments in the various
methods can be integer values or string values (as
long as the string value matches the key in the lookup
dicts shown below). All values must be strings and all
platform line ending conversion is handled automatically
EXCEPT in the setSpecificRecord method. If you need to do
line ending conversion, the convertLineEndings method
is publicly available.
"""
from robofab import RoboFabError
##
## internal pid constants
##
UNI = 'unicode'
UNI_INT = 0
MAC = 'macintosh'
MAC_INT = 1
MS = 'microsoft'
MS_INT = 3
##
## lookup dicts
##
def _flipDict(aDict):
bDict = {}
for k, v in aDict.items():
bDict[v] = k
return bDict
pidName2Int = {
UNI : UNI_INT,
MAC : MAC_INT,
MS : MS_INT,
}
nidName2Int = {
'copyright' : 0,
'familyName' : 1,
'subfamilyName' : 2,
'uniqueID' : 3,
'fullName' : 4,
'versionString' : 5,
'postscriptName' : 6,
'trademark' : 7,
'manufacturer' : 8,
'designer' : 9,
'description' : 10,
'vendorURL' : 11,
'designerURL' : 12,
'license' : 13,
'licenseURL' : 14,
# ID 15 is reserved
'preferredFamily' : 16,
'preferredSubfamily' : 17,
'compatibleFull' : 18,
'sampleText' : 19,
'postscriptCID' : 20
}
nidInt2Name = _flipDict(nidName2Int)
uniEIDName2Int = {
"unicode_1.0" : 0,
"unicode_1.1" : 1,
"iso_10646:1993" : 2,
"unicode_2.0_bmp" : 3,
"unicode_2.0_full" : 4,
}
uniEIDInt2Name = _flipDict(uniEIDName2Int)
uniLIDName2Int = {}
uniLIDInt2Name = _flipDict(uniLIDName2Int)
msEIDName2Int = {
'symbol' : 0,
'unicode_bmp_only' : 1,
'shift_jis' : 2,
'prc' : 3,
'big5' : 4,
'wansung' : 5,
'johab' : 6,
# 7 is reserved
# 8 is reserved
# 9 is reserved
'unicode_full_repertoire' : 7,
}
msEIDInt2Name = _flipDict(msEIDName2Int)
msLIDName2Int = {
# need to find a parsable file
}
msLIDInt2Name = _flipDict(msLIDName2Int)
macEIDName2Int = {
"roman" : 0,
"japanese" : 1,
"chinese" : 2,
"korean" : 3,
"arabic" : 4,
"hebrew" : 5,
"greek" : 6,
"russian" : 7,
"rsymbol" : 8,
"devanagari" : 9,
"gurmukhi" : 10,
"gujarati" : 11,
"oriya" : 12,
"bengali" : 13,
"tamil" : 14,
"telugu" : 15,
"kannada" : 16,
"malayalam" : 17,
"sinhalese" : 18,
"burmese" : 19,
"khmer" : 20,
"thai" : 21,
"laotian" : 22,
"georgian" : 23,
"armenian" : 24,
"chinese" : 25,
"tibetan" : 26,
"mongolian" : 27,
"geez" : 28,
"slavic" : 29,
"vietnamese" : 30,
"sindhi" : 31,
"uninterpreted" : 32,
}
macEIDInt2Name = _flipDict(macEIDName2Int)
macLIDName2Int = {
"english" : 0,
"french" : 1,
"german" : 2,
"italian" : 3,
"dutch" : 4,
"swedish" : 5,
"spanish" : 6,
"danish" : 7,
"portuguese" : 8,
"norwegian" : 9,
"hebrew" : 10,
"japanese" : 11,
"arabic" : 12,
"finnish" : 13,
"inuktitut" : 14,
"icelandic" : 15,
"maltese" : 16,
"turkish" : 17,
"croatian" : 18,
"chinese" : 19,
"urdu" : 20,
"hindi" : 21,
"thai" : 22,
"korean" : 23,
"lithuanian" : 24,
"polish" : 25,
"hungarian" : 26,
"estonian" : 27,
"latvian" : 28,
"sami" : 29,
"faroese" : 30,
"farsi_persian" : 31,
"russian" : 32,
"chinese" : 33,
"flemish" : 34,
"irish gaelic" : 35,
"albanian" : 36,
"romanian" : 37,
"czech" : 38,
"slovak" : 39,
"slovenian" : 40,
"yiddish" : 41,
"serbian" : 42,
"macedonian" : 43,
"bulgarian" : 44,
"ukrainian" : 45,
"byelorussian" : 46,
"uzbek" : 47,
"kazakh" : 48,
"azerbaijani_cyrillic" : 49,
"azerbaijani_arabic" : 50,
"armenian" : 51,
"georgian" : 52,
"moldavian" : 53,
"kirghiz" : 54,
"tajiki" : 55,
"turkmen" : 56,
"mongolian_mongolian" : 57,
"mongolian_cyrillic" : 58,
"pashto" : 59,
"kurdish" : 60,
"kashmiri" : 61,
"sindhi" : 62,
"tibetan" : 63,
"nepali" : 64,
"sanskrit" : 65,
"marathi" : 66,
"bengali" : 67,
"assamese" : 68,
"gujarati" : 69,
"punjabi" : 70,
"oriya" : 71,
"malayalam" : 72,
"kannada" : 73,
"tamil" : 74,
"telugu" : 75,
"sinhalese" : 76,
"burmese" : 77,
"khmer" : 78,
"lao" : 79,
"vietnamese" : 80,
"indonesian" : 81,
"tagalong" : 82,
"malay_roman" : 83,
"malay_arabic" : 84,
"amharic" : 85,
"tigrinya" : 86,
"galla" : 87,
"somali" : 88,
"swahili" : 89,
"kinyarwanda_ruanda" : 90,
"rundi" : 91,
"nyanja_chewa" : 92,
"malagasy" : 93,
"esperanto" : 94,
"welsh" : 128,
"basque" : 129,
"catalan" : 130,
"latin" : 131,
"quenchua" : 132,
"guarani" : 133,
"aymara" : 134,
"tatar" : 135,
"uighur" : 136,
"dzongkha" : 137,
"javanese_roman" : 138,
"sundanese_roman" : 139,
"galician" : 140,
"afrikaans" : 141,
"breton" : 142,
"scottish_gaelic" : 144,
"manx_gaelic" : 145,
"irish_gaelic" : 146,
"tongan" : 147,
"greek_polytonic" : 148,
"greenlandic" : 149,
"azerbaijani_roman" : 150,
}
macLIDInt2Name = _flipDict(macLIDName2Int)
##
## value converters
##
def _convertNID2Int(nid):
if isinstance(nid, int):
return nid
return nidName2Int[nid]
def _convertPID2Int(pid):
if isinstance(pid, int):
return pid
return pidName2Int[pid]
def _convertEID2Int(pid, eid):
if isinstance(eid, int):
return eid
pid = _convertPID2Int(pid)
if pid == UNI_INT:
return uniEIDName2Int[eid]
elif pid == MAC_INT:
return macEIDName2Int[eid]
elif pid == MS_INT:
return msEIDName2Int[eid]
def _convertLID2Int(pid, lid):
if isinstance(lid, int):
return lid
pid = _convertPID2Int(pid)
if pid == UNI_INT:
return uniLIDName2Int[lid]
elif pid == MAC_INT:
return macLIDName2Int[lid]
elif pid == MS_INT:
return msLIDName2Int[lid]
def _compareValues(v1, v2):
if isinstance(v1, str):
v1 = v1.replace('\r\n', '\n')
if isinstance(v2, str):
v2 = v2.replace('\r\n', '\n')
return v1 == v2
def convertLineEndings(text, convertToMS=False):
"""convert the line endings in a given text string"""
if isinstance(text, str):
if convertToMS:
text = text.replace('\r\n', '\n')
text = text.replace('\n', '\r\n')
else:
text = text.replace('\r\n', '\n')
return text
##
## main object
##
class NameTable(object):
"""
An object that allows direct manipulation of the name table of a given font.
For example:
from robofab.world import CurrentFont
from robofab.tools.nameTable import NameTable
f = CurrentFont()
nt = NameTable(f)
# bluntly set all copyright records to a string
nt.copyright = "Copyright 2004 RoboFab"
# get a record
print nt.copyright
# set a specific record to a string
nt.setSpecificRecord(pid=1, eid=0, lid=0, nid=0, value="You Mac-Roman-English folks should know that this is Copyright 2004 RoboFab.")
# get a record again to show what happens
# when the records for a NID are not the same
print nt.copyright
# look at the code to see what else is possible
f.update()
"""
def __init__(self, font):
self._object = font
self._pid_eid_lid = {}
self._records = {}
self._indexRef = {}
self._populate()
def _populate(self):
# keys are tuples (pid, eid, lid, nid)
self._records = {}
# keys are tuples (pid, eid, lid, nid), values are indices
self._indexRef = {}
count = 0
for record in self._object.naked().fontnames:
pid = record.pid
eid = record.eid
lid = record.lid
nid = record.nid
value = record.name
self._records[(pid, eid, lid, nid)] = value
self._indexRef[(pid, eid, lid, nid)] = count
count = count + 1
def addRecord(self, pid, eid, lid, nidDict=None):
"""add a record. the optional nidDict is
a dictionary of NIDs and values. If no
nidDict is given, the method will make
an empty entry for ALL public NIDs."""
if nidDict is None:
nidDict = dict.fromkeys(nidInt2Name.keys())
for nid in nidDict.keys():
nidDict[nid] = ''
pid = _convertPID2Int(pid)
eid = _convertEID2Int(pid, eid)
lid = _convertLID2Int(pid, lid)
self.removeLID(pid, eid, lid)
for nid, value in nidDict.items():
nid = _convertNID2Int(nid)
self._setRecord(pid, eid, lid, nid, value)
def removePID(self, pid):
"""remove a PID entry"""
pid = _convertPID2Int(pid)
for _pid, _eid, _lid, _nid in self._records.keys():
if pid == _pid:
self._removeRecord(_pid, _eid, _lid, _nid)
def removeEID(self, pid, eid):
"""remove an EID from a PID entry"""
pid = _convertPID2Int(pid)
eid = _convertEID2Int(pid, eid)
for _pid, _eid, _lid, _nid in self._records.keys():
if pid == _pid and eid == _eid:
self._removeRecord(_pid, _eid, _lid, _nid)
def removeLID(self, pid, eid, lid):
"""remove a LID from a PID entry"""
pid = _convertPID2Int(pid)
eid = _convertEID2Int(pid, eid)
lid = _convertLID2Int(pid, lid)
for _pid, _eid, _lid, _nid in self._records.keys():
if pid == _pid and eid == _eid and lid == _lid:
self._removeRecord(_pid, _eid, _lid, _nid)
def removeNID(self, nid):
"""remove a NID from ALL PID, EID and LID entries"""
nid = _convertNID2Int(nid)
for _pid, _eid, _lid, _nid in self._records.keys():
if nid == _nid:
self._removeRecord(_pid, _eid, _lid, _nid)
def setSpecificRecord(self, pid, eid, lid, nid, value):
"""set a specific record based on the PID, EID, LID and NID
this method does not do platform lineending conversion"""
pid = _convertPID2Int(pid)
eid = _convertEID2Int(pid, eid)
lid = _convertLID2Int(pid, lid)
nid = _convertNID2Int(nid)
# id 18 is mac only, so it should
# not be set for other PIDs
if pid != MAC_INT and nid == 18:
raise RoboFabError, "NID 18 is Macintosh only"
self._setRecord(pid, eid, lid, nid, value)
#
# interface to FL name records
#
def _removeRecord(self, pid, eid, lid, nid):
# remove the record from the font
# note: this won't raise an error if the record doesn't exist
if self._indexRef.has_key((pid, eid, lid, nid)):
index = self._indexRef[(pid, eid, lid, nid)]
del self._object.naked().fontnames[index]
self._populate()
def _setRecord(self, pid, eid, lid, nid, value):
# set a record in the font
if pid != MAC_INT and nid == 18:
# id 18 is mac only, so it should
# not be set for other PIDs
return
if pid == UNI_INT or pid == MAC_INT:
value = convertLineEndings(value, convertToMS=False)
if pid == MS_INT:
value = convertLineEndings(value, convertToMS=True)
self._removeRecord(pid, eid, lid, nid)
from FL import NameRecord
nr = NameRecord(nid, pid, eid, lid, value)
self._object.naked().fontnames.append(nr)
self._populate()
def _setAllRecords(self, nid, value):
# set nid for all pid, eid and lid records
done = []
for _pid, _eid, _lid, _nid in self._records.keys():
if (_pid, _eid, _lid) not in done:
self._setRecord(_pid, _eid, _lid, nid, value)
done.append((_pid, _eid, _lid))
def _getAllRecords(self, nid):
# this retrieves all nid records and compares
# them. if the values are all the same, it returns
# the value. otherwise it returns a list of all values
# as tuples (pid, eid, lid, value).
found = []
for (_pid, _eid, _lid, _nid), value in self._records.items():
if nid == _nid:
found.append((_pid, _eid, _lid, value))
isSame = True
compare = {}
for pid, eid, lid, value in found:
if compare == {}:
compare = value
continue
vC = _compareValues(compare, value)
if not vC:
isSame = False
if isSame:
found = found[0][-1]
found = convertLineEndings(found, convertToMS=False)
return found
#
# attrs
#
def _get_copyright(self):
nid = 0
return self._getAllRecords(nid)
def _set_copyright(self, value):
nid = 0
self._setAllRecords(nid, value)
copyright = property(_get_copyright, _set_copyright, doc="NID 0")
def _get_familyName(self):
nid = 1
return self._getAllRecords(nid)
def _set_familyName(self, value):
nid = 1
self._setAllRecords(nid, value)
familyName = property(_get_familyName, _set_familyName, doc="NID 1")
def _get_subfamilyName(self):
nid = 2
return self._getAllRecords(nid)
def _set_subfamilyName(self, value):
nid = 2
self._setAllRecords(nid, value)
subfamilyName = property(_get_subfamilyName, _set_subfamilyName, doc="NID 2")
def _get_uniqueID(self):
nid = 3
return self._getAllRecords(nid)
def _set_uniqueID(self, value):
nid = 3
self._setAllRecords(nid, value)
uniqueID = property(_get_uniqueID, _set_uniqueID, doc="NID 3")
def _get_fullName(self):
nid = 4
return self._getAllRecords(nid)
def _set_fullName(self, value):
nid = 4
self._setAllRecords(nid, value)
fullName = property(_get_fullName, _set_fullName, doc="NID 4")
def _get_versionString(self):
nid = 5
return self._getAllRecords(nid)
def _set_versionString(self, value):
nid = 5
self._setAllRecords(nid, value)
versionString = property(_get_versionString, _set_versionString, doc="NID 5")
def _get_postscriptName(self):
nid = 6
return self._getAllRecords(nid)
def _set_postscriptName(self, value):
nid = 6
self._setAllRecords(nid, value)
postscriptName = property(_get_postscriptName, _set_postscriptName, doc="NID 6")
def _get_trademark(self):
nid = 7
return self._getAllRecords(nid)
def _set_trademark(self, value):
nid = 7
self._setAllRecords(nid, value)
trademark = property(_get_trademark, _set_trademark, doc="NID 7")
def _get_manufacturer(self):
nid = 8
return self._getAllRecords(nid)
def _set_manufacturer(self, value):
nid = 8
self._setAllRecords(nid, value)
manufacturer = property(_get_manufacturer, _set_manufacturer, doc="NID 8")
def _get_designer(self):
nid = 9
return self._getAllRecords(nid)
def _set_designer(self, value):
nid = 9
self._setAllRecords(nid, value)
designer = property(_get_designer, _set_designer, doc="NID 9")
def _get_description(self):
nid = 10
return self._getAllRecords(nid)
def _set_description(self, value):
nid = 10
self._setAllRecords(nid, value)
description = property(_get_description, _set_description, doc="NID 10")
def _get_vendorURL(self):
nid = 11
return self._getAllRecords(nid)
def _set_vendorURL(self, value):
nid = 11
self._setAllRecords(nid, value)
vendorURL = property(_get_vendorURL, _set_vendorURL, doc="NID 11")
def _get_designerURL(self):
nid = 12
return self._getAllRecords(nid)
def _set_designerURL(self, value):
nid = 12
self._setAllRecords(nid, value)
designerURL = property(_get_designerURL, _set_designerURL, doc="NID 12")
def _get_license(self):
nid = 13
return self._getAllRecords(nid)
def _set_license(self, value):
nid = 13
self._setAllRecords(nid, value)
license = property(_get_license, _set_license, doc="NID 13")
def _get_licenseURL(self):
nid = 14
return self._getAllRecords(nid)
def _set_licenseURL(self, value):
nid = 14
self._setAllRecords(nid, value)
licenseURL = property(_get_licenseURL, _set_licenseURL, doc="NID 14")
def _get_preferredFamily(self):
nid = 16
return self._getAllRecords(nid)
def _set_preferredFamily(self, value):
nid = 16
self._setAllRecords(nid, value)
preferredFamily = property(_get_preferredFamily, _set_preferredFamily, doc="NID 16")
def _get_preferredSubfamily(self):
nid = 17
return self._getAllRecords(nid)
def _set_preferredSubfamily(self, value):
nid = 17
self._setAllRecords(nid, value)
preferredSubfamily = property(_get_preferredSubfamily, _set_preferredSubfamily, doc="NID 17")
def _get_compatibleFull(self):
nid = 18
return self._getAllRecords(nid)
def _set_compatibleFull(self, value):
nid = 18
self._setAllRecords(nid, value)
compatibleFull = property(_get_compatibleFull, _set_compatibleFull, doc="NID 18")
def _get_sampleText(self):
nid = 19
return self._getAllRecords(nid)
def _set_sampleText(self, value):
nid = 19
self._setAllRecords(nid, value)
sampleText = property(_get_sampleText, _set_sampleText, doc="NID 19")
def _get_postscriptCID(self):
nid = 20
return self._getAllRecords(nid)
def _set_postscriptCID(self, value):
nid = 20
self._setAllRecords(nid, value)
postscriptCID = property(_get_postscriptCID, _set_postscriptCID, doc="NID 20")
if __name__ == "__main__":
from robofab.world import CurrentFont
f = CurrentFont()
nt = NameTable(f)
print nt.copyright
f.update()