174 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			174 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright 2013 Google, Inc. All Rights Reserved.
 | |
| #
 | |
| # Google Author(s): Behdad Esfahbod, Roozbeh Pournader
 | |
| 
 | |
| from fontTools.merge.unicode import is_Default_Ignorable
 | |
| from fontTools.pens.recordingPen import DecomposingRecordingPen
 | |
| import logging
 | |
| 
 | |
| 
 | |
| log = logging.getLogger("fontTools.merge")
 | |
| 
 | |
| 
 | |
| def computeMegaGlyphOrder(merger, glyphOrders):
 | |
|     """Modifies passed-in glyphOrders to reflect new glyph names.
 | |
|     Stores merger.glyphOrder."""
 | |
|     megaOrder = {}
 | |
|     for glyphOrder in glyphOrders:
 | |
|         for i, glyphName in enumerate(glyphOrder):
 | |
|             if glyphName in megaOrder:
 | |
|                 n = megaOrder[glyphName]
 | |
|                 while (glyphName + "." + repr(n)) in megaOrder:
 | |
|                     n += 1
 | |
|                 megaOrder[glyphName] = n
 | |
|                 glyphName += "." + repr(n)
 | |
|                 glyphOrder[i] = glyphName
 | |
|             megaOrder[glyphName] = 1
 | |
|     merger.glyphOrder = megaOrder = list(megaOrder.keys())
 | |
| 
 | |
| 
 | |
| def _glyphsAreSame(
 | |
|     glyphSet1,
 | |
|     glyphSet2,
 | |
|     glyph1,
 | |
|     glyph2,
 | |
|     advanceTolerance=0.05,
 | |
|     advanceToleranceEmpty=0.20,
 | |
| ):
 | |
|     pen1 = DecomposingRecordingPen(glyphSet1)
 | |
|     pen2 = DecomposingRecordingPen(glyphSet2)
 | |
|     g1 = glyphSet1[glyph1]
 | |
|     g2 = glyphSet2[glyph2]
 | |
|     g1.draw(pen1)
 | |
|     g2.draw(pen2)
 | |
|     if pen1.value != pen2.value:
 | |
|         return False
 | |
|     # Allow more width tolerance for glyphs with no ink
 | |
|     tolerance = advanceTolerance if pen1.value else advanceToleranceEmpty
 | |
|     # TODO Warn if advances not the same but within tolerance.
 | |
|     if abs(g1.width - g2.width) > g1.width * tolerance:
 | |
|         return False
 | |
|     if hasattr(g1, "height") and g1.height is not None:
 | |
|         if abs(g1.height - g2.height) > g1.height * tolerance:
 | |
|             return False
 | |
|     return True
 | |
| 
 | |
| 
 | |
| def computeMegaUvs(merger, uvsTables):
 | |
|     """Returns merged UVS subtable (cmap format=14)."""
 | |
|     uvsDict = {}
 | |
|     cmap = merger.cmap
 | |
|     for table in uvsTables:
 | |
|         for variationSelector, uvsMapping in table.uvsDict.items():
 | |
|             if variationSelector not in uvsDict:
 | |
|                 uvsDict[variationSelector] = {}
 | |
|             for unicodeValue, glyphName in uvsMapping:
 | |
|                 if cmap.get(unicodeValue) == glyphName:
 | |
|                     # this is a default variation
 | |
|                     glyphName = None
 | |
|                     # prefer previous glyph id if both fonts defined UVS
 | |
|                 if unicodeValue not in uvsDict[variationSelector]:
 | |
|                     uvsDict[variationSelector][unicodeValue] = glyphName
 | |
| 
 | |
|     for variationSelector in uvsDict:
 | |
|         uvsDict[variationSelector] = [*uvsDict[variationSelector].items()]
 | |
| 
 | |
|     return uvsDict
 | |
| 
 | |
| 
 | |
| # Valid (format, platformID, platEncID) triplets for cmap subtables containing
 | |
| # Unicode BMP-only and Unicode Full Repertoire semantics.
 | |
| # Cf. OpenType spec for "Platform specific encodings":
 | |
| # https://docs.microsoft.com/en-us/typography/opentype/spec/name
 | |
| class _CmapUnicodePlatEncodings:
 | |
|     BMP = {(4, 3, 1), (4, 0, 3), (4, 0, 4), (4, 0, 6)}
 | |
|     FullRepertoire = {(12, 3, 10), (12, 0, 4), (12, 0, 6)}
 | |
|     UVS = {(14, 0, 5)}
 | |
| 
 | |
| 
 | |
| def computeMegaCmap(merger, cmapTables):
 | |
|     """Sets merger.cmap and merger.uvsDict."""
 | |
| 
 | |
|     # TODO Handle format=14.
 | |
|     # Only merge format 4 and 12 Unicode subtables, ignores all other subtables
 | |
|     # If there is a format 12 table for a font, ignore the format 4 table of it
 | |
|     chosenCmapTables = []
 | |
|     chosenUvsTables = []
 | |
|     for fontIdx, table in enumerate(cmapTables):
 | |
|         format4 = None
 | |
|         format12 = None
 | |
|         format14 = None
 | |
|         for subtable in table.tables:
 | |
|             properties = (subtable.format, subtable.platformID, subtable.platEncID)
 | |
|             if properties in _CmapUnicodePlatEncodings.BMP:
 | |
|                 format4 = subtable
 | |
|             elif properties in _CmapUnicodePlatEncodings.FullRepertoire:
 | |
|                 format12 = subtable
 | |
|             elif properties in _CmapUnicodePlatEncodings.UVS:
 | |
|                 format14 = subtable
 | |
|             else:
 | |
|                 log.warning(
 | |
|                     "Dropped cmap subtable from font '%s':\t"
 | |
|                     "format %2s, platformID %2s, platEncID %2s",
 | |
|                     fontIdx,
 | |
|                     subtable.format,
 | |
|                     subtable.platformID,
 | |
|                     subtable.platEncID,
 | |
|                 )
 | |
|         if format12 is not None:
 | |
|             chosenCmapTables.append((format12, fontIdx))
 | |
|         elif format4 is not None:
 | |
|             chosenCmapTables.append((format4, fontIdx))
 | |
| 
 | |
|         if format14 is not None:
 | |
|             chosenUvsTables.append(format14)
 | |
| 
 | |
|     # Build the unicode mapping
 | |
|     merger.cmap = cmap = {}
 | |
|     fontIndexForGlyph = {}
 | |
|     glyphSets = [None for f in merger.fonts] if hasattr(merger, "fonts") else None
 | |
| 
 | |
|     for table, fontIdx in chosenCmapTables:
 | |
|         # handle duplicates
 | |
|         for uni, gid in table.cmap.items():
 | |
|             oldgid = cmap.get(uni, None)
 | |
|             if oldgid is None:
 | |
|                 cmap[uni] = gid
 | |
|                 fontIndexForGlyph[gid] = fontIdx
 | |
|             elif is_Default_Ignorable(uni) or uni in (0x25CC,):  # U+25CC DOTTED CIRCLE
 | |
|                 continue
 | |
|             elif oldgid != gid:
 | |
|                 # Char previously mapped to oldgid, now to gid.
 | |
|                 # Record, to fix up in GSUB 'locl' later.
 | |
|                 if merger.duplicateGlyphsPerFont[fontIdx].get(oldgid) is None:
 | |
|                     if glyphSets is not None:
 | |
|                         oldFontIdx = fontIndexForGlyph[oldgid]
 | |
|                         for idx in (fontIdx, oldFontIdx):
 | |
|                             if glyphSets[idx] is None:
 | |
|                                 glyphSets[idx] = merger.fonts[idx].getGlyphSet()
 | |
|                         # if _glyphsAreSame(glyphSets[oldFontIdx], glyphSets[fontIdx], oldgid, gid):
 | |
|                         # 	continue
 | |
|                     merger.duplicateGlyphsPerFont[fontIdx][oldgid] = gid
 | |
|                 elif merger.duplicateGlyphsPerFont[fontIdx][oldgid] != gid:
 | |
|                     # Char previously mapped to oldgid but oldgid is already remapped to a different
 | |
|                     # gid, because of another Unicode character.
 | |
|                     # TODO: Try harder to do something about these.
 | |
|                     log.warning(
 | |
|                         "Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid
 | |
|                     )
 | |
| 
 | |
|     merger.uvsDict = computeMegaUvs(merger, chosenUvsTables)
 | |
| 
 | |
| 
 | |
| def renameCFFCharStrings(merger, glyphOrder, cffTable):
 | |
|     """Rename topDictIndex charStrings based on glyphOrder."""
 | |
|     td = cffTable.cff.topDictIndex[0]
 | |
| 
 | |
|     charStrings = {}
 | |
|     for i, v in enumerate(td.CharStrings.charStrings.values()):
 | |
|         glyphName = glyphOrder[i]
 | |
|         charStrings[glyphName] = v
 | |
|     td.CharStrings.charStrings = charStrings
 | |
| 
 | |
|     td.charset = list(glyphOrder)
 |