diff --git a/Lib/fontTools/fontBuilder.py b/Lib/fontTools/fontBuilder.py index 70519822f..8854c1644 100644 --- a/Lib/fontTools/fontBuilder.py +++ b/Lib/fontTools/fontBuilder.py @@ -364,9 +364,18 @@ class FontBuilder(object): """Set the glyph order for the font.""" self.font.setGlyphOrder(glyphOrder) - def setupCharacterMap(self, cmapping, allowFallback=False): + def setupCharacterMap(self, cmapping, uvs=None, allowFallback=False): """Build the `cmap` table for the font. The `cmapping` argument should be a dict mapping unicode code points as integers to glyph names. + + The `uvs` argument, when passed, must be a list of tuples, describing + Unicode Variation Sequences. These tuples have three elements: + (unicodeValue, variationSelector, glyphName) + `unicodeValue` and `variationSelector` are integer code points. + `glyphName` may be None, to indicate this is the default variation. + Text processors will then use the cmap to find the glyph name. + Each Unicode Variation Sequence should be an officially supported + sequence, but this is not policed. """ subTables = [] highestUnicode = max(cmapping) @@ -390,6 +399,19 @@ class FontBuilder(object): subTable_0_3 = buildCmapSubTable(cmapping_3_1, format, 0, 3) subTables.append(subTable_0_3) + if uvs is not None: + uvsDict = {} + for unicodeValue, variationSelector, glyphName in uvs: + if cmapping.get(unicodeValue) == glyphName: + # this is a default variation + glyphName = None + if variationSelector not in uvsDict: + uvsDict[variationSelector] = [] + uvsDict[variationSelector].append((unicodeValue, glyphName)) + uvsSubTable = buildCmapSubTable({}, 14, 0, 5) + uvsSubTable.uvsDict = uvsDict + subTables.append(uvsSubTable) + self.font["cmap"] = newTable("cmap") self.font["cmap"].tableVersion = 0 self.font["cmap"].tables = subTables diff --git a/Tests/fontBuilder/data/test_uvs.ttf.ttx b/Tests/fontBuilder/data/test_uvs.ttf.ttx new file mode 100644 index 000000000..ab05bed3a --- /dev/null +++ b/Tests/fontBuilder/data/test_uvs.ttf.ttx @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/fontBuilder/fontBuilder_test.py b/Tests/fontBuilder/fontBuilder_test.py index d23f0f179..97d7cb6a7 100644 --- a/Tests/fontBuilder/fontBuilder_test.py +++ b/Tests/fontBuilder/fontBuilder_test.py @@ -53,9 +53,9 @@ def _setupFontBuilder(isTTF, unitsPerEm=1024): return fb, advanceWidths, nameStrings -def _verifyOutput(outPath): +def _verifyOutput(outPath, tables=None): f = TTFont(outPath) - f.saveXML(outPath + ".ttx") + f.saveXML(outPath + ".ttx", tables=tables) with open(outPath + ".ttx") as f: testData = strip_VariableItems(f.read()) refData = strip_VariableItems(getTestData(os.path.basename(outPath) + ".ttx")) @@ -244,3 +244,42 @@ def test_setupNameTable_no_windows(): assert all(n for n in fb.font["name"].names if n.platformID == 1) assert not any(n for n in fb.font["name"].names if n.platformID == 3) + + +def test_unicodeVariationSequences(tmpdir): + familyName = "UVSTestFont" + styleName = "Regular" + nameStrings = dict(familyName=familyName, styleName=styleName) + nameStrings['psName'] = familyName + "-" + styleName + glyphOrder = [".notdef", "space", "zero", "zero.slash"] + cmap = {ord(" "): "space", ord("0"): "zero"} + uvs = [ + (0x0030, 0xFE00, "zero.slash"), + (0x0030, 0xFE01, None), # not an official sequence, just testing + ] + metrics = {gn: (600, 0) for gn in glyphOrder} + pen = TTGlyphPen(None) + glyph = pen.glyph() # empty placeholder + glyphs = {gn: glyph for gn in glyphOrder} + + fb = FontBuilder(1024, isTTF=True) + fb.setupGlyphOrder(glyphOrder) + fb.setupCharacterMap(cmap, uvs) + fb.setupGlyf(glyphs) + fb.setupHorizontalMetrics(metrics) + fb.setupHorizontalHeader(ascent=824, descent=200) + fb.setupNameTable(nameStrings) + fb.setupOS2() + fb.setupPost() + + outPath = os.path.join(str(tmpdir), "test_uvs.ttf") + fb.save(outPath) + _verifyOutput(outPath, tables=["cmap"]) + + uvs = [ + (0x0030, 0xFE00, "zero.slash"), + (0x0030, 0xFE01, "zero"), # should result in the exact same subtable data, due to cmap[0x0030] == "zero" + ] + fb.setupCharacterMap(cmap, uvs) + fb.save(outPath) + _verifyOutput(outPath, tables=["cmap"])