diff options
Diffstat (limited to 'Tests/merge/merge_test.py')
-rw-r--r-- | Tests/merge/merge_test.py | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/Tests/merge/merge_test.py b/Tests/merge/merge_test.py new file mode 100644 index 00000000..5ff12d1c --- /dev/null +++ b/Tests/merge/merge_test.py @@ -0,0 +1,237 @@ +import io +import itertools +from fontTools import ttLib +from fontTools.ttLib.tables._g_l_y_f import Glyph +from fontTools.fontBuilder import FontBuilder +from fontTools.merge import Merger, main as merge_main +import difflib +import os +import re +import shutil +import sys +import tempfile +import unittest +import pathlib +import pytest + + +class MergeIntegrationTest(unittest.TestCase): + def setUp(self): + self.tempdir = None + self.num_tempfiles = 0 + + def tearDown(self): + if self.tempdir: + shutil.rmtree(self.tempdir) + + @staticmethod + def getpath(testfile): + path, _ = os.path.split(__file__) + return os.path.join(path, "data", testfile) + + def temp_path(self, suffix): + if not self.tempdir: + self.tempdir = tempfile.mkdtemp() + self.num_tempfiles += 1 + return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix)) + + IGNORED_LINES_RE = re.compile( + "^(<ttFont | <(checkSumAdjustment|created|modified) ).*" + ) + def read_ttx(self, path): + lines = [] + with open(path, "r", encoding="utf-8") as ttx: + for line in ttx.readlines(): + # Elide lines with data that often change. + if self.IGNORED_LINES_RE.match(line): + lines.append("\n") + else: + lines.append(line.rstrip() + "\n") + return lines + + def expect_ttx(self, font, expected_ttx, tables=None): + path = self.temp_path(suffix=".ttx") + font.saveXML(path, tables=tables) + actual = self.read_ttx(path) + expected = self.read_ttx(expected_ttx) + if actual != expected: + for line in difflib.unified_diff( + expected, actual, fromfile=expected_ttx, tofile=path): + sys.stdout.write(line) + self.fail("TTX output is different from expected") + + def compile_font(self, path, suffix): + savepath = self.temp_path(suffix=suffix) + font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False) + font.importXML(path) + font.save(savepath, reorderTables=None) + return font, savepath + +# ----- +# Tests +# ----- + + def test_merge_cff(self): + _, fontpath1 = self.compile_font(self.getpath("CFFFont1.ttx"), ".otf") + _, fontpath2 = self.compile_font(self.getpath("CFFFont2.ttx"), ".otf") + mergedpath = self.temp_path(".otf") + merge_main([fontpath1, fontpath2, "--output-file=%s" % mergedpath]) + mergedfont = ttLib.TTFont(mergedpath) + self.expect_ttx(mergedfont, self.getpath("CFFFont_expected.ttx")) + + +class gaspMergeUnitTest(unittest.TestCase): + def setUp(self): + self.merger = Merger() + + self.table1 = ttLib.newTable('gasp') + self.table1.version = 1 + self.table1.gaspRange = { + 0x8: 0xA , + 0x10: 0x5, + } + + self.table2 = ttLib.newTable('gasp') + self.table2.version = 1 + self.table2.gaspRange = { + 0x6: 0xB , + 0xFF: 0x4, + } + + self.result = ttLib.newTable('gasp') + + def test_gasp_merge_basic(self): + result = self.result.merge(self.merger, [self.table1, self.table2]) + self.assertEqual(result, self.table1) + + result = self.result.merge(self.merger, [self.table2, self.table1]) + self.assertEqual(result, self.table2) + + def test_gasp_merge_notImplemented(self): + result = self.result.merge(self.merger, [NotImplemented, self.table1]) + self.assertEqual(result, NotImplemented) + + result = self.result.merge(self.merger, [self.table1, NotImplemented]) + self.assertEqual(result, self.table1) + + +class CmapMergeUnitTest(unittest.TestCase): + def setUp(self): + self.merger = Merger() + self.table1 = ttLib.newTable('cmap') + self.table2 = ttLib.newTable('cmap') + self.mergedTable = ttLib.newTable('cmap') + pass + + def tearDown(self): + pass + + + def makeSubtable(self, format, platformID, platEncID, cmap): + module = ttLib.getTableModule('cmap') + subtable = module.cmap_classes[format](format) + (subtable.platformID, + subtable.platEncID, + subtable.language, + subtable.cmap) = (platformID, platEncID, 0, cmap) + return subtable + + # 4-3-1 table merged with 12-3-10 table with no dupes with codepoints outside BMP + def test_cmap_merge_no_dupes(self): + table1 = self.table1 + table2 = self.table2 + mergedTable = self.mergedTable + + cmap1 = {0x2603: 'SNOWMAN'} + table1.tables = [self.makeSubtable(4,3,1, cmap1)] + + cmap2 = {0x26C4: 'SNOWMAN WITHOUT SNOW'} + cmap2Extended = {0x1F93C: 'WRESTLERS'} + cmap2Extended.update(cmap2) + table2.tables = [self.makeSubtable(4,3,1, cmap2), self.makeSubtable(12,3,10, cmap2Extended)] + + self.merger.alternateGlyphsPerFont = [{},{}] + mergedTable.merge(self.merger, [table1, table2]) + + expectedCmap = cmap2.copy() + expectedCmap.update(cmap1) + expectedCmapExtended = cmap2Extended.copy() + expectedCmapExtended.update(cmap1) + self.assertEqual(mergedTable.numSubTables, 2) + self.assertEqual([(table.format, table.platformID, table.platEncID, table.language) for table in mergedTable.tables], + [(4,3,1,0),(12,3,10,0)]) + self.assertEqual(mergedTable.tables[0].cmap, expectedCmap) + self.assertEqual(mergedTable.tables[1].cmap, expectedCmapExtended) + + # Tests Issue #322 + def test_cmap_merge_three_dupes(self): + table1 = self.table1 + table2 = self.table2 + mergedTable = self.mergedTable + + cmap1 = {0x20: 'space#0', 0xA0: 'space#0'} + table1.tables = [self.makeSubtable(4,3,1,cmap1)] + cmap2 = {0x20: 'space#1', 0xA0: 'uni00A0#1'} + table2.tables = [self.makeSubtable(4,3,1,cmap2)] + + self.merger.duplicateGlyphsPerFont = [{},{}] + mergedTable.merge(self.merger, [table1, table2]) + + expectedCmap = cmap1.copy() + self.assertEqual(mergedTable.numSubTables, 1) + table = mergedTable.tables[0] + self.assertEqual((table.format, table.platformID, table.platEncID, table.language), (4,3,1,0)) + self.assertEqual(table.cmap, expectedCmap) + self.assertEqual(self.merger.duplicateGlyphsPerFont, [{}, {'space#0': 'space#1'}]) + + +def _compile(ttFont): + buf = io.BytesIO() + ttFont.save(buf) + buf.seek(0) + return buf + + +def _make_fontfile_with_OS2(*, version, **kwargs): + upem = 1000 + glyphOrder = [".notdef", "a"] + cmap = {0x61: "a"} + glyphs = {gn: Glyph() for gn in glyphOrder} + hmtx = {gn: (500, 0) for gn in glyphOrder} + names = {"familyName": "TestOS2", "styleName": "Regular"} + + fb = FontBuilder(unitsPerEm=upem) + fb.setupGlyphOrder(glyphOrder) + fb.setupCharacterMap(cmap) + fb.setupGlyf(glyphs) + fb.setupHorizontalMetrics(hmtx) + fb.setupHorizontalHeader() + fb.setupNameTable(names) + fb.setupOS2(version=version, **kwargs) + + return _compile(fb.font) + + +def _merge_and_recompile(fontfiles, options=None): + merger = Merger(options) + merged = merger.merge(fontfiles) + buf = _compile(merged) + return ttLib.TTFont(buf) + + +@pytest.mark.parametrize( + "v1, v2", list(itertools.permutations(range(5+1), 2)) +) +def test_merge_OS2_mixed_versions(v1, v2): + # https://github.com/fonttools/fonttools/issues/1865 + fontfiles = [ + _make_fontfile_with_OS2(version=v1), + _make_fontfile_with_OS2(version=v2), + ] + merged = _merge_and_recompile(fontfiles) + assert merged["OS/2"].version == max(v1, v2) + + +if __name__ == "__main__": + import sys + sys.exit(unittest.main()) |