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