diff --git a/Lib/fontTools/ttLib/tables/__init__.py b/Lib/fontTools/ttLib/tables/__init__.py index deb1e5e21..3f4c33491 100644 --- a/Lib/fontTools/ttLib/tables/__init__.py +++ b/Lib/fontTools/ttLib/tables/__init__.py @@ -60,6 +60,7 @@ def _moduleFinderHint(): from . import _f_p_g_m from . import _f_v_a_r from . import _g_a_s_p + from . import _g_c_i_d from . import _g_l_y_f from . import _g_v_a_r from . import _h_d_m_x diff --git a/Lib/fontTools/ttLib/tables/_g_c_i_d.py b/Lib/fontTools/ttLib/tables/_g_c_i_d.py new file mode 100644 index 000000000..f8b57e2ac --- /dev/null +++ b/Lib/fontTools/ttLib/tables/_g_c_i_d.py @@ -0,0 +1,8 @@ +from __future__ import print_function, division, absolute_import +from fontTools.misc.py23 import * +from .otBase import BaseTTXConverter + + +# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gcid.html +class table__g_c_i_d(BaseTTXConverter): + pass diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py index 5b04417a3..1d081d1f7 100644 --- a/Lib/fontTools/ttLib/tables/otConverters.py +++ b/Lib/fontTools/ttLib/tables/otConverters.py @@ -5,7 +5,8 @@ from fontTools.misc.fixedTools import ( versionToFixed as ve2fi) from fontTools.misc.textTools import pad, safeEval from fontTools.ttLib import getSearchRange -from .otBase import ValueRecordFactory, CountReference, OTTableWriter +from .otBase import (CountReference, FormatSwitchingBaseTable, + OTTableWriter, ValueRecordFactory) from .otTables import (AATStateTable, AATState, AATAction, ContextualMorphAction) from functools import partial @@ -42,6 +43,8 @@ def buildConverters(tableSpec, tableNamespace): converterClass = SubStruct elif name == "FeatureParams": converterClass = FeatureParams + elif name == "GlyphCIDMapping": + converterClass = StructWithLength else: if not tp in converterMapping and '(' not in tp: tableName = tp @@ -461,6 +464,8 @@ class StructWithLength(Struct): if conv.name == "StructLength": break lengthIndex = len(writer.items) + convIndex + if isinstance(value, FormatSwitchingBaseTable): + lengthIndex += 1 # implicit Format field deadbeef = {1:0xDE, 2:0xDEAD, 4:0xDEADBEEF}[conv.staticSize] before = writer.getDataLength() @@ -1077,6 +1082,52 @@ class STXHeader(BaseConverter): return state +class GlyphCIDMap(BaseConverter): + def read(self, reader, font, tableDict): + glyphOrder = font.getGlyphOrder() + count = reader.readUShort() + cids = reader.readUShortArray(count) + if count > len(glyphOrder): + log.warning("GlyphCIDMap has %d elements, " + "but the font has only %d glyphs; " + "ignoring the rest" % + (count, len(glyphOrder))) + result = {} + for glyphID in range(min(len(cids), len(glyphOrder))): + cid = cids[glyphID] + if cid != 0xFFFF: + result[glyphOrder[glyphID]] = cids[glyphID] + return result + + def write(self, writer, font, tableDict, value, repeatIndex=None): + items = {font.getGlyphID(g): cid + for g, cid in value.items() + if cid is not None and cid != 0xFFFF} + count = max(items) + 1 if items else 0 + writer.writeUShort(count) + for glyphID in range(count): + writer.writeUShort(items.get(glyphID, 0xFFFF)) + + def xmlRead(self, attrs, content, font): + result = {} + for eName, eAttrs, _eContent in filter(istuple, content): + if eName == "CID": + result[eAttrs["glyph"]] = \ + safeEval(eAttrs["value"]) + return result + + def xmlWrite(self, xmlWriter, font, value, name, attrs): + xmlWriter.begintag(name, attrs) + xmlWriter.newline() + for glyph, cid in sorted(value.items()): + if cid is not None and cid != 0xFFFF: + xmlWriter.simpletag( + "CID", glyph=glyph, value=cid) + xmlWriter.newline() + xmlWriter.endtag(name) + xmlWriter.newline() + + class DeltaValue(BaseConverter): def read(self, reader, font, tableDict): @@ -1243,6 +1294,7 @@ converterMapping = { "VarDataValue": VarDataValue, # AAT + "GlyphCIDMap": GlyphCIDMap, "MortChain": StructWithLength, "MortSubtable": StructWithLength, "MorxChain": StructWithLength, diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py index c1615ad11..e39a6fe41 100755 --- a/Lib/fontTools/ttLib/tables/otData.py +++ b/Lib/fontTools/ttLib/tables/otData.py @@ -1283,6 +1283,27 @@ otData = [ ]), + # + # gcid + # + + ('gcid', [ + ('struct', 'GlyphCIDMapping', None, None, 'Glyph to CID mapping table.'), + ]), + + ('GlyphCIDMappingFormat0', [ + ('uint16', 'Format', None, None, 'Format of the glyph-to-CID mapping table, = 0.'), + ('uint16', 'DataFormat', None, None, 'Currenty unused, set to zero.'), + ('uint32', 'StructLength', None, None, 'Size of the table in bytes.'), + ('uint16', 'Registry', None, None, 'The registry ID.'), + ('char64', 'RegistryName', None, None, 'The registry name in ASCII; unused bytes should be set to 0.'), + ('uint16', 'Order', None, None, 'The order ID.'), + ('char64', 'OrderName', None, None, 'The order name in ASCII; unused bytes should be set to 0.'), + ('uint16', 'SupplementVersion', None, None, 'The supplement version.'), + ('GlyphCIDMap', 'CIDs', None, None, 'The CIDs for the glyphs in the font, starting with glyph 0. If a glyph does not correspond to a CID in the identified collection, 0xFFFF is used'), + ]), + + # # lcar # diff --git a/README.rst b/README.rst index 5ff6c4e66..f8cf8b826 100644 --- a/README.rst +++ b/README.rst @@ -101,9 +101,9 @@ The following tables are currently supported: GDEF, GMAP, GPKG, GPOS, GSUB, HVAR, JSTF, LTSH, MATH, META, MVAR, OS/2, SING, STAT, SVG, TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS, TSIV, TTFA, VDMX, VORG, VVAR, ankr, avar, bsln, - cmap, cvar, cvt, feat, fpgm, fvar, gasp, glyf, gvar, hdmx, head, - hhea, hmtx, kern, lcar, loca, ltag, maxp, meta, mort, morx, name, - opbd, post, prep, prop, sbix, trak, vhea and vmtx + cmap, cvar, cvt, feat, fpgm, fvar, gasp, gcid, glyf, gvar, hdmx, + head, hhea, hmtx, kern, lcar, loca, ltag, maxp, meta, mort, morx, + name, opbd, post, prep, prop, sbix, trak, vhea and vmtx .. end table list Other tables are dumped as hexadecimal data. diff --git a/Tests/ttLib/tables/_g_c_i_d_test.py b/Tests/ttLib/tables/_g_c_i_d_test.py new file mode 100644 index 000000000..6d68511a2 --- /dev/null +++ b/Tests/ttLib/tables/_g_c_i_d_test.py @@ -0,0 +1,70 @@ +# coding: utf-8 +from __future__ import print_function, division, absolute_import, unicode_literals +from fontTools.misc.py23 import * +from fontTools.misc.testTools import FakeFont, getXML, parseXML +from fontTools.misc.textTools import deHexStr, hexStr +from fontTools.ttLib import newTable +import unittest + + +# On macOS X 10.12.3, the font /Library/Fonts/AppleGothic.ttf has a ‘gcid’ +# table with a similar structure as this test data, just more CIDs. +GCID_DATA = deHexStr( + "0000 0000 " # 0: Format=0, Flags=0 + "0000 0098 " # 4: Size=152 + "0000 " # 8: Registry=0 + "41 64 6F 62 65 " # 10: RegistryName="Adobe" + + ("00" * 59) + # 15: + "0003 " # 74: Order=3 + "4B 6F 72 65 61 31 " # 76: Order="Korea1" + + ("00" * 58) + # 82: + "0001 " # 140: SupplementVersion + "0004 " # 142: Count + "1234 " # 144: CIDs[0/.notdef]=4660 + "FFFF " # 146: CIDs[1/A]=None + "0007 " # 148: CIDs[2/B]=7 + "DEF0 " # 150: CIDs[3/C]=57072 +) # 152: +assert len(GCID_DATA) == 152, len(GCID_DATA) + + +GCID_XML = [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', +] + + +class GCIDTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.maxDiff = None + cls.font = FakeFont(['.notdef', 'A', 'B', 'C', 'D']) + + def testDecompileToXML(self): + table = newTable('gcid') + table.decompile(GCID_DATA, self.font) + self.assertEqual(getXML(table.toXML, self.font), GCID_XML) + + def testCompileFromXML(self): + table = newTable('gcid') + for name, attrs, content in parseXML(GCID_XML): + table.fromXML(name, attrs, content, font=self.font) + self.assertEqual(hexStr(table.compile(self.font)), + hexStr(GCID_DATA)) + + +if __name__ == '__main__': + import sys + sys.exit(unittest.main())