Initial work on reading single file UFO.

This is a work in progress update of UFOReader and UFOWriter that
supports UFO in its package and zipped forms. Reading works. Writing is
not yet implemented.

I'm building a base file system (that lives on top of fs for now and
maybe in the long term) that the reader and writer then subclass. This
base class implements the file system interaction so that the reader
and writer can be blissfully ignorant about file systems.

Additionally, I ran into a problem with the local plistlib.py creating
an import error, so I've temporarily renamed it plistlibShim.py so that
I can continue working.

Did I mention that this is a work in progress? It's a work in progress.
This commit is contained in:
Tal Leming 2016-05-02 23:06:25 -04:00
parent 185c04220d
commit 45910b6131
6 changed files with 418 additions and 220 deletions

View File

@ -3,11 +3,14 @@ import shutil
from io import StringIO, BytesIO, open
import codecs
from copy import deepcopy
from ufoLib.filesystem import FileSystem
from ufoLib.glifLib import GlyphSet
from ufoLib.validators import *
from ufoLib.filenames import userNameToFileName
from ufoLib.converters import convertUFO1OrUFO2KerningToUFO3Kerning
from ufoLib.plistlib import readPlist, writePlist
from ufoLib.plistlibShim import readPlist, writePlist
from ufoLib.errors import UFOLibError
"""
A library for importing .ufo files and their descendants.
Refer to http://unifiedfontobject.com for the UFO specification.
@ -61,7 +64,7 @@ __all__ = [
]
class UFOLibError(Exception): pass
# ----------
@ -118,14 +121,12 @@ def _getPlist(self, fileName, default=None):
# UFO Reader
# ----------
class UFOReader(object):
class UFOReader(FileSystem):
"""Read the various components of the .ufo."""
def __init__(self, path):
if not os.path.exists(path):
raise UFOLibError("The specified UFO doesn't exist.")
self._path = path
super(UFOReader, self).__init__(path)
self.readMetaInfo()
self._upConvertedKerningData = None
@ -182,73 +183,13 @@ class UFOReader(object):
self._upConvertedKerningData["groups"] = groups
self._upConvertedKerningData["groupRenameMaps"] = conversionMaps
# support methods
_checkForFile = staticmethod(os.path.exists)
_getPlist = _getPlist
def readBytesFromPath(self, path, encoding=None):
"""
Returns the bytes in the file at the given path.
The path must be relative to the UFO path.
Returns None if the file does not exist.
An encoding may be passed if needed.
"""
fullPath = os.path.join(self._path, path)
if not self._checkForFile(fullPath):
return None
if os.path.isdir(fullPath):
raise UFOLibError("%s is a directory." % path)
if encoding:
f = open(fullPath, encoding=encoding)
else:
f = open(fullPath, "rb", encoding=encoding)
data = f.read()
f.close()
return data
def getReadFileForPath(self, path, encoding=None):
"""
Returns a file (or file-like) object for the
file at the given path. The path must be relative
to the UFO path. Returns None if the file does not exist.
An encoding may be passed if needed.
Note: The caller is responsible for closing the open file.
"""
fullPath = os.path.join(self._path, path)
if not self._checkForFile(fullPath):
return None
if os.path.isdir(fullPath):
raise UFOLibError("%s is a directory." % path)
if encoding:
f = open(fullPath, "rb", encoding=encoding)
else:
f = open(fullPath, "r")
return f
def getFileModificationTime(self, path):
"""
Returns the modification time (as reported by os.path.getmtime)
for the file at the given path. The path must be relative to
the UFO path. Returns None if the file does not exist.
"""
fullPath = os.path.join(self._path, path)
if not self._checkForFile(fullPath):
return None
return os.path.getmtime(fullPath)
# metainfo.plist
def readMetaInfo(self):
"""
Read metainfo.plist. Only used for internal operations.
"""
# 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 = self._getPlist(METAINFO_FILENAME)
data = self.readPlist(METAINFO_FILENAME)
if not isinstance(data, dict):
raise UFOLibError("metainfo.plist is not properly formatted.")
formatVersion = data["formatVersion"]
@ -259,7 +200,7 @@ class UFOReader(object):
# groups.plist
def _readGroups(self):
return self._getPlist(GROUPS_FILENAME, {})
return self.readPlist(GROUPS_FILENAME, {})
def readGroups(self):
"""
@ -301,7 +242,7 @@ class UFOReader(object):
# fontinfo.plist
def _readInfo(self):
data = self._getPlist(FONTINFO_FILENAME, {})
data = self.readPlist(FONTINFO_FILENAME, {})
if not isinstance(data, dict):
raise UFOLibError("fontinfo.plist is not properly formatted.")
return data
@ -353,7 +294,7 @@ class UFOReader(object):
# kerning.plist
def _readKerning(self):
data = self._getPlist(KERNING_FILENAME, {})
data = self.readPlist(KERNING_FILENAME, {})
return data
def readKerning(self):
@ -384,7 +325,7 @@ class UFOReader(object):
"""
Read lib.plist. Returns a dict.
"""
data = self._getPlist(LIB_FILENAME, {})
data = self.readPlist(LIB_FILENAME, {})
valid, message = fontLibValidator(data)
if not valid:
raise UFOLibError(message)
@ -396,10 +337,9 @@ class UFOReader(object):
"""
Read features.fea. Returns a string.
"""
path = os.path.join(self._path, FEATURES_FILENAME)
if not self._checkForFile(path):
if not self.exists(FEATURES_FILENAME):
return ""
with open(path, "r") as f:
with self.open(FEATURES_FILENAME, "r") as f:
text = f.read()
return text
@ -413,7 +353,7 @@ class UFOReader(object):
if self._formatVersion < 3:
return [(DEFAULT_LAYER_NAME, DEFAULT_GLYPHS_DIRNAME)]
# read the file on disk
contents = self._getPlist(LAYERCONTENTS_FILENAME)
contents = self.readPlist(LAYERCONTENTS_FILENAME)
valid, error = layerContentsValidator(contents, self._path)
if not valid:
raise UFOLibError(error)
@ -456,8 +396,7 @@ class UFOReader(object):
break
if directory is None:
raise UFOLibError("No glyphs directory is mapped to \"%s\"." % layerName)
glyphsPath = os.path.join(self._path, directory)
return GlyphSet(glyphsPath, ufoFormatVersion=self._formatVersion)
return GlyphSet(directory, fileSystem=self, ufoFormatVersion=self._formatVersion)
def getCharacterMapping(self, layerName=None):
"""
@ -477,36 +416,18 @@ class UFOReader(object):
# /data
def getDataDirectoryListing(self, maxDepth=100):
def getDataDirectoryListing(self):
"""
Returns a list of all files in the data directory.
The returned paths will be relative to the UFO.
This will not list directory names, only file names.
Thus, empty directories will be skipped.
The maxDepth argument sets the maximum number
of sub-directories that are allowed.
"""
path = os.path.join(self._path, DATA_DIRNAME)
if not self._checkForFile(path):
if not self.exists(DATA_DIRNAME):
return []
listing = self._getDirectoryListing(path, maxDepth=maxDepth)
listing = [os.path.relpath(path, "data") for path in listing]
listing = self.listDirectory(path, recurse=True)
return listing
def _getDirectoryListing(self, path, depth=0, maxDepth=100):
if depth > maxDepth:
raise UFOLibError("Maximum recusion depth reached.")
result = []
for fileName in os.listdir(path):
p = os.path.join(path, fileName)
if os.path.isdir(p):
result += self._getDirectoryListing(p, depth=depth+1, maxDepth=maxDepth)
else:
p = os.path.relpath(p, self._path)
result.append(p)
return result
def getImageDirectoryListing(self):
"""
Returns a list of all image file names in
@ -515,15 +436,13 @@ class UFOReader(object):
"""
if self._formatVersion < 3:
return []
path = os.path.join(self._path, IMAGES_DIRNAME)
if not os.path.exists(path):
if not self.exists(IMAGES_DIRNAME):
return []
if not os.path.isdir(path):
if not self.isDirectory(IMAGES_DIRNAME):
raise UFOLibError("The UFO contains an \"images\" file instead of a directory.")
result = []
for fileName in os.listdir(path):
p = os.path.join(path, fileName)
if os.path.isdir(p):
for fileName in self.listDirectory(path):
if self.isDirectory(fileName):
# silently skip this as version control
# systems often have hidden directories
continue
@ -538,7 +457,8 @@ class UFOReader(object):
"""
if self._formatVersion < 3:
raise UFOLibError("Reading images is not allowed in UFO %d." % self._formatVersion)
data = self.readBytesFromPath(os.path.join(IMAGES_DIRNAME, fileName))
path = self.joinPath(IMAGES_DIRNAME, fileName)
data = self.readBytesFromPath(path)
if data is None:
raise UFOLibError("No image file named %s." % fileName)
valid, error = pngValidator(data=data)

