diff --git a/Lib/ufoLib/glifLib.py b/Lib/ufoLib/glifLib.py
index cb08b386d..108ca4748 100755
--- a/Lib/ufoLib/glifLib.py
+++ b/Lib/ufoLib/glifLib.py
@@ -485,6 +485,10 @@ def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, writer=
aFile = None
identifiers = set()
# start
+ if glyphName == "":
+ raise GlifLibError("The glyph name is empty.")
+ if not isinstance(glyphName, basestring):
+ raise GlifLibError("The glyph name is not properly formatted.")
writer.begintag("glyph", [("name", glyphName), ("format", formatVersion)])
writer.newline()
# advance
@@ -544,9 +548,13 @@ def _writeUnicodes(glyphObject, writer):
unicodes = getattr(glyphObject, "unicodes", None)
if isinstance(unicodes, int):
unicodes = [unicodes]
+ seen = set()
for code in unicodes:
if not isinstance(code, int):
raise GlifLibError("unicode values must be int")
+ if code in seen:
+ continue
+ seen.add(code)
hexCode = hex(code)[2:].upper()
if len(hexCode) < 4:
hexCode = "0" * (4 - len(hexCode)) + hexCode
@@ -687,7 +695,10 @@ def validateLayerInfoVersion3Data(infoData):
# -----------------
def _stripGlyphXMLTree(nodes):
- for element, attrs, children in nodes:
+ for node in nodes:
+ if len(node) != 3:
+ raise GlifLibError("Invalid GLIF structure.")
+ element, attrs, children = node
# "lib" is formatted as a plist, so we need unstripped
# character data so we can support strings with leading or
# trailing whitespace. Do strip everything else.
@@ -723,6 +734,8 @@ def _readGlyphFromTree(tree, glyphObject=None, pointPen=None):
raise GlifLibError, "Unsupported GLIF format version: %s" % formatVersion
# get the name
glyphName = tree[1].get("name")
+ if glyphName == "":
+ raise GlifLibError("Empty glyph name in GLIF.")
if glyphName and glyphObject is not None:
_relaxedSetattr(glyphObject, "name", glyphName)
# populate the sub elements
@@ -751,7 +764,8 @@ def _readGlyphFromTree(tree, glyphObject=None, pointPen=None):
try:
v = attrs.get("hex", "undefined")
v = int(v, 16)
- unicodes.append(v)
+ if v not in unicodes:
+ unicodes.append(v)
except ValueError:
raise GlifLibError("Illegal value for hex attribute of unicode element.")
elif element == "guideline":
@@ -840,11 +854,14 @@ def _buildOutlineContour(pen, (attrs, children), formatVersion, identifiers):
raise GlifLibError("The contour identifier %s is not valid." % identifier)
identifiers.add(identifier)
# try to pass the identifier attribute
- try:
- pen.beginPath(identifier)
- except TypeError:
+ if formatVersion == 1:
pen.beginPath()
- raise DeprecationWarning("The beginPath method needs an identifier kwarg. The contour's identifier value has been discarded.")
+ else:
+ try:
+ pen.beginPath(identifier)
+ except TypeError:
+ pen.beginPath()
+ raise DeprecationWarning("The beginPath method needs an identifier kwarg. The contour's identifier value has been discarded.")
# points
if children:
# loop through the points very quickly to make sure that the number of off-curves is correct
diff --git a/Lib/ufoLib/test/test_GLIF1.py b/Lib/ufoLib/test/test_GLIF1.py
new file mode 100644
index 000000000..d2922361c
--- /dev/null
+++ b/Lib/ufoLib/test/test_GLIF1.py
@@ -0,0 +1,414 @@
+import unittest
+from ufoLib.glifLib import GlifLibError, readGlyphFromString, writeGlyphToString
+
+# -----------------
+# Glyph and Support
+# -----------------
+
+class Glyph(object):
+
+ def __init__(self):
+ self.name = None
+ self.width = None
+ self.height = None
+ self.unicodes = None
+ self.note = None
+ self.image = None
+ self.guidelines = None
+ self.outline = []
+
+ def _writePointPenCommand(self, command, args, kwargs):
+ if args and kwargs:
+ return "pointPen.%s(*%s, **%s)" % (command, listToString(args), dictToString(kwargs))
+ elif args:
+ return "pointPen.%s(*%s)" % (command, listToString(args))
+ elif kwargs:
+ return "pointPen.%s(**%s)" % (command, dictToString(kwargs))
+ else:
+ return "pointPen.%s()" % command
+
+ def beginPath(self, **kwargs):
+ self.outline.append(self._writePointPenCommand("beginPath", args))
+
+ def endPath(self):
+ self.outline.append(self._writePointPenCommand("endPath"))
+
+ def addPoint(self, *args, **kwargs):
+ self.outline.append(self._writePointPenCommand("addPoint", args, kwargs))
+
+ def addComponent(self, *args, **kwargs):
+ self.outline.append(self._writePointPenCommand("addComponent", args, kwargs))
+
+ def drawPoints(self, pointPen):
+ if self.outline:
+ py = "\n".join(self.outline)
+ exec py in {"pointPen" : pointPen}
+
+ def py(self):
+ text = []
+ if self.name is not None:
+ text.append("glyph.name = \"%s\"" % self.name)
+ if self.width:
+ text.append("glyph.width = %s" % str(self.width))
+ if self.height:
+ text.append("glyph.height = %s" % str(self.height))
+ if self.unicodes is not None:
+ text.append("glyph.unicodes = [%s]" % ", ".join([str(i) for i in self.unicodes]))
+ if self.note is not None:
+ text.append("glyph.note = \"%s\"" % self.note)
+ if self.image is not None:
+ text.append("glyph.image = %s" % dictToString(self.image))
+ if self.guidelines is not None:
+ text.append("glyph.guidelines = %s" % listToString(self.guidelines))
+ if self.outline:
+ text.append("pointPen = glyph.getPointPen()")
+ text += self.outline
+ return "\n".join(text)
+
+def dictToString(d):
+ text = []
+ for key, value in sorted(d.items()):
+ if value is None:
+ continue
+ key = "\"%s\"" % key
+ if isinstance(value, dict):
+ value = dictToString(value)
+ elif isinstance(value, list):
+ value = listToString(value)
+ elif isinstance(value, tuple):
+ value = tupleToString(value)
+ elif isinstance(value, (int, float)):
+ value = str(value)
+ elif isinstance(value, basestring):
+ value = "\"%s\"" % value
+ text.append("%s : %s" % (key, value))
+ return "{%s}" % ", ".join(text)
+
+def listToString(l):
+ text = []
+ for value in l:
+ if isinstance(value, dict):
+ value = dictToString(value)
+ elif isinstance(value, list):
+ value = listToString(value)
+ elif isinstance(value, tuple):
+ value = tupleToString(value)
+ elif isinstance(value, (int, float)):
+ value = str(value)
+ elif isinstance(value, basestring):
+ value = "\"%s\"" % value
+ text.append(value)
+ return "[%s]" % ", ".join(text)
+
+def tupleToString(t):
+ text = []
+ for value in t:
+ if isinstance(value, dict):
+ value = dictToString(value)
+ elif isinstance(value, list):
+ value = listToString(value)
+ elif isinstance(value, tuple):
+ value = tupleToString(value)
+ elif isinstance(value, (int, float)):
+ value = str(value)
+ elif isinstance(value, basestring):
+ value = "\"%s\"" % value
+ text.append(value)
+ return "(%s)" % ", ".join(text)
+
+
+def stripText(text):
+ new = []
+ for line in text.strip().splitlines():
+ line = line.strip()
+ if not line:
+ continue
+ new.append(line)
+ return "\n".join(new)
+
+# ----------
+# Test Cases
+# ----------
+
+class TestGLIF1(unittest.TestCase):
+
+ def assertEqual(self, first, second, msg=None):
+ if isinstance(first, basestring):
+ first = stripText(first)
+ if isinstance(second, basestring):
+ second = stripText(second)
+ return super(TestGLIF1, self).assertEqual(first, second, msg=msg)
+
+ def glyphToGLIF(self, py):
+ py = stripText(py)
+ glyph = Glyph()
+ exec py in {"glyph" : glyph}
+ glif = writeGlyphToString(glyph.name, glyphObject=glyph, drawPointsFunc=glyph.drawPoints, formatVersion=1)
+ glif = "\n".join(glif.splitlines()[1:])
+ return glif
+
+ def glifToPy(self, glif):
+ glif = stripText(glif)
+ glif = "\n" + glif
+ glyph = Glyph()
+ readGlyphFromString(glif, glyphObject=glyph, pointPen=glyph)
+ return glyph.py()
+
+ def testTopElement(self):
+ # not glyph
+ glif = """
+
+
+
+
+ """
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+
+ def testName(self):
+ # legal
+ glif = """
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ """
+ resultGlif = self.glyphToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
+ # empty
+ glif = """
+
+
+
+
+ """
+ py = """
+ glyph.name = ""
+ """
+ self.assertRaises(GlifLibError, self.glyphToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ # not a string
+ py = """
+ glyph.name = 1
+ """
+ self.assertRaises(GlifLibError, self.glyphToGLIF, py)
+
+ def testFormat(self):
+ # legal
+ glif = """
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ """
+ resultGlif = self.glyphToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
+ # wrong number
+ glif = """
+
+
+
+
+ """
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ # not an int
+ glif = """
+
+
+
+
+ """
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+
+ def testBogusGlyphStructure(self):
+ # unknown element
+ glif = """
+
+
+
+ """
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ # content
+ glif = """
+
+ Hello World.
+
+ """
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+
+ def testAdvance(self):
+ # legal: width and height
+ glif = """
+
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ glyph.width = 100
+ glyph.height = 200
+ """
+ resultGlif = self.glyphToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
+ # legal: width and height floats
+ glif = """
+
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ glyph.width = 100.1
+ glyph.height = 200.1
+ """
+ resultGlif = self.glyphToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
+ # legal: width
+ glif = """
+
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ glyph.width = 100
+ """
+ resultGlif = self.glyphToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
+ # legal: height
+ glif = """
+
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ glyph.height = 200
+ """
+ resultGlif = self.glyphToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
+ # illegal: not a number
+ glif = """
+
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ glyph.width = "a"
+ """
+ self.assertRaises(GlifLibError, self.glyphToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
+
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ glyph.height = "a"
+ """
+ self.assertRaises(GlifLibError, self.glyphToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+
+ def testUnicodes(self):
+ # legal
+ glif = """
+
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ glyph.unicodes = [97]
+ """
+ resultGlif = self.glyphToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
+ glif = """
+
+
+
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ glyph.unicodes = [98, 99, 97]
+ """
+ resultGlif = self.glyphToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
+ # illegal
+ glif = """
+
+
+
+
+
+ """
+ py = """
+ glyph.name = "zzzzzz"
+ glyph.unicodes = ["1.1"]
+ """
+ self.assertRaises(GlifLibError, self.glyphToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+
+ def testNote(self):
+ glif = """
+
+
+ hello
+
+
+
+
+ """
+ py = """
+ glyph.name = "a"
+ glyph.note = "hello"
+ """
+ resultGlif = self.glyphToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
+
+
+if __name__ == "__main__":
+ from robofab.test.testSupport import runTests
+ runTests()
diff --git a/Lib/ufoLib/validators.py b/Lib/ufoLib/validators.py
index 3dced5102..2b7c8c206 100644
--- a/Lib/ufoLib/validators.py
+++ b/Lib/ufoLib/validators.py
@@ -696,7 +696,7 @@ def imageValidator(value):
if not genericDictValidator(value, dictPrototype):
return False
# fileName must be one or more characters
- if not fileName:
+ if not value["fileName"]:
return False
# color must follow the proper format
color = value.get("color")