diff --git a/Lib/fontTools/fontBuilder.py b/Lib/fontTools/fontBuilder.py index 70519822f..caa8c0533 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 variationSelector not in uvsDict: + uvsDict[variationSelector] = [] + if cmapping.get(unicodeValue) == glyphName: + # this is a default variation + glyphName = None + 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..04730d0f6 --- /dev/null +++ b/Tests/fontBuilder/data/test_uvs.ttf.ttx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UVSTestFont + + + Regular + + + UVSTestFont-Regular + + + UVSTestFont + + + Regular + + + UVSTestFont-Regular + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/fontBuilder/fontBuilder_test.py b/Tests/fontBuilder/fontBuilder_test.py index d23f0f179..dda3b7409 100644 --- a/Tests/fontBuilder/fontBuilder_test.py +++ b/Tests/fontBuilder/fontBuilder_test.py @@ -244,3 +244,34 @@ 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, "zero"), # 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)