1
Lib/ufoLib/errors.py Normal file
View File

@ -0,0 +1 @@
class UFOLibError(Exception): pass

282
Lib/ufoLib/filesystem.py Normal file
View File

@ -0,0 +1,282 @@
import os
from io import StringIO
from fs.osfs import OSFS
from fs.zipfs import ZipFS, ZipOpenError
from ufoLib.plistlibShim import readPlist, writePlist
from ufoLib.errors import UFOLibError
try:
basestring
except NameError:
basestring = str
class FileSystem(object):
def __init__(self, path):
self._path = "<data stream>"
if isinstance(path, basestring):
self._path = path
if not os.path.exists(path):
raise UFOLibError("The specified UFO doesn't exist.")
if os.path.isdir(path):
path = OSFS(path)
else:
try:
path = ZipFS(path, mode="r", allow_zip_64=True)
except ZipOpenError:
raise UFOLibError("The specified UFO is not in a proper format.")
self._fs = path
def close(self):
self._fs.close()
# -----------------
# Path Manipulation
# -----------------
def joinPath(self, *parts):
return os.path.join(*parts)
def splitPath(self, path):
return os.path.split(path)
def directoryName(self, path):
return self.splitPath(path)[0]
def relativePath(self, path, start):
return os.path.relpath(path, start)
# ---------
# Existence
# ---------
def exists(self, path):
return self._fs.exists(path)
def isDirectory(self, path):
return self._fs.isdir(path)
def listDirectory(self, path, recurse=False):
return self._listDirectory(path, recurse=recurse)
def _listDirectory(self, path, recurse=False, depth=0, maxDepth=100):
if depth > maxDepth:
raise UFOLibError("Maximum recusion depth reached.")
result = []
for fileName in self._fs.listdir(path):
p = self.joinPath(path, fileName)
if os.path.isdir(p) and recurse:
result += self._listDirectory(p, recurse=True, depth=depth+1, maxDepth=maxDepth)
else:
result.append(p)
return result
# -----------
# File Opener
# -----------
def open(self, path, mode="r", encoding=None):
"""
Returns a file (or file-like) object for the
file at the given path. The path must be relative
to the UFO path. Returns None if the file does
not exist and the mode is "r" or "rb. An encoding
may be passed if needed.
Note: The caller is responsible for closing the open file.
"""
if encoding:
if mode == "r":
mode = "rb"
elif mode == "w":
mode = "wb"
if mode in ("r", "rb") and not self.exists(path):
return None
if self.exists(path) and self.isDirectory(path):
raise UFOLibError("%s is a directory." % path)
if mode in ("w", "wb"):
self._buildDirectoryTree(path)
f = self._fs.open(path, mode, encoding=encoding)
return f
def _buildDirectoryTree(self, path):
directory, fileName = self.splitPath(path)
directoryTree = []
while directory:
directory, d = self.splitPath(directory)
directoryTree.append(d)
directoryTree.reverse()
built = ""
for d in directoryTree:
d = self.joinPath(built, d)
p = self.joinPath(self._path, d)
if not self.exists(p):
self._fs.mkdir(p)
built = d
# ------------------
# Modification Times
# ------------------
def getFileModificationTime(self, path):
"""
Returns the modification time (as reported by os.path.getmtime)
for the file at the given path. The path must be relative to
the UFO path. Returns None if the file does not exist.
"""
if not self.exists(path):
return None
info = self._fs.getinfo(path)
return info["modified_time"]
# --------------
# Raw Read/Write
# --------------
def readBytesFromPath(self, path, encoding=None):
"""
Returns the bytes in the file at the given path.
The path must be relative to the UFO path.
Returns None if the file does not exist.
An encoding may be passed if needed.
"""
f = self.open(path, mode="r", encoding=encoding)
if f is None:
return None
data = f.read()
f.close()
return data
def writeBytesToPath(self, path, data, encoding=None):
"""
Write bytes to path. If needed, the directory tree
for the given path will be built. The path must be
relative to the UFO. An encoding may be passed if needed.
"""
if encoding:
data = StringIO(data).encode(encoding)
self._writeFileAtomically(data, fullPath)
def _writeFileAtomically(self, data, path):
"""
Write data into a file at path. Do this sort of atomically
making it harder to cause corrupt files. This also checks to see
if data matches the data that is already in the file at path.
If so, the file is not rewritten so that the modification date
is preserved.
"""
assert isinstance(data, bytes)
if self.exists(path):
f = self.open(path, "rb")
oldData = f.read()
f.close()
if data == oldData:
return
if data:
f = self.open(path, "wb")
f.write(data)
f.close()
# ------------
# File Removal
# ------------
def remove(self, path):
"""
Remove the file (or directory) at path. The path
must be relative to the UFO. This is only allowed
for files in the data and image directories.
"""
d = path
parts = []
while d:
d, p = self.splitPath(d)
if p:
parts.append(p)
if parts[-1] not in ("images", "data"):
raise UFOLibError("Removing \"%s\" is not legal." % path)
self._removeFileForPath(path, raiseErrorIfMissing=True)
def _removeFileForPath(self, path, raiseErrorIfMissing=False):
if not self.exists(path):
if raiseErrorIfMissing:
raise UFOLibError("The file %s does not exist." % path)
else:
if self.isDirectory(path):
self._fs.removedir(path)
else:
self._fs.remove(path)
directory = self.directoryName(path)
self._removeEmptyDirectoriesForPath(directory)
def _removeEmptyDirectoriesForPath(self, directory):
if not self.exists(directory):
return
if not len(self._fs.listdir(directory)):
self._fs.removedir(directory)
else:
return
directory = self.directoryName(directory)
if directory:
self._removeEmptyDirectoriesForPath(directory)
# --------------
# Property Lists
# --------------
def readPlist(self, path, default=None):
"""
Read a property list relative to the
UFO path. If the file is missing and
default is None a UFOLibError will be
raised. Otherwise default is returned.
The errors that could be raised during
the reading of a plist are unpredictable
and/or too large to list, so, a blind
try: except: is done. If an exception
occurs, a UFOLibError will be raised.
"""
if not self.exists(path):
if default is not None:
return default
else:
raise UFOLibError("%s is missing. This file is required" % path)
try:
with self.open(path, "rb") as f:
return readPlist(f)
except:
raise UFOLibError("The file %s could not be read." % fileName)
if __name__ == "__main__":
from ufoLib import UFOReader
path = os.path.dirname(__file__)
path = os.path.dirname(path)
path = os.path.dirname(path)
path = os.path.join(path, "TestData", "TestFont1 (UFO2).ufo")
import zipfile
def _packDirectory(z, d, root=""):
for i in os.listdir(d):
p = os.path.join(d, i)
l = os.path.join(root, i)
if os.path.isdir(p):
_packDirectory(z, p, root=l)
else:
l = os.path.join(root, i)
z.write(p, l)
p = path + ".zip"
if os.path.exists(p):
os.remove(p)
z = zipfile.ZipFile(p, "w")
_packDirectory(z, path)
z.close()
path += ".zip"
reader = UFOReader(path)
glyphSet = reader.getGlyphSet()
print glyphSet.getGLIF("A")

View File

@ -15,8 +15,9 @@ import os
from io import BytesIO, StringIO, open
from warnings import warn
from fontTools.misc.py23 import tobytes, tostr
from ufoLib.filesystem import FileSystem
from ufoLib.xmlTreeBuilder import buildTree, stripCharacterData
from ufoLib.plistlib import PlistWriter, readPlist, writePlist
from ufoLib.plistlibShim import PlistWriter, readPlist, writePlist
from ufoLib.pointPen import AbstractPointPen, PointToSegmentPen
from ufoLib.filenames import userNameToFileName
from ufoLib.validators import isDictEnough, genericTypeValidator, colorValidator,\
@ -96,7 +97,7 @@ class GlyphSet(object):
glyphClass = Glyph
def __init__(self, dirName, glyphNameToFileNameFunc=None, ufoFormatVersion=3):
def __init__(self, dirName, fileSystem=None, glyphNameToFileNameFunc=None, ufoFormatVersion=3):
"""
'dirName' should be a path to an existing directory.
@ -109,6 +110,10 @@ class GlyphSet(object):
self.dirName = dirName
if ufoFormatVersion not in supportedUFOFormatVersions:
raise GlifLibError("Unsupported UFO format version: %s" % ufoFormatVersion)
if fileSystem is None:
fileSystem = FileSystem(dirName)
dirName = ""
self._fs = fileSystem
self.ufoFormatVersion = ufoFormatVersion
if glyphNameToFileNameFunc is None:
glyphNameToFileNameFunc = glyphNameToFileName
@ -121,12 +126,12 @@ class GlyphSet(object):
"""
Rebuild the contents dict by loading contents.plist.
"""
contentsPath = os.path.join(self.dirName, "contents.plist")
if not os.path.exists(contentsPath):
contentsPath = self._fs.joinPath(self.dirName, "contents.plist")
if not self._fs.exists(contentsPath):
# missing, consider the glyphset empty.
contents = {}
else:
contents = self._readPlist(contentsPath)
contents = self._fs.readPlist(contentsPath)
# validate the contents
invalidFormat = False
if not isinstance(contents, dict):
@ -137,7 +142,7 @@ class GlyphSet(object):
invalidFormat = True
if not isinstance(fileName, basestring):
invalidFormat = True
elif not os.path.exists(os.path.join(self.dirName, fileName)):
elif not self._fs.exists(self._fs.joinPath(self.dirName, fileName)):
raise GlifLibError("contents.plist references a file that does not exist: %s" % fileName)
if invalidFormat:
raise GlifLibError("contents.plist is not properly formatted")
@ -160,22 +165,22 @@ class GlyphSet(object):
self._reverseContents = d
return self._reverseContents
def writeContents(self):
"""
Write the contents.plist file out to disk. Call this method when
you're done writing glyphs.
"""
contentsPath = os.path.join(self.dirName, "contents.plist")
with open(contentsPath, "wb") as f:
writePlist(self.contents, f)
# def writeContents(self):
# """
# Write the contents.plist file out to disk. Call this method when
# you're done writing glyphs.
# """
# contentsPath = os.path.join(self.dirName, "contents.plist")
# with open(contentsPath, "wb") as f:
# writePlist(self.contents, f)
# layer info
def readLayerInfo(self, info):
path = os.path.join(self.dirName, LAYERINFO_FILENAME)
if not os.path.exists(path):
path = self._fs.joinPath(self.dirName, LAYERINFO_FILENAME)
if not self._fs.exists(path):
return
infoDict = self._readPlist(path)
infoDict = self._fs.readPlist(path)
if not isinstance(infoDict, dict):
raise GlifLibError("layerinfo.plist is not properly formatted.")
infoDict = validateLayerInfoVersion3Data(infoDict)
@ -186,28 +191,28 @@ class GlyphSet(object):
except AttributeError:
raise GlifLibError("The supplied layer info object does not support setting a necessary attribute (%s)." % attr)
def writeLayerInfo(self, info):
if self.ufoFormatVersion < 3:
raise GlifLibError("layerinfo.plist is not allowed in UFO %d." % self.ufoFormatVersion)
# gather data
infoData = {}
for attr in list(layerInfoVersion3ValueData.keys()):
if hasattr(info, attr):
try:
value = getattr(info, attr)
except AttributeError:
raise GlifLibError("The supplied info object does not support getting a necessary attribute (%s)." % attr)
if value is None:
continue
infoData[attr] = value
# validate
infoData = validateLayerInfoVersion3Data(infoData)
# write file
path = os.path.join(self.dirName, LAYERINFO_FILENAME)
with open(path, "wb") as f:
writePlist(infoData, f)
# def writeLayerInfo(self, info):
# if self.ufoFormatVersion < 3:
# raise GlifLibError("layerinfo.plist is not allowed in UFO %d." % self.ufoFormatVersion)
# # gather data
# infoData = {}
# for attr in list(layerInfoVersion3ValueData.keys()):
# if hasattr(info, attr):
# try:
# value = getattr(info, attr)
# except AttributeError:
# raise GlifLibError("The supplied info object does not support getting a necessary attribute (%s)." % attr)
# if value is None:
# continue
# infoData[attr] = value
# # validate
# infoData = validateLayerInfoVersion3Data(infoData)
# # write file
# path = os.path.join(self.dirName, LAYERINFO_FILENAME)
# with open(path, "wb") as f:
# writePlist(infoData, f)
# read caching
# # read caching
def getGLIF(self, glyphName):
"""
@ -229,18 +234,18 @@ class GlyphSet(object):
fileName = self.contents.get(glyphName)
path = None
if fileName is not None:
path = os.path.join(self.dirName, fileName)
path = self._fs.joinPath(self.dirName, fileName)
if glyphName not in self._glifCache:
needRead = True
elif fileName is not None and os.path.getmtime(path) != self._glifCache[glyphName][1]:
elif fileName is not None and self._fs.getFileModificationTime(path) != self._glifCache[glyphName][1]:
needRead = True
if needRead:
fileName = self.contents[glyphName]
if not os.path.exists(path):
if not self._fs.exists(path):
raise KeyError(glyphName)
with open(path, "rb") as f:
with self._fs.open(path, "rb") as f:
text = f.read()
self._glifCache[glyphName] = (text, os.path.getmtime(path))
self._glifCache[glyphName] = (text, self._fs.getFileModificationTime(path))
return self._glifCache[glyphName][0]
def getGLIFModificationTime(self, glyphName):
@ -297,71 +302,71 @@ class GlyphSet(object):
formatVersions = (1, 2)
_readGlyphFromTree(tree, glyphObject, pointPen, formatVersions=formatVersions)
def writeGlyph(self, glyphName, glyphObject=None, drawPointsFunc=None, formatVersion=None):
"""
Write a .glif file for 'glyphName' to the glyph set. The
'glyphObject' argument can be any kind of object (even None);
the writeGlyph() method will attempt to get the following
attributes from it:
"width" the advance with of the glyph
"height" the advance height of the glyph
"unicodes" a list of unicode values for this glyph
"note" a string
"lib" a dictionary containing custom data
"image" a dictionary containing image data
"guidelines" a list of guideline data dictionaries
"anchors" a list of anchor data dictionaries
# def writeGlyph(self, glyphName, glyphObject=None, drawPointsFunc=None, formatVersion=None):
# """
# Write a .glif file for 'glyphName' to the glyph set. The
# 'glyphObject' argument can be any kind of object (even None);
# the writeGlyph() method will attempt to get the following
# attributes from it:
# "width" the advance with of the glyph
# "height" the advance height of the glyph
# "unicodes" a list of unicode values for this glyph
# "note" a string
# "lib" a dictionary containing custom data
# "image" a dictionary containing image data
# "guidelines" a list of guideline data dictionaries
# "anchors" a list of anchor data dictionaries
All attributes are optional: if 'glyphObject' doesn't
have the attribute, it will simply be skipped.
# All attributes are optional: if 'glyphObject' doesn't
# have the attribute, it will simply be skipped.
To write outline data to the .glif file, writeGlyph() needs
a function (any callable object actually) that will take one
argument: an object that conforms to the PointPen protocol.
The function will be called by writeGlyph(); it has to call the
proper PointPen methods to transfer the outline to the .glif file.
# To write outline data to the .glif file, writeGlyph() needs
# a function (any callable object actually) that will take one
# argument: an object that conforms to the PointPen protocol.
# The function will be called by writeGlyph(); it has to call the
# proper PointPen methods to transfer the outline to the .glif file.
The GLIF format version will be chosen based on the ufoFormatVersion
passed during the creation of this object. If a particular format
version is desired, it can be passed with the formatVersion argument.
"""
if formatVersion is None:
if self.ufoFormatVersion >= 3:
formatVersion = 2
else:
formatVersion = 1
else:
if formatVersion not in supportedGLIFFormatVersions:
raise GlifLibError("Unsupported GLIF format version: %s" % formatVersion)
if formatVersion == 2 and self.ufoFormatVersion < 3:
raise GlifLibError("Unsupported GLIF format version (%d) for UFO format version %d." % (formatVersion, self.ufoFormatVersion))
self._purgeCachedGLIF(glyphName)
data = writeGlyphToString(glyphName, glyphObject, drawPointsFunc, formatVersion=formatVersion)
fileName = self.contents.get(glyphName)
if fileName is None:
fileName = self.glyphNameToFileName(glyphName, self)
self.contents[glyphName] = fileName
if self._reverseContents is not None:
self._reverseContents[fileName.lower()] = glyphName
path = os.path.join(self.dirName, fileName)
if os.path.exists(path):
with open(path, "rb") as f:
oldData = f.read()
if data == oldData:
return
with open(path, "wb") as f:
f.write(tobytes(data, encoding="utf-8"))
# The GLIF format version will be chosen based on the ufoFormatVersion
# passed during the creation of this object. If a particular format
# version is desired, it can be passed with the formatVersion argument.
# """
# if formatVersion is None:
# if self.ufoFormatVersion >= 3:
# formatVersion = 2
# else:
# formatVersion = 1
# else:
# if formatVersion not in supportedGLIFFormatVersions:
# raise GlifLibError("Unsupported GLIF format version: %s" % formatVersion)
# if formatVersion == 2 and self.ufoFormatVersion < 3:
# raise GlifLibError("Unsupported GLIF format version (%d) for UFO format version %d." % (formatVersion, self.ufoFormatVersion))
# self._purgeCachedGLIF(glyphName)
# data = writeGlyphToString(glyphName, glyphObject, drawPointsFunc, formatVersion=formatVersion)
# fileName = self.contents.get(glyphName)
# if fileName is None:
# fileName = self.glyphNameToFileName(glyphName, self)
# self.contents[glyphName] = fileName
# if self._reverseContents is not None:
# self._reverseContents[fileName.lower()] = glyphName
# path = os.path.join(self.dirName, fileName)
# if os.path.exists(path):
# with open(path, "rb") as f:
# oldData = f.read()
# if data == oldData:
# return
# with open(path, "wb") as f:
# f.write(tobytes(data, encoding="utf-8"))
def deleteGlyph(self, glyphName):
"""Permanently delete the glyph from the glyph set on disk. Will
raise KeyError if the glyph is not present in the glyph set.
"""
self._purgeCachedGLIF(glyphName)
fileName = self.contents[glyphName]
os.remove(os.path.join(self.dirName, fileName))
if self._reverseContents is not None:
del self._reverseContents[self.contents[glyphName].lower()]
del self.contents[glyphName]
# def deleteGlyph(self, glyphName):
# """Permanently delete the glyph from the glyph set on disk. Will
# raise KeyError if the glyph is not present in the glyph set.
# """
# self._purgeCachedGLIF(glyphName)
# fileName = self.contents[glyphName]
# os.remove(os.path.join(self.dirName, fileName))
# if self._reverseContents is not None:
# del self._reverseContents[self.contents[glyphName].lower()]
# del self.contents[glyphName]
# dict-like support
@ -428,16 +433,6 @@ class GlyphSet(object):
images[glyphName] = _fetchImageFileName(text)
return images
# internal methods
def _readPlist(self, path):
try:
with open(path, "rb") as f:
data = readPlist(f)
return data
except:
raise GlifLibError("The file %s could not be read." % path)
# -----------------------
# Glyph Name to File Name

View File

@ -1,4 +1,4 @@
from ufoLib.plistlib import PlistParser
from ufoLib.plistlibShim import PlistParser
"""
Small helper module to parse Plist-formatted data from trees as created
by xmlTreeBuilder.