aboutsummaryrefslogtreecommitdiff
path: root/Tests/merge/merge_test.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/merge/merge_test.py')
-rw-r--r--Tests/merge/merge_test.py237
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())