blob: 5ff12d1cb25580da5ba67d3440b40acf23bce5bb [file] [log] [blame]
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())