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")