aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/fonttools/fontTools/merge/cmap.py
blob: 3209a5d7b82c7ff0776dcae55e92c3cf816553a7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# 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)