diff options
author | Rasmus Andersson <rasmus@notion.se> | 2018-09-03 22:55:49 +0300 |
---|---|---|
committer | Rasmus Andersson <rasmus@notion.se> | 2018-09-03 22:55:49 +0300 |
commit | c833e252c925e8dd68108660710ca835d95daa6f (patch) | |
tree | 6b2e28264ed45efd7f054e453b622098d0d875b8 /misc/pylib/robofab/ufoLib.py | |
parent | 8c1a4c181ef12000179dfec541f1af87e9b03122 (diff) | |
download | inter-c833e252c925e8dd68108660710ca835d95daa6f.tar.xz |
Major overhaul, moving from UFO2 to Glyphs and UFO3, plus a brand new and much simpler fontbuild
Diffstat (limited to 'misc/pylib/robofab/ufoLib.py')
-rwxr-xr-x | misc/pylib/robofab/ufoLib.py | 1084 |
1 files changed, 0 insertions, 1084 deletions
diff --git a/misc/pylib/robofab/ufoLib.py b/misc/pylib/robofab/ufoLib.py deleted file mode 100755 index b329fd929..000000000 --- a/misc/pylib/robofab/ufoLib.py +++ /dev/null @@ -1,1084 +0,0 @@ -"""" -A library for importing .ufo files and their descendants. -Refer to http://unifiedfontobject.com for the UFO specification. - -The UFOReader and UFOWriter classes support versions 1 and 2 -of the specification. Up and down conversion functions are also -supplied in this library. These conversion functions are only -necessary if conversion without loading the UFO data into -a set of objects is desired. These functions are: - convertUFOFormatVersion1ToFormatVersion2 - convertUFOFormatVersion2ToFormatVersion1 - -Two sets that list the font info attribute names for the two -fontinfo.plist formats are available for external use. These are: - fontInfoAttributesVersion1 - fontInfoAttributesVersion2 - -A set listing the fontinfo.plist attributes that were deprecated -in version 2 is available for external use: - deprecatedFontInfoAttributesVersion2 - -A function, validateFontInfoVersion2ValueForAttribute, that does -some basic validation on values for a fontinfo.plist value is -available for external use. - -Two value conversion functions are availble for converting -fontinfo.plist values between the possible format versions. - convertFontInfoValueForAttributeFromVersion1ToVersion2 - convertFontInfoValueForAttributeFromVersion2ToVersion1 -""" - - -import os -import shutil -from cStringIO import StringIO -import calendar -from robofab.plistlib import readPlist, writePlist -from robofab.glifLib import GlyphSet, READ_MODE, WRITE_MODE - -try: - set -except NameError: - from sets import Set as set - -__all__ = [ - "makeUFOPath" - "UFOLibError", - "UFOReader", - "UFOWriter", - "convertUFOFormatVersion1ToFormatVersion2", - "convertUFOFormatVersion2ToFormatVersion1", - "fontInfoAttributesVersion1", - "fontInfoAttributesVersion2", - "deprecatedFontInfoAttributesVersion2", - "validateFontInfoVersion2ValueForAttribute", - "convertFontInfoValueForAttributeFromVersion1ToVersion2", - "convertFontInfoValueForAttributeFromVersion2ToVersion1" -] - - -class UFOLibError(Exception): pass - - -# ---------- -# File Names -# ---------- - -GLYPHS_DIRNAME = "glyphs" -METAINFO_FILENAME = "metainfo.plist" -FONTINFO_FILENAME = "fontinfo.plist" -LIB_FILENAME = "lib.plist" -GROUPS_FILENAME = "groups.plist" -KERNING_FILENAME = "kerning.plist" -FEATURES_FILENAME = "features.fea" - -supportedUFOFormatVersions = [1, 2] - - -# --------------------------- -# Format Conversion Functions -# --------------------------- - - -def convertUFOFormatVersion1ToFormatVersion2(inPath, outPath=None): - """ - Function for converting a version format 1 UFO - to version format 2. inPath should be a path - to a UFO. outPath is the path where the new UFO - should be written. If outPath is not given, the - inPath will be used and, therefore, the UFO will - be converted in place. Otherwise, if outPath is - specified, nothing must exist at that path. - """ - if outPath is None: - outPath = inPath - if inPath != outPath and os.path.exists(outPath): - raise UFOLibError("A file already exists at %s." % outPath) - # use a reader for loading most of the data - reader = UFOReader(inPath) - if reader.formatVersion == 2: - raise UFOLibError("The UFO at %s is already format version 2." % inPath) - groups = reader.readGroups() - kerning = reader.readKerning() - libData = reader.readLib() - # read the info data manually and convert - infoPath = os.path.join(inPath, FONTINFO_FILENAME) - if not os.path.exists(infoPath): - infoData = {} - else: - infoData = readPlist(infoPath) - infoData = _convertFontInfoDataVersion1ToVersion2(infoData) - # if the paths are the same, only need to change the - # fontinfo and meta info files. - infoPath = os.path.join(outPath, FONTINFO_FILENAME) - if inPath == outPath: - metaInfoPath = os.path.join(inPath, METAINFO_FILENAME) - metaInfo = dict( - creator="org.robofab.ufoLib", - formatVersion=2 - ) - writePlistAtomically(metaInfo, metaInfoPath) - writePlistAtomically(infoData, infoPath) - # otherwise write everything. - else: - writer = UFOWriter(outPath) - writer.writeGroups(groups) - writer.writeKerning(kerning) - writer.writeLib(libData) - # write the info manually - writePlistAtomically(infoData, infoPath) - # copy the glyph tree - inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME) - outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME) - if os.path.exists(inGlyphs): - shutil.copytree(inGlyphs, outGlyphs) - -def convertUFOFormatVersion2ToFormatVersion1(inPath, outPath=None): - """ - Function for converting a version format 2 UFO - to version format 1. inPath should be a path - to a UFO. outPath is the path where the new UFO - should be written. If outPath is not given, the - inPath will be used and, therefore, the UFO will - be converted in place. Otherwise, if outPath is - specified, nothing must exist at that path. - """ - if outPath is None: - outPath = inPath - if inPath != outPath and os.path.exists(outPath): - raise UFOLibError("A file already exists at %s." % outPath) - # use a reader for loading most of the data - reader = UFOReader(inPath) - if reader.formatVersion == 1: - raise UFOLibError("The UFO at %s is already format version 1." % inPath) - groups = reader.readGroups() - kerning = reader.readKerning() - libData = reader.readLib() - # read the info data manually and convert - infoPath = os.path.join(inPath, FONTINFO_FILENAME) - if not os.path.exists(infoPath): - infoData = {} - else: - infoData = readPlist(infoPath) - infoData = _convertFontInfoDataVersion2ToVersion1(infoData) - # if the paths are the same, only need to change the - # fontinfo, metainfo and feature files. - infoPath = os.path.join(outPath, FONTINFO_FILENAME) - if inPath == outPath: - metaInfoPath = os.path.join(inPath, METAINFO_FILENAME) - metaInfo = dict( - creator="org.robofab.ufoLib", - formatVersion=1 - ) - writePlistAtomically(metaInfo, metaInfoPath) - writePlistAtomically(infoData, infoPath) - featuresPath = os.path.join(inPath, FEATURES_FILENAME) - if os.path.exists(featuresPath): - os.remove(featuresPath) - # otherwise write everything. - else: - writer = UFOWriter(outPath, formatVersion=1) - writer.writeGroups(groups) - writer.writeKerning(kerning) - writer.writeLib(libData) - # write the info manually - writePlistAtomically(infoData, infoPath) - # copy the glyph tree - inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME) - outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME) - if os.path.exists(inGlyphs): - shutil.copytree(inGlyphs, outGlyphs) - - -# ---------- -# UFO Reader -# ---------- - - -class UFOReader(object): - - """Read the various components of the .ufo.""" - - def __init__(self, path): - self._path = path - self.readMetaInfo() - - def _get_formatVersion(self): - return self._formatVersion - - formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is determined by reading metainfo.plist during __init__.") - - def _checkForFile(self, path): - if not os.path.exists(path): - return False - else: - return True - - def readMetaInfo(self): - """ - Read metainfo.plist. Only used for internal operations. - """ - path = os.path.join(self._path, METAINFO_FILENAME) - if not self._checkForFile(path): - raise UFOLibError("metainfo.plist is missing in %s. This file is required." % self._path) - # should there be a blind try/except with a UFOLibError - # raised in except here (and elsewhere)? It would be nice to - # provide external callers with a single exception to catch. - data = readPlist(path) - formatVersion = data["formatVersion"] - if formatVersion not in supportedUFOFormatVersions: - raise UFOLibError("Unsupported UFO format (%d) in %s." % (formatVersion, self._path)) - self._formatVersion = formatVersion - - def readGroups(self): - """ - Read groups.plist. Returns a dict. - """ - path = os.path.join(self._path, GROUPS_FILENAME) - if not self._checkForFile(path): - return {} - return readPlist(path) - - def readInfo(self, info): - """ - Read fontinfo.plist. It requires an object that allows - setting attributes with names that follow the fontinfo.plist - version 2 specification. This will write the attributes - defined in the file into the object. - """ - # load the file and return if there is no file - path = os.path.join(self._path, FONTINFO_FILENAME) - if not self._checkForFile(path): - return - infoDict = readPlist(path) - infoDataToSet = {} - # version 1 - if self._formatVersion == 1: - for attr in fontInfoAttributesVersion1: - value = infoDict.get(attr) - if value is not None: - infoDataToSet[attr] = value - infoDataToSet = _convertFontInfoDataVersion1ToVersion2(infoDataToSet) - # version 2 - elif self._formatVersion == 2: - for attr, dataValidationDict in _fontInfoAttributesVersion2ValueData.items(): - value = infoDict.get(attr) - if value is None: - continue - infoDataToSet[attr] = value - # unsupported version - else: - raise NotImplementedError - # validate data - infoDataToSet = _validateInfoVersion2Data(infoDataToSet) - # populate the object - for attr, value in infoDataToSet.items(): - try: - setattr(info, attr, value) - except AttributeError: - raise UFOLibError("The supplied info object does not support setting a necessary attribute (%s)." % attr) - - def readKerning(self): - """ - Read kerning.plist. Returns a dict. - """ - path = os.path.join(self._path, KERNING_FILENAME) - if not self._checkForFile(path): - return {} - kerningNested = readPlist(path) - kerning = {} - for left in kerningNested: - for right in kerningNested[left]: - value = kerningNested[left][right] - kerning[left, right] = value - return kerning - - def readLib(self): - """ - Read lib.plist. Returns a dict. - """ - path = os.path.join(self._path, LIB_FILENAME) - if not self._checkForFile(path): - return {} - return readPlist(path) - - def readFeatures(self): - """ - Read features.fea. Returns a string. - """ - path = os.path.join(self._path, FEATURES_FILENAME) - if not self._checkForFile(path): - return "" - f = open(path, READ_MODE) - text = f.read() - f.close() - return text - - def getGlyphSet(self): - """ - Return the GlyphSet associated with the - glyphs directory in the .ufo. - """ - glyphsPath = os.path.join(self._path, GLYPHS_DIRNAME) - return GlyphSet(glyphsPath) - - def getCharacterMapping(self): - """ - Return a dictionary that maps unicode values (ints) to - lists of glyph names. - """ - glyphsPath = os.path.join(self._path, GLYPHS_DIRNAME) - glyphSet = GlyphSet(glyphsPath) - allUnicodes = glyphSet.getUnicodes() - cmap = {} - for glyphName, unicodes in allUnicodes.iteritems(): - for code in unicodes: - if code in cmap: - cmap[code].append(glyphName) - else: - cmap[code] = [glyphName] - return cmap - - -# ---------- -# UFO Writer -# ---------- - - -class UFOWriter(object): - - """Write the various components of the .ufo.""" - - def __init__(self, path, formatVersion=2, fileCreator="org.robofab.ufoLib"): - if formatVersion not in supportedUFOFormatVersions: - raise UFOLibError("Unsupported UFO format (%d)." % formatVersion) - self._path = path - self._formatVersion = formatVersion - self._fileCreator = fileCreator - self._writeMetaInfo() - # handle down conversion - if formatVersion == 1: - ## remove existing features.fea - featuresPath = os.path.join(path, FEATURES_FILENAME) - if os.path.exists(featuresPath): - os.remove(featuresPath) - - def _get_formatVersion(self): - return self._formatVersion - - formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is set into metainfo.plist during __init__.") - - def _get_fileCreator(self): - return self._fileCreator - - fileCreator = property(_get_fileCreator, doc="The file creator of the UFO. This is set into metainfo.plist during __init__.") - - def _makeDirectory(self, subDirectory=None): - path = self._path - if subDirectory: - path = os.path.join(self._path, subDirectory) - if not os.path.exists(path): - os.makedirs(path) - return path - - def _writeMetaInfo(self): - self._makeDirectory() - path = os.path.join(self._path, METAINFO_FILENAME) - metaInfo = dict( - creator=self._fileCreator, - formatVersion=self._formatVersion - ) - writePlistAtomically(metaInfo, path) - - def writeGroups(self, groups): - """ - Write groups.plist. This method requires a - dict of glyph groups as an argument. - """ - self._makeDirectory() - path = os.path.join(self._path, GROUPS_FILENAME) - groupsNew = {} - for key, value in groups.items(): - groupsNew[key] = list(value) - if groupsNew: - writePlistAtomically(groupsNew, path) - elif os.path.exists(path): - os.remove(path) - - def writeInfo(self, info): - """ - Write info.plist. This method requires an object - that supports getting attributes that follow the - fontinfo.plist version 2 secification. Attributes - will be taken from the given object and written - into the file. - """ - self._makeDirectory() - path = os.path.join(self._path, FONTINFO_FILENAME) - # gather version 2 data - infoData = {} - for attr in _fontInfoAttributesVersion2ValueData.keys(): - try: - value = getattr(info, attr) - except AttributeError: - raise UFOLibError("The supplied info object does not support getting a necessary attribute (%s)." % attr) - if value is None: - continue - infoData[attr] = value - # validate data - infoData = _validateInfoVersion2Data(infoData) - # down convert data to version 1 if necessary - if self._formatVersion == 1: - infoData = _convertFontInfoDataVersion2ToVersion1(infoData) - # write file - writePlistAtomically(infoData, path) - - def writeKerning(self, kerning): - """ - Write kerning.plist. This method requires a - dict of kerning pairs as an argument. - """ - self._makeDirectory() - path = os.path.join(self._path, KERNING_FILENAME) - kerningDict = {} - for left, right in kerning.keys(): - value = kerning[left, right] - if not left in kerningDict: - kerningDict[left] = {} - kerningDict[left][right] = value - if kerningDict: - writePlistAtomically(kerningDict, path) - elif os.path.exists(path): - os.remove(path) - - def writeLib(self, libDict): - """ - Write lib.plist. This method requires a - lib dict as an argument. - """ - self._makeDirectory() - path = os.path.join(self._path, LIB_FILENAME) - if libDict: - writePlistAtomically(libDict, path) - elif os.path.exists(path): - os.remove(path) - - def writeFeatures(self, features): - """ - Write features.fea. This method requires a - features string as an argument. - """ - if self._formatVersion == 1: - raise UFOLibError("features.fea is not allowed in UFO Format Version 1.") - self._makeDirectory() - path = os.path.join(self._path, FEATURES_FILENAME) - writeFileAtomically(features, path) - - def makeGlyphPath(self): - """ - Make the glyphs directory in the .ufo. - Returns the path of the directory created. - """ - glyphDir = self._makeDirectory(GLYPHS_DIRNAME) - return glyphDir - - def getGlyphSet(self, glyphNameToFileNameFunc=None): - """ - Return the GlyphSet associated with the - glyphs directory in the .ufo. - """ - return GlyphSet(self.makeGlyphPath(), glyphNameToFileNameFunc) - -# ---------------- -# Helper Functions -# ---------------- - -def makeUFOPath(path): - """ - Return a .ufo pathname. - - >>> makeUFOPath("/directory/something.ext") - '/directory/something.ufo' - >>> makeUFOPath("/directory/something.another.thing.ext") - '/directory/something.another.thing.ufo' - """ - dir, name = os.path.split(path) - name = ".".join([".".join(name.split(".")[:-1]), "ufo"]) - return os.path.join(dir, name) - -def writePlistAtomically(obj, path): - """ - Write a plist for "obj" to "path". Do this sort of atomically, - making it harder to cause corrupt files, for example when writePlist - encounters an error halfway during write. This also checks to see - if text matches the text that is already in the file at path. - If so, the file is not rewritten so that the modification date - is preserved. - """ - f = StringIO() - writePlist(obj, f) - data = f.getvalue() - writeFileAtomically(data, path) - -def writeFileAtomically(text, path): - """Write text into a file at path. Do this sort of atomically - making it harder to cause corrupt files. This also checks to see - if text matches the text that is already in the file at path. - If so, the file is not rewritten so that the modification date - is preserved.""" - if os.path.exists(path): - f = open(path, READ_MODE) - oldText = f.read() - f.close() - if text == oldText: - return - # if the text is empty, remove the existing file - if not text: - os.remove(path) - if text: - f = open(path, WRITE_MODE) - f.write(text) - f.close() - -# ---------------------- -# fontinfo.plist Support -# ---------------------- - -# Version 1 - -fontInfoAttributesVersion1 = set([ - "familyName", - "styleName", - "fullName", - "fontName", - "menuName", - "fontStyle", - "note", - "versionMajor", - "versionMinor", - "year", - "copyright", - "notice", - "trademark", - "license", - "licenseURL", - "createdBy", - "designer", - "designerURL", - "vendorURL", - "unitsPerEm", - "ascender", - "descender", - "capHeight", - "xHeight", - "defaultWidth", - "slantAngle", - "italicAngle", - "widthName", - "weightName", - "weightValue", - "fondName", - "otFamilyName", - "otStyleName", - "otMacName", - "msCharSet", - "fondID", - "uniqueID", - "ttVendor", - "ttUniqueID", - "ttVersion", -]) - -# Version 2 - -# Validators - -def validateFontInfoVersion2ValueForAttribute(attr, value): - """ - This performs very basic validation of the value for attribute - following the UFO fontinfo.plist specification. The results - of this should not be interpretted as *correct* for the font - that they are part of. This merely indicates that the value - is of the proper type and, where the specification defines - a set range of possible values for an attribute, that the - value is in the accepted range. - """ - dataValidationDict = _fontInfoAttributesVersion2ValueData[attr] - valueType = dataValidationDict.get("type") - validator = dataValidationDict.get("valueValidator") - valueOptions = dataValidationDict.get("valueOptions") - # have specific options for the validator - if valueOptions is not None: - isValidValue = validator(value, valueOptions) - # no specific options - else: - if validator == _fontInfoTypeValidator: - isValidValue = validator(value, valueType) - else: - isValidValue = validator(value) - return isValidValue - -def _validateInfoVersion2Data(infoData): - validInfoData = {} - for attr, value in infoData.items(): - isValidValue = validateFontInfoVersion2ValueForAttribute(attr, value) - if not isValidValue: - raise UFOLibError("Invalid value for attribute %s (%s)." % (attr, repr(value))) - else: - validInfoData[attr] = value - return infoData - -def _fontInfoTypeValidator(value, typ): - return isinstance(value, typ) - -def _fontInfoVersion2IntListValidator(values, validValues): - if not isinstance(values, (list, tuple)): - return False - valuesSet = set(values) - validValuesSet = set(validValues) - if len(valuesSet - validValuesSet) > 0: - return False - for value in values: - if not isinstance(value, int): - return False - return True - -def _fontInfoVersion2StyleMapStyleNameValidator(value): - options = ["regular", "italic", "bold", "bold italic"] - return value in options - -def _fontInfoVersion2OpenTypeHeadCreatedValidator(value): - # format: 0000/00/00 00:00:00 - if not isinstance(value, (str, unicode)): - return False - # basic formatting - if not len(value) == 19: - return False - if value.count(" ") != 1: - return False - date, time = value.split(" ") - if date.count("/") != 2: - return False - if time.count(":") != 2: - return False - # date - year, month, day = date.split("/") - if len(year) != 4: - return False - if len(month) != 2: - return False - if len(day) != 2: - return False - try: - year = int(year) - month = int(month) - day = int(day) - except ValueError: - return False - if month < 1 or month > 12: - return False - monthMaxDay = calendar.monthrange(year, month) - if month > monthMaxDay: - return False - # time - hour, minute, second = time.split(":") - if len(hour) != 2: - return False - if len(minute) != 2: - return False - if len(second) != 2: - return False - try: - hour = int(hour) - minute = int(minute) - second = int(second) - except ValueError: - return False - if hour < 0 or hour > 23: - return False - if minute < 0 or minute > 59: - return False - if second < 0 or second > 59: - return True - # fallback - return True - -def _fontInfoVersion2OpenTypeOS2WeightClassValidator(value): - if not isinstance(value, int): - return False - if value < 0: - return False - return True - -def _fontInfoVersion2OpenTypeOS2WidthClassValidator(value): - if not isinstance(value, int): - return False - if value < 1: - return False - if value > 9: - return False - return True - -def _fontInfoVersion2OpenTypeOS2PanoseValidator(values): - if not isinstance(values, (list, tuple)): - return False - if len(values) != 10: - return False - for value in values: - if not isinstance(value, int): - return False - # XXX further validation? - return True - -def _fontInfoVersion2OpenTypeOS2FamilyClassValidator(values): - if not isinstance(values, (list, tuple)): - return False - if len(values) != 2: - return False - for value in values: - if not isinstance(value, int): - return False - classID, subclassID = values - if classID < 0 or classID > 14: - return False - if subclassID < 0 or subclassID > 15: - return False - return True - -def _fontInfoVersion2PostscriptBluesValidator(values): - if not isinstance(values, (list, tuple)): - return False - if len(values) > 14: - return False - if len(values) % 2: - return False - for value in values: - if not isinstance(value, (int, float)): - return False - return True - -def _fontInfoVersion2PostscriptOtherBluesValidator(values): - if not isinstance(values, (list, tuple)): - return False - if len(values) > 10: - return False - if len(values) % 2: - return False - for value in values: - if not isinstance(value, (int, float)): - return False - return True - -def _fontInfoVersion2PostscriptStemsValidator(values): - if not isinstance(values, (list, tuple)): - return False - if len(values) > 12: - return False - for value in values: - if not isinstance(value, (int, float)): - return False - return True - -def _fontInfoVersion2PostscriptWindowsCharacterSetValidator(value): - validValues = range(1, 21) - if value not in validValues: - return False - return True - -# Attribute Definitions -# This defines the attributes, types and, in some -# cases the possible values, that can exist is -# fontinfo.plist. - -_fontInfoVersion2OpenTypeHeadFlagsOptions = range(0, 14) -_fontInfoVersion2OpenTypeOS2SelectionOptions = [1, 2, 3, 4, 7, 8, 9] -_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions = range(0, 128) -_fontInfoVersion2OpenTypeOS2CodePageRangesOptions = range(0, 64) -_fontInfoVersion2OpenTypeOS2TypeOptions = [0, 1, 2, 3, 8, 9] - -_fontInfoAttributesVersion2ValueData = { - "familyName" : dict(type=(str, unicode)), - "styleName" : dict(type=(str, unicode)), - "styleMapFamilyName" : dict(type=(str, unicode)), - "styleMapStyleName" : dict(type=(str, unicode), valueValidator=_fontInfoVersion2StyleMapStyleNameValidator), - "versionMajor" : dict(type=int), - "versionMinor" : dict(type=int), - "year" : dict(type=int), - "copyright" : dict(type=(str, unicode)), - "trademark" : dict(type=(str, unicode)), - "unitsPerEm" : dict(type=(int, float)), - "descender" : dict(type=(int, float)), - "xHeight" : dict(type=(int, float)), - "capHeight" : dict(type=(int, float)), - "ascender" : dict(type=(int, float)), - "italicAngle" : dict(type=(float, int)), - "note" : dict(type=(str, unicode)), - "openTypeHeadCreated" : dict(type=(str, unicode), valueValidator=_fontInfoVersion2OpenTypeHeadCreatedValidator), - "openTypeHeadLowestRecPPEM" : dict(type=(int, float)), - "openTypeHeadFlags" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeHeadFlagsOptions), - "openTypeHheaAscender" : dict(type=(int, float)), - "openTypeHheaDescender" : dict(type=(int, float)), - "openTypeHheaLineGap" : dict(type=(int, float)), - "openTypeHheaCaretSlopeRise" : dict(type=int), - "openTypeHheaCaretSlopeRun" : dict(type=int), - "openTypeHheaCaretOffset" : dict(type=(int, float)), - "openTypeNameDesigner" : dict(type=(str, unicode)), - "openTypeNameDesignerURL" : dict(type=(str, unicode)), - "openTypeNameManufacturer" : dict(type=(str, unicode)), - "openTypeNameManufacturerURL" : dict(type=(str, unicode)), - "openTypeNameLicense" : dict(type=(str, unicode)), - "openTypeNameLicenseURL" : dict(type=(str, unicode)), - "openTypeNameVersion" : dict(type=(str, unicode)), - "openTypeNameUniqueID" : dict(type=(str, unicode)), - "openTypeNameDescription" : dict(type=(str, unicode)), - "openTypeNamePreferredFamilyName" : dict(type=(str, unicode)), - "openTypeNamePreferredSubfamilyName" : dict(type=(str, unicode)), - "openTypeNameCompatibleFullName" : dict(type=(str, unicode)), - "openTypeNameSampleText" : dict(type=(str, unicode)), - "openTypeNameWWSFamilyName" : dict(type=(str, unicode)), - "openTypeNameWWSSubfamilyName" : dict(type=(str, unicode)), - "openTypeOS2WidthClass" : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WidthClassValidator), - "openTypeOS2WeightClass" : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WeightClassValidator), - "openTypeOS2Selection" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2SelectionOptions), - "openTypeOS2VendorID" : dict(type=(str, unicode)), - "openTypeOS2Panose" : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2PanoseValidator), - "openTypeOS2FamilyClass" : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2FamilyClassValidator), - "openTypeOS2UnicodeRanges" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions), - "openTypeOS2CodePageRanges" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2CodePageRangesOptions), - "openTypeOS2TypoAscender" : dict(type=(int, float)), - "openTypeOS2TypoDescender" : dict(type=(int, float)), - "openTypeOS2TypoLineGap" : dict(type=(int, float)), - "openTypeOS2WinAscent" : dict(type=(int, float)), - "openTypeOS2WinDescent" : dict(type=(int, float)), - "openTypeOS2Type" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2TypeOptions), - "openTypeOS2SubscriptXSize" : dict(type=(int, float)), - "openTypeOS2SubscriptYSize" : dict(type=(int, float)), - "openTypeOS2SubscriptXOffset" : dict(type=(int, float)), - "openTypeOS2SubscriptYOffset" : dict(type=(int, float)), - "openTypeOS2SuperscriptXSize" : dict(type=(int, float)), - "openTypeOS2SuperscriptYSize" : dict(type=(int, float)), - "openTypeOS2SuperscriptXOffset" : dict(type=(int, float)), - "openTypeOS2SuperscriptYOffset" : dict(type=(int, float)), - "openTypeOS2StrikeoutSize" : dict(type=(int, float)), - "openTypeOS2StrikeoutPosition" : dict(type=(int, float)), - "openTypeVheaVertTypoAscender" : dict(type=(int, float)), - "openTypeVheaVertTypoDescender" : dict(type=(int, float)), - "openTypeVheaVertTypoLineGap" : dict(type=(int, float)), - "openTypeVheaCaretSlopeRise" : dict(type=int), - "openTypeVheaCaretSlopeRun" : dict(type=int), - "openTypeVheaCaretOffset" : dict(type=(int, float)), - "postscriptFontName" : dict(type=(str, unicode)), - "postscriptFullName" : dict(type=(str, unicode)), - "postscriptSlantAngle" : dict(type=(float, int)), - "postscriptUniqueID" : dict(type=int), - "postscriptUnderlineThickness" : dict(type=(int, float)), - "postscriptUnderlinePosition" : dict(type=(int, float)), - "postscriptIsFixedPitch" : dict(type=bool), - "postscriptBlueValues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator), - "postscriptOtherBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator), - "postscriptFamilyBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator), - "postscriptFamilyOtherBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator), - "postscriptStemSnapH" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator), - "postscriptStemSnapV" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator), - "postscriptBlueFuzz" : dict(type=(int, float)), - "postscriptBlueShift" : dict(type=(int, float)), - "postscriptBlueScale" : dict(type=(float, int)), - "postscriptForceBold" : dict(type=bool), - "postscriptDefaultWidthX" : dict(type=(int, float)), - "postscriptNominalWidthX" : dict(type=(int, float)), - "postscriptWeightName" : dict(type=(str, unicode)), - "postscriptDefaultCharacter" : dict(type=(str, unicode)), - "postscriptWindowsCharacterSet" : dict(type=int, valueValidator=_fontInfoVersion2PostscriptWindowsCharacterSetValidator), - "macintoshFONDFamilyID" : dict(type=int), - "macintoshFONDName" : dict(type=(str, unicode)), -} -fontInfoAttributesVersion2 = set(_fontInfoAttributesVersion2ValueData.keys()) - -# insert the type validator for all attrs that -# have no defined validator. -for attr, dataDict in _fontInfoAttributesVersion2ValueData.items(): - if "valueValidator" not in dataDict: - dataDict["valueValidator"] = _fontInfoTypeValidator - -# Version Conversion Support -# These are used from converting from version 1 -# to version 2 or vice-versa. - -def _flipDict(d): - flipped = {} - for key, value in d.items(): - flipped[value] = key - return flipped - -_fontInfoAttributesVersion1To2 = { - "menuName" : "styleMapFamilyName", - "designer" : "openTypeNameDesigner", - "designerURL" : "openTypeNameDesignerURL", - "createdBy" : "openTypeNameManufacturer", - "vendorURL" : "openTypeNameManufacturerURL", - "license" : "openTypeNameLicense", - "licenseURL" : "openTypeNameLicenseURL", - "ttVersion" : "openTypeNameVersion", - "ttUniqueID" : "openTypeNameUniqueID", - "notice" : "openTypeNameDescription", - "otFamilyName" : "openTypeNamePreferredFamilyName", - "otStyleName" : "openTypeNamePreferredSubfamilyName", - "otMacName" : "openTypeNameCompatibleFullName", - "weightName" : "postscriptWeightName", - "weightValue" : "openTypeOS2WeightClass", - "ttVendor" : "openTypeOS2VendorID", - "uniqueID" : "postscriptUniqueID", - "fontName" : "postscriptFontName", - "fondID" : "macintoshFONDFamilyID", - "fondName" : "macintoshFONDName", - "defaultWidth" : "postscriptDefaultWidthX", - "slantAngle" : "postscriptSlantAngle", - "fullName" : "postscriptFullName", - # require special value conversion - "fontStyle" : "styleMapStyleName", - "widthName" : "openTypeOS2WidthClass", - "msCharSet" : "postscriptWindowsCharacterSet" -} -_fontInfoAttributesVersion2To1 = _flipDict(_fontInfoAttributesVersion1To2) -deprecatedFontInfoAttributesVersion2 = set(_fontInfoAttributesVersion1To2.keys()) - -_fontStyle1To2 = { - 64 : "regular", - 1 : "italic", - 32 : "bold", - 33 : "bold italic" -} -_fontStyle2To1 = _flipDict(_fontStyle1To2) -# Some UFO 1 files have 0 -_fontStyle1To2[0] = "regular" - -_widthName1To2 = { - "Ultra-condensed" : 1, - "Extra-condensed" : 2, - "Condensed" : 3, - "Semi-condensed" : 4, - "Medium (normal)" : 5, - "Semi-expanded" : 6, - "Expanded" : 7, - "Extra-expanded" : 8, - "Ultra-expanded" : 9 -} -_widthName2To1 = _flipDict(_widthName1To2) -# FontLab's default width value is "Normal". -# Many format version 1 UFOs will have this. -_widthName1To2["Normal"] = 5 -# FontLab has an "All" width value. In UFO 1 -# move this up to "Normal". -_widthName1To2["All"] = 5 -# "medium" appears in a lot of UFO 1 files. -_widthName1To2["medium"] = 5 -# "Medium" appears in a lot of UFO 1 files. -_widthName1To2["Medium"] = 5 - -_msCharSet1To2 = { - 0 : 1, - 1 : 2, - 2 : 3, - 77 : 4, - 128 : 5, - 129 : 6, - 130 : 7, - 134 : 8, - 136 : 9, - 161 : 10, - 162 : 11, - 163 : 12, - 177 : 13, - 178 : 14, - 186 : 15, - 200 : 16, - 204 : 17, - 222 : 18, - 238 : 19, - 255 : 20 -} -_msCharSet2To1 = _flipDict(_msCharSet1To2) - -def convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value): - """ - Convert value from version 1 to version 2 format. - Returns the new attribute name and the converted value. - If the value is None, None will be returned for the new value. - """ - # convert floats to ints if possible - if isinstance(value, float): - if int(value) == value: - value = int(value) - if value is not None: - if attr == "fontStyle": - v = _fontStyle1To2.get(value) - if v is None: - raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) - value = v - elif attr == "widthName": - v = _widthName1To2.get(value) - if v is None: - raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) - value = v - elif attr == "msCharSet": - v = _msCharSet1To2.get(value) - if v is None: - raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) - value = v - attr = _fontInfoAttributesVersion1To2.get(attr, attr) - return attr, value - -def convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value): - """ - Convert value from version 2 to version 1 format. - Returns the new attribute name and the converted value. - If the value is None, None will be returned for the new value. - """ - if value is not None: - if attr == "styleMapStyleName": - value = _fontStyle2To1.get(value) - elif attr == "openTypeOS2WidthClass": - value = _widthName2To1.get(value) - elif attr == "postscriptWindowsCharacterSet": - value = _msCharSet2To1.get(value) - attr = _fontInfoAttributesVersion2To1.get(attr, attr) - return attr, value - -def _convertFontInfoDataVersion1ToVersion2(data): - converted = {} - for attr, value in data.items(): - # FontLab gives -1 for the weightValue - # for fonts wil no defined value. Many - # format version 1 UFOs will have this. - if attr == "weightValue" and value == -1: - continue - newAttr, newValue = convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value) - # skip if the attribute is not part of version 2 - if newAttr not in fontInfoAttributesVersion2: - continue - # catch values that can't be converted - if value is None: - raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr)) - # store - converted[newAttr] = newValue - return converted - -def _convertFontInfoDataVersion2ToVersion1(data): - converted = {} - for attr, value in data.items(): - newAttr, newValue = convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value) - # only take attributes that are registered for version 1 - if newAttr not in fontInfoAttributesVersion1: - continue - # catch values that can't be converted - if value is None: - raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr)) - # store - converted[newAttr] = newValue - return converted - - -if __name__ == "__main__": - import doctest - doctest.testmod() |