1# Copyright 2013 Google, Inc. All Rights Reserved. 2# 3# Google Author(s): Behdad Esfahbod, Roozbeh Pournader 4 5from fontTools.merge.unicode import is_Default_Ignorable 6from fontTools.pens.recordingPen import DecomposingRecordingPen 7import logging 8 9 10log = logging.getLogger("fontTools.merge") 11 12 13def computeMegaGlyphOrder(merger, glyphOrders): 14 """Modifies passed-in glyphOrders to reflect new glyph names. 15 Stores merger.glyphOrder.""" 16 megaOrder = {} 17 for glyphOrder in glyphOrders: 18 for i, glyphName in enumerate(glyphOrder): 19 if glyphName in megaOrder: 20 n = megaOrder[glyphName] 21 while (glyphName + "." + repr(n)) in megaOrder: 22 n += 1 23 megaOrder[glyphName] = n 24 glyphName += "." + repr(n) 25 glyphOrder[i] = glyphName 26 megaOrder[glyphName] = 1 27 merger.glyphOrder = megaOrder = list(megaOrder.keys()) 28 29 30def _glyphsAreSame( 31 glyphSet1, 32 glyphSet2, 33 glyph1, 34 glyph2, 35 advanceTolerance=0.05, 36 advanceToleranceEmpty=0.20, 37): 38 pen1 = DecomposingRecordingPen(glyphSet1) 39 pen2 = DecomposingRecordingPen(glyphSet2) 40 g1 = glyphSet1[glyph1] 41 g2 = glyphSet2[glyph2] 42 g1.draw(pen1) 43 g2.draw(pen2) 44 if pen1.value != pen2.value: 45 return False 46 # Allow more width tolerance for glyphs with no ink 47 tolerance = advanceTolerance if pen1.value else advanceToleranceEmpty 48 # TODO Warn if advances not the same but within tolerance. 49 if abs(g1.width - g2.width) > g1.width * tolerance: 50 return False 51 if hasattr(g1, "height") and g1.height is not None: 52 if abs(g1.height - g2.height) > g1.height * tolerance: 53 return False 54 return True 55 56 57# Valid (format, platformID, platEncID) triplets for cmap subtables containing 58# Unicode BMP-only and Unicode Full Repertoire semantics. 59# Cf. OpenType spec for "Platform specific encodings": 60# https://docs.microsoft.com/en-us/typography/opentype/spec/name 61class _CmapUnicodePlatEncodings: 62 BMP = {(4, 3, 1), (4, 0, 3), (4, 0, 4), (4, 0, 6)} 63 FullRepertoire = {(12, 3, 10), (12, 0, 4), (12, 0, 6)} 64 65 66def computeMegaCmap(merger, cmapTables): 67 """Sets merger.cmap and merger.glyphOrder.""" 68 69 # TODO Handle format=14. 70 # Only merge format 4 and 12 Unicode subtables, ignores all other subtables 71 # If there is a format 12 table for a font, ignore the format 4 table of it 72 chosenCmapTables = [] 73 for fontIdx, table in enumerate(cmapTables): 74 format4 = None 75 format12 = None 76 for subtable in table.tables: 77 properties = (subtable.format, subtable.platformID, subtable.platEncID) 78 if properties in _CmapUnicodePlatEncodings.BMP: 79 format4 = subtable 80 elif properties in _CmapUnicodePlatEncodings.FullRepertoire: 81 format12 = subtable 82 else: 83 log.warning( 84 "Dropped cmap subtable from font '%s':\t" 85 "format %2s, platformID %2s, platEncID %2s", 86 fontIdx, 87 subtable.format, 88 subtable.platformID, 89 subtable.platEncID, 90 ) 91 if format12 is not None: 92 chosenCmapTables.append((format12, fontIdx)) 93 elif format4 is not None: 94 chosenCmapTables.append((format4, fontIdx)) 95 96 # Build the unicode mapping 97 merger.cmap = cmap = {} 98 fontIndexForGlyph = {} 99 glyphSets = [None for f in merger.fonts] if hasattr(merger, "fonts") else None 100 101 for table, fontIdx in chosenCmapTables: 102 # handle duplicates 103 for uni, gid in table.cmap.items(): 104 oldgid = cmap.get(uni, None) 105 if oldgid is None: 106 cmap[uni] = gid 107 fontIndexForGlyph[gid] = fontIdx 108 elif is_Default_Ignorable(uni) or uni in (0x25CC,): # U+25CC DOTTED CIRCLE 109 continue 110 elif oldgid != gid: 111 # Char previously mapped to oldgid, now to gid. 112 # Record, to fix up in GSUB 'locl' later. 113 if merger.duplicateGlyphsPerFont[fontIdx].get(oldgid) is None: 114 if glyphSets is not None: 115 oldFontIdx = fontIndexForGlyph[oldgid] 116 for idx in (fontIdx, oldFontIdx): 117 if glyphSets[idx] is None: 118 glyphSets[idx] = merger.fonts[idx].getGlyphSet() 119 # if _glyphsAreSame(glyphSets[oldFontIdx], glyphSets[fontIdx], oldgid, gid): 120 # continue 121 merger.duplicateGlyphsPerFont[fontIdx][oldgid] = gid 122 elif merger.duplicateGlyphsPerFont[fontIdx][oldgid] != gid: 123 # Char previously mapped to oldgid but oldgid is already remapped to a different 124 # gid, because of another Unicode character. 125 # TODO: Try harder to do something about these. 126 log.warning( 127 "Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid 128 ) 129 130 131def renameCFFCharStrings(merger, glyphOrder, cffTable): 132 """Rename topDictIndex charStrings based on glyphOrder.""" 133 td = cffTable.cff.topDictIndex[0] 134 135 charStrings = {} 136 for i, v in enumerate(td.CharStrings.charStrings.values()): 137 glyphName = glyphOrder[i] 138 charStrings[glyphName] = v 139 td.CharStrings.charStrings = charStrings 140 141 td.charset = list(glyphOrder) 142