From 0709eaef85977b5cef0641f1173896d70a000f8c Mon Sep 17 00:00:00 2001 From: Tal Leming Date: Sun, 18 Sep 2011 11:18:31 +0000 Subject: [PATCH] Support guidelines. git-svn-id: http://svn.robofab.com/branches/ufo3k@286 b5fa9d6c-a76f-4ffd-b3cb-f825fc41095c --- Lib/robofab/test/testSupport.py | 41 +++++ Lib/robofab/test/test_ufoLib.py | 308 +++++++++++++++++++++++++++++++- Lib/robofab/ufoLib.py | 101 +++++++++++ 3 files changed, 448 insertions(+), 2 deletions(-) diff --git a/Lib/robofab/test/testSupport.py b/Lib/robofab/test/testSupport.py index 7017c21a9..1bf198fad 100755 --- a/Lib/robofab/test/testSupport.py +++ b/Lib/robofab/test/testSupport.py @@ -434,6 +434,47 @@ fontInfoVersion3 = { ], "firstKerningGroupPrefix" : "@kern1", "secondKerningGroupPrefix" : "@kern2", + "guidelines" : [ + # ints + dict(x=100, y=200, angle=45), + # floats + dict(x=100.5, y=200.5, angle=45.5), + # edges + dict(x=0, y=0, angle=0), + dict(x=0, y=0, angle=360), + dict(x=0, y=0, angle=360.0), + # no y + dict(x=100), + # no x + dict(y=200), + # name + dict(x=100, y=200, angle=45, name="foo"), + dict(x=100, y=200, angle=45, name=""), + # identifier + dict(x=100, y=200, angle=45, identifier="guide1"), + dict(x=100, y=200, angle=45, identifier="guide2"), + dict(x=100, y=200, angle=45, identifier=u"\x20"), + dict(x=100, y=200, angle=45, identifier=u"\x7E"), + # colors + dict(x=100, y=200, angle=45, color="0,0,0,0"), + dict(x=100, y=200, angle=45, color="1,0,0,0"), + dict(x=100, y=200, angle=45, color="1,1,1,1"), + dict(x=100, y=200, angle=45, color="0,1,0,0"), + dict(x=100, y=200, angle=45, color="0,0,1,0"), + dict(x=100, y=200, angle=45, color="0,0,0,1"), + dict(x=100, y=200, angle=45, color="1, 0, 0, 0"), + dict(x=100, y=200, angle=45, color="0, 1, 0, 0"), + dict(x=100, y=200, angle=45, color="0, 0, 1, 0"), + dict(x=100, y=200, angle=45, color="0, 0, 0, 1"), + dict(x=100, y=200, angle=45, color=".5,0,0,0"), + dict(x=100, y=200, angle=45, color="0,.5,0,0"), + dict(x=100, y=200, angle=45, color="0,0,.5,0"), + dict(x=100, y=200, angle=45, color="0,0,0,.5"), + dict(x=100, y=200, angle=45, color=".5,1,1,1"), + dict(x=100, y=200, angle=45, color="1,.5,1,1"), + dict(x=100, y=200, angle=45, color="1,1,.5,1"), + dict(x=100, y=200, angle=45, color="1,1,1,.5"), + ], } expectedFontInfo1To2Conversion = { diff --git a/Lib/robofab/test/test_ufoLib.py b/Lib/robofab/test/test_ufoLib.py index 7b67818ef..6b17d4018 100644 --- a/Lib/robofab/test/test_ufoLib.py +++ b/Lib/robofab/test/test_ufoLib.py @@ -2426,6 +2426,167 @@ class ReadFontInfoVersion3TestCase(unittest.TestCase): reader = UFOReader(self.dstDir) self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + def testGuidelinesRead(self): + # x + ## not an int or float + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x="1")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # y + ## not an int or float + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(y="1")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # angle + ## < 0 + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, y=0, angle=-1)] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## > 360 + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, y=0, angle=361)] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # name + ## not a string + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, name=1)] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # color + ## not a string + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color=1)] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## not enough commas + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="1 0, 0, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="1 0 0, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="1 0 0 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## not enough parts + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color=", 0, 0, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="1, , 0, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="1, 0, , 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="1, 0, 0, ")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color=", , , ")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## not a number in all positions + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="r, 1, 1, 1")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="1, g, 1, 1")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="1, 1, b, 1")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="1, 1, 1, a")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## too many parts + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="1, 0, 0, 0, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## < 0 in each position + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="-1, 0, 0, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="0, -1, 0, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="0, 0, -1, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="0, 0, 0, -1")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## > 1 in each position + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="2, 0, 0, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="0, 2, 0, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="0, 0, 2, 0")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, color="0, 0, 0, 2")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # identifier + ## duplicate + info = dict(fontInfoVersion3) + info["guidelines"] = [dict(x=0, identifier="guide1"), dict(y=0, identifier="guide1")] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + class WriteFontInfoVersion1TestCase(unittest.TestCase): @@ -4420,17 +4581,160 @@ class WriteFontInfoVersion3TestCase(unittest.TestCase): writer = UFOWriter(self.dstDir, formatVersion=3) self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) # overlap - info = dict(fontInfoVersion3) + infoObject = self.makeInfoObject() infoObject.firstKerningGroupPrefix = "@kern" infoObject.secondKerningGroupPrefix = "@kern2" writer = UFOWriter(self.dstDir, formatVersion=3) self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - info = dict(fontInfoVersion3) + infoObject = self.makeInfoObject() infoObject.firstKerningGroupPrefix = "@kern1" infoObject.secondKerningGroupPrefix = "@kern" writer = UFOWriter(self.dstDir, formatVersion=3) self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + def testGuidelinesWrite(self): + # x + ## not an int or float + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x="1")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # y + ## not an int or float + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(y="1")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # angle + ## < 0 + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, y=0, angle=-1)] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## > 360 + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, y=0, angle=361)] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # name + ## not a string + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, name=1)] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # color + ## not a string + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color=1)] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## not enough commas + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="1 0, 0, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="1 0 0, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="1 0 0 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## not enough parts + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color=", 0, 0, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="1, , 0, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="1, 0, , 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="1, 0, 0, ")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color=", , , ")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## not a number in all positions + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="r, 1, 1, 1")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="1, g, 1, 1")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="1, 1, b, 1")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="1, 1, 1, a")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too many parts + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="1, 0, 0, 0, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## < 0 in each position + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="-1, 0, 0, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="0, -1, 0, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="0, 0, -1, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="0, 0, 0, -1")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## > 1 in each position + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="2, 0, 0, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="0, 2, 0, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="0, 0, 2, 0")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, color="0, 0, 0, 2")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # identifier + ## duplicate + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, identifier="guide1"), dict(y=0, identifier="guide1")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## below min + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, identifier=u"\0x1F")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## above max + infoObject = self.makeInfoObject() + infoObject.guidelines = [dict(x=0, identifier=u"\0x7F")] + writer = UFOWriter(self.dstDir, formatVersion=3) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + class UFO3ReadDataTestCase(unittest.TestCase): diff --git a/Lib/robofab/ufoLib.py b/Lib/robofab/ufoLib.py index 6c26f104f..7e7a02721 100755 --- a/Lib/robofab/ufoLib.py +++ b/Lib/robofab/ufoLib.py @@ -1405,6 +1405,106 @@ def _fontInfoKerningPrefixValidator(value): return False return True +def _fontInfoGuidelinesValidator(value): + """ + Version 3+. + """ + if not isinstance(value, list): + return True + identifiers = set() + for guide in value: + if not _fontInfoGuidelineValidator(guide): + return False + identifier = guide.get("identifier") + if identifier is not None: + if identifier in identifiers: + return False + identifiers.add(identifier) + return True + +def _fontInfoGuidelineValidator(value): + """ + Version 3+. + """ + dictPrototype = dict( + x=((int, float), False), y=((int, float), False), angle=((int, float), False), + name=(basestring, False), color=(basestring, False), identifier=(basestring, False) + ) + if not _fontInfoDictValidator(value, dictPrototype): + return False + x = value.get("x") + y = value.get("y") + angle = value.get("angle") + # x or y must be present + if x is None and y is None: + return False + # if x or y are None, angle must not be present + if x is None or y is None: + if angle is not None: + return False + # angle must be between 0 and 360 + if angle is not None: + if angle < 0: + return False + if angle > 360: + return False + # identifier must be 1 or more characters + identifier = value.get("identifier") + if identifier is not None and not _fontInfoIdentifierValidator(identifier): + return False + # color must follow the proper format + color = value.get("color") + if color is not None and not _fontInfoColorValidator(color): + return False + return True + +def _fontInfoIdentifierValidator(value): + """ + Version 3+. + """ + validCharactersMin = 0x20 + validCharactersMax = 0x7E + if not isinstance(value, basestring): + return False + if not value: + return False + for c in value: + c = ord(c) + if c < validCharactersMin or c > validCharactersMax: + return False + return True + +def _fontInfoColorValidator(value): + """ + Version 3+. + """ + if not isinstance(value, basestring): + return False + parts = value.split(",") + if len(parts) != 4: + return False + for part in parts: + part = part.strip() + converted = False + try: + part = int(part) + converted = True + except ValueError: + pass + if not converted: + try: + part = float(part) + converted = True + except ValueError: + pass + if not converted: + return False + if part < 0: + return False + if part > 1: + return False + return True + # Value Options _fontInfoOpenTypeHeadFlagsOptions = range(0, 14) @@ -1582,6 +1682,7 @@ _fontInfoAttributesVersion3ValueData.update({ "woffMetadataExtensions" : dict(type=list, valueValidator=_fontInfoWOFFMetadataExtensionsValidator), "firstKerningGroupPrefix" : dict(type=basestring, valueValidator=_fontInfoKerningPrefixValidator), "secondKerningGroupPrefix" : dict(type=basestring, valueValidator=_fontInfoKerningPrefixValidator), + "guidelines" : dict(type=list, valueValidator=_fontInfoGuidelinesValidator) }) # insert the type validator for all attrs that