Add write time kerning group down conversion to UFOWriter.
This allows the UFO 3 up converted kerning group names to be reset to their original names at the time of writing in UFO <= 2.
This commit is contained in:
parent
0f6b6d741a
commit
8dc8742959
@ -591,6 +591,7 @@ class UFOWriter(object):
|
|||||||
self._path = path
|
self._path = path
|
||||||
self._formatVersion = formatVersion
|
self._formatVersion = formatVersion
|
||||||
self._fileCreator = fileCreator
|
self._fileCreator = fileCreator
|
||||||
|
self._downConversionKerningData = None
|
||||||
# if the file already exists, get the format version.
|
# if the file already exists, get the format version.
|
||||||
# this will be needed for up and down conversion.
|
# this will be needed for up and down conversion.
|
||||||
previousFormatVersion = None
|
previousFormatVersion = None
|
||||||
@ -815,14 +816,67 @@ class UFOWriter(object):
|
|||||||
|
|
||||||
# groups.plist
|
# groups.plist
|
||||||
|
|
||||||
|
def setKerningGroupConversionRenameMaps(self, maps):
|
||||||
|
"""
|
||||||
|
Set maps defining the renaming that should be done
|
||||||
|
when writing groups and kerning in UFO 1 and UFO 2.
|
||||||
|
This will effectively undo the conversion done when
|
||||||
|
UFOReader reads this data. The dictionary should have
|
||||||
|
this form:
|
||||||
|
|
||||||
|
{
|
||||||
|
"side1" : {"group name to use when writing" : "group name in data"},
|
||||||
|
"side2" : {"group name to use when writing" : "group name in data"}
|
||||||
|
}
|
||||||
|
|
||||||
|
This is the same form returned by UFOReader's
|
||||||
|
getKerningGroupConversionRenameMaps method.
|
||||||
|
"""
|
||||||
|
if self._formatVersion >= 3:
|
||||||
|
return # XXX raise an error here
|
||||||
|
# flip the dictionaries
|
||||||
|
remap = {}
|
||||||
|
for side in ("side1", "side2"):
|
||||||
|
for writeName, dataName in maps[side].items():
|
||||||
|
remap[dataName] = writeName
|
||||||
|
self._downConversionKerningData = dict(groupRenameMap=remap)
|
||||||
|
|
||||||
def writeGroups(self, groups):
|
def writeGroups(self, groups):
|
||||||
"""
|
"""
|
||||||
Write groups.plist. This method requires a
|
Write groups.plist. This method requires a
|
||||||
dict of glyph groups as an argument.
|
dict of glyph groups as an argument.
|
||||||
"""
|
"""
|
||||||
|
# validate the data structure
|
||||||
valid, message = groupsValidator(groups)
|
valid, message = groupsValidator(groups)
|
||||||
if not valid:
|
if not valid:
|
||||||
raise UFOLibError(message)
|
raise UFOLibError(message)
|
||||||
|
# down convert
|
||||||
|
if self._formatVersion < 3 and self._downConversionKerningData is not None:
|
||||||
|
remap = self._downConversionKerningData["groupRenameMap"]
|
||||||
|
remappedGroups = {}
|
||||||
|
# there are some edge cases here that are ignored:
|
||||||
|
# 1. if a group is being renamed to a name that
|
||||||
|
# already exists, the existing group is always
|
||||||
|
# overwritten. (this is why there are two loops
|
||||||
|
# below.) there doesn't seem to be a logical
|
||||||
|
# solution to groups mismatching and overwriting
|
||||||
|
# with the specifiecd group seems like a better
|
||||||
|
# solution than throwing an error.
|
||||||
|
# 2. if side 1 and side 2 groups are being renamed
|
||||||
|
# to the same group name there is no check to
|
||||||
|
# ensure that the contents are identical. that
|
||||||
|
# is left up to the caller.
|
||||||
|
for name, contents in groups.items():
|
||||||
|
if name in remap:
|
||||||
|
continue
|
||||||
|
remappedGroups[name] = contents
|
||||||
|
for name, contents in groups.items():
|
||||||
|
if name not in remap:
|
||||||
|
continue
|
||||||
|
name = remap[name]
|
||||||
|
remappedGroups[name] = contents
|
||||||
|
groups = remappedGroups
|
||||||
|
# pack and write
|
||||||
self._makeDirectory()
|
self._makeDirectory()
|
||||||
path = os.path.join(self._path, GROUPS_FILENAME)
|
path = os.path.join(self._path, GROUPS_FILENAME)
|
||||||
groupsNew = {}
|
groupsNew = {}
|
||||||
@ -881,6 +935,7 @@ class UFOWriter(object):
|
|||||||
regards to conflicting pairs. The assumption is that the
|
regards to conflicting pairs. The assumption is that the
|
||||||
kerning data being passed is standards compliant.
|
kerning data being passed is standards compliant.
|
||||||
"""
|
"""
|
||||||
|
# validate the data structure
|
||||||
invalidFormatMessage = "The kerning is not properly formatted."
|
invalidFormatMessage = "The kerning is not properly formatted."
|
||||||
if not isDictEnough(kerning):
|
if not isDictEnough(kerning):
|
||||||
raise UFOLibError(invalidFormatMessage)
|
raise UFOLibError(invalidFormatMessage)
|
||||||
@ -895,6 +950,16 @@ class UFOWriter(object):
|
|||||||
raise UFOLibError(invalidFormatMessage)
|
raise UFOLibError(invalidFormatMessage)
|
||||||
if not isinstance(value, (int, float)):
|
if not isinstance(value, (int, float)):
|
||||||
raise UFOLibError(invalidFormatMessage)
|
raise UFOLibError(invalidFormatMessage)
|
||||||
|
# down convert
|
||||||
|
if self._formatVersion < 3 and self._downConversionKerningData is not None:
|
||||||
|
remap = self._downConversionKerningData["groupRenameMap"]
|
||||||
|
remappedKerning = {}
|
||||||
|
for (side1, side2), value in kerning.items():
|
||||||
|
side1 = remap.get(side1, side1)
|
||||||
|
side2 = remap.get(side2, side2)
|
||||||
|
remappedKerning[side1, side2] = value
|
||||||
|
kerning = remappedKerning
|
||||||
|
# pack and write
|
||||||
self._makeDirectory()
|
self._makeDirectory()
|
||||||
path = os.path.join(self._path, KERNING_FILENAME)
|
path = os.path.join(self._path, KERNING_FILENAME)
|
||||||
kerningDict = {}
|
kerningDict = {}
|
||||||
|
@ -6,7 +6,7 @@ import unittest
|
|||||||
import tempfile
|
import tempfile
|
||||||
import codecs
|
import codecs
|
||||||
from plistlib import writePlist, readPlist
|
from plistlib import writePlist, readPlist
|
||||||
from ufoLib import convertUFOFormatVersion1ToFormatVersion2, UFOReader
|
from ufoLib import convertUFOFormatVersion1ToFormatVersion2, UFOReader, UFOWriter
|
||||||
from testSupport import expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion
|
from testSupport import expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion
|
||||||
|
|
||||||
|
|
||||||
@ -113,13 +113,6 @@ class ConversionFunctionsTestCase(unittest.TestCase):
|
|||||||
convertUFOFormatVersion1ToFormatVersion2(path1, path2)
|
convertUFOFormatVersion1ToFormatVersion2(path1, path2)
|
||||||
self.compareFileStructures(path2, path3, expectedFontInfo1To2Conversion, False)
|
self.compareFileStructures(path2, path3, expectedFontInfo1To2Conversion, False)
|
||||||
|
|
||||||
# def test2To1(self):
|
|
||||||
# path1 = self.getFontPath("TestFont1 (UFO2).ufo")
|
|
||||||
# path2 = self.getFontPath("TestFont1 (UFO2) converted.ufo")
|
|
||||||
# path3 = self.getFontPath("TestFont1 (UFO1).ufo")
|
|
||||||
# convertUFOFormatVersion2ToFormatVersion1(path1, path2)
|
|
||||||
# self.compareFileStructures(path2, path3, expectedFontInfo2To1Conversion, False)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
# kerning up conversion
|
# kerning up conversion
|
||||||
@ -236,6 +229,115 @@ class KerningUpConversionTestCase(unittest.TestCase):
|
|||||||
reader.readInfo(info)
|
reader.readInfo(info)
|
||||||
|
|
||||||
|
|
||||||
|
class KerningDownConversionTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
expectedKerning = {
|
||||||
|
("public.kern1.BGroup", "public.kern2.CGroup"): 7,
|
||||||
|
("public.kern1.BGroup", "public.kern2.DGroup"): 8,
|
||||||
|
("public.kern1.BGroup", "A"): 5,
|
||||||
|
("public.kern1.BGroup", "B"): 6,
|
||||||
|
("public.kern1.CGroup", "public.kern2.CGroup"): 11,
|
||||||
|
("public.kern1.CGroup", "public.kern2.DGroup"): 12,
|
||||||
|
("public.kern1.CGroup", "A"): 9,
|
||||||
|
("public.kern1.CGroup", "B"): 10,
|
||||||
|
("A", "public.kern2.CGroup"): 3,
|
||||||
|
("A", "public.kern2.DGroup"): 4,
|
||||||
|
("A", "A"): 1,
|
||||||
|
("A", "B"): 2
|
||||||
|
}
|
||||||
|
|
||||||
|
groups = {
|
||||||
|
"BGroup": ["B"],
|
||||||
|
"CGroup": ["C"],
|
||||||
|
"DGroup": ["D"],
|
||||||
|
"public.kern1.BGroup": ["B"],
|
||||||
|
"public.kern1.CGroup": ["C", "Ccedilla"],
|
||||||
|
"public.kern2.CGroup": ["C", "Ccedilla"],
|
||||||
|
"public.kern2.DGroup": ["D"],
|
||||||
|
"Not A Kerning Group" : ["A"]
|
||||||
|
}
|
||||||
|
expectedWrittenGroups = {
|
||||||
|
"BGroup": ["B"],
|
||||||
|
"CGroup": ["C", "Ccedilla"],
|
||||||
|
"DGroup": ["D"],
|
||||||
|
"Not A Kerning Group" : ["A"]
|
||||||
|
}
|
||||||
|
|
||||||
|
kerning = {
|
||||||
|
("public.kern1.BGroup", "public.kern2.CGroup"): 7,
|
||||||
|
("public.kern1.BGroup", "public.kern2.DGroup"): 8,
|
||||||
|
("public.kern1.BGroup", "A"): 5,
|
||||||
|
("public.kern1.BGroup", "B"): 6,
|
||||||
|
("public.kern1.CGroup", "public.kern2.CGroup"): 11,
|
||||||
|
("public.kern1.CGroup", "public.kern2.DGroup"): 12,
|
||||||
|
("public.kern1.CGroup", "A"): 9,
|
||||||
|
("public.kern1.CGroup", "B"): 10,
|
||||||
|
("A", "public.kern2.CGroup"): 3,
|
||||||
|
("A", "public.kern2.DGroup"): 4,
|
||||||
|
("A", "A"): 1,
|
||||||
|
("A", "B"): 2
|
||||||
|
}
|
||||||
|
expectedWrittenKerning = {
|
||||||
|
"BGroup" : {
|
||||||
|
"CGroup" : 7,
|
||||||
|
"DGroup" : 8,
|
||||||
|
"A" : 5,
|
||||||
|
"B" : 6
|
||||||
|
},
|
||||||
|
"CGroup" : {
|
||||||
|
"CGroup" : 11,
|
||||||
|
"DGroup" : 12,
|
||||||
|
"A" : 9,
|
||||||
|
"B" : 10
|
||||||
|
},
|
||||||
|
"A" : {
|
||||||
|
"CGroup" : 3,
|
||||||
|
"DGroup" : 4,
|
||||||
|
"A" : 1,
|
||||||
|
"B" : 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
downConversionMapping = {
|
||||||
|
"side1" : {
|
||||||
|
"BGroup" : "public.kern1.BGroup",
|
||||||
|
"CGroup" : "public.kern1.CGroup"
|
||||||
|
},
|
||||||
|
"side2" : {
|
||||||
|
"CGroup" : "public.kern2.CGroup",
|
||||||
|
"DGroup" : "public.kern2.DGroup"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.tempDir = tempfile.mktemp()
|
||||||
|
os.mkdir(self.tempDir)
|
||||||
|
self.dstDir = os.path.join(self.tempDir, "test.ufo")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
shutil.rmtree(self.tempDir)
|
||||||
|
|
||||||
|
def tearDownUFO(self):
|
||||||
|
shutil.rmtree(self.dstDir)
|
||||||
|
|
||||||
|
def testWrite(self):
|
||||||
|
writer = UFOWriter(self.dstDir, formatVersion=2)
|
||||||
|
writer.setKerningGroupConversionRenameMaps(self.downConversionMapping)
|
||||||
|
writer.writeKerning(self.kerning)
|
||||||
|
writer.writeGroups(self.groups)
|
||||||
|
# test groups
|
||||||
|
path = os.path.join(self.dstDir, "groups.plist")
|
||||||
|
writtenGroups = readPlist(path)
|
||||||
|
self.assertEqual(writtenGroups, self.expectedWrittenGroups)
|
||||||
|
# test kerning
|
||||||
|
path = os.path.join(self.dstDir, "kerning.plist")
|
||||||
|
writtenKerning = readPlist(path)
|
||||||
|
self.assertEqual(writtenKerning, self.expectedWrittenKerning)
|
||||||
|
self.tearDownUFO()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from robofab.test.testSupport import runTests
|
from robofab.test.testSupport import runTests
|
||||||
runTests()
|
runTests()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user