Spaces:
Running
Running
# 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 | |
# 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)} | |
def computeMegaCmap(merger, cmapTables): | |
"""Sets merger.cmap and merger.glyphOrder.""" | |
# 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 = [] | |
for fontIdx, table in enumerate(cmapTables): | |
format4 = None | |
format12 = 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 | |
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)) | |
# 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 | |
) | |
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) | |