File: colrv1_add_soft_light_to_flags.py

package info (click to toggle)
fonts-noto-color-emoji 2.042-0%2Bdeb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 272,720 kB
  • sloc: python: 4,010; ansic: 356; makefile: 177; sh: 38
file content (188 lines) | stat: -rw-r--r-- 5,518 bytes parent folder | download | duplicates (4)
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
"""Utility to add soft-light effect to NotoColorEmoji-COLRv1 region flags."""
import sys
import subprocess
from fontTools import ttLib
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables.C_P_A_L_ import Color
from fontTools.colorLib.builder import LayerListBuilder
from add_aliases import read_default_emoji_aliases
from flag_glyph_name import flag_code_to_glyph_name
from map_pua_emoji import get_glyph_name_from_gsub


REGIONAL_INDICATOR_RANGE = range(0x1F1E6, 0x1F1FF + 1)
BLACK_FLAG = 0x1F3F4
CANCEL_TAG = 0xE007F
TAG_RANGE = range(0xE0000, CANCEL_TAG + 1)


def is_flag(sequence):
    # regular region flags are comprised of regional indicators
    if all(cp in REGIONAL_INDICATOR_RANGE for cp in sequence):
        return True

    # subdivision flags start with  black flag, contain some tag characters and end with
    # a cancel tag
    if (
        len(sequence) > 2
        and sequence[0] == BLACK_FLAG
        and sequence[-1] == CANCEL_TAG
        and all(cp in TAG_RANGE for cp in sequence[1:-1])
    ):
        return True

    return False


def read_makefile_variable(var_name):
    # see `print-%` command in Makefile
    value = subprocess.run(
        ["make", f"print-{var_name}"], capture_output=True, check=True
    ).stdout.decode("utf-8")
    return value[len(var_name) + len(" = ") :].strip()


def flag_code_to_sequence(flag_code):
    # I use the existing code to first convert from flag code to glyph name,
    # and then convert names back to integer codepoints since it already
    # handles both the region indicators and subdivision tags.
    name = flag_code_to_glyph_name(flag_code)
    assert name.startswith("u")
    return tuple(int(v, 16) for v in name[1:].split("_"))


def all_flag_sequences():
    """Return the set of all noto-emoji's region and subdivision flag sequences.
    These include those in SELECTED_FLAGS Makefile variable plus those listed
    in the 'emoji_aliases.txt' file.
    """
    result = {
        flag_code_to_sequence(flag_code)
        for flag_code in read_makefile_variable("SELECTED_FLAGS").split()
    }
    result.update(seq for seq in read_default_emoji_aliases() if is_flag(seq))
    return result


_builder = LayerListBuilder()


def _build_paint(source):
    return _builder.buildPaint(source)


def _paint_composite(source, mode, backdrop):
    return _build_paint(
        {
            "Format": ot.PaintFormat.PaintComposite,
            "SourcePaint": source,
            "CompositeMode": mode,
            "BackdropPaint": backdrop,
        }
    )


def _palette_index(cpal, color):
    assert len(cpal.palettes) == 1
    palette = cpal.palettes[0]
    try:
        i = palette.index(color)
    except ValueError:
        i = len(palette)
        palette.append(color)
        cpal.numPaletteEntries += 1
        assert len(palette) == cpal.numPaletteEntries
    return i


WHITE = Color.fromHex("#FFFFFFFF")
GRAY = Color.fromHex("#808080FF")
BLACK = Color.fromHex("#000000FF")


def _soft_light_gradient(cpal):
    return _build_paint(
        {
            "Format": ot.PaintFormat.PaintLinearGradient,
            "ColorLine": {
                "Extend": "pad",
                "ColorStop": [
                    {
                        "StopOffset": 0.0,
                        "PaletteIndex": _palette_index(cpal, WHITE),
                        "Alpha": 0.5,
                    },
                    {
                        "StopOffset": 0.5,
                        "PaletteIndex": _palette_index(cpal, GRAY),
                        "Alpha": 0.5,
                    },
                    {
                        "StopOffset": 1.0,
                        "PaletteIndex": _palette_index(cpal, BLACK),
                        "Alpha": 0.5,
                    },
                ],
            },
            "x0": 47,
            "y0": 790,
            "x1": 890,
            "y1": -342,
            "x2": -1085,
            "y2": -53,
        },
    )


def flag_ligature_glyphs(font):
    """Yield ligature glyph names for all the region/subdivision flags in the font."""
    for flag_sequence in all_flag_sequences():
        flag_name = get_glyph_name_from_gsub(flag_sequence, font)
        if flag_name is not None:
            yield flag_name


def add_soft_light_to_flags(font, flag_glyph_names=None):
    """Add soft-light effect to region and subdivision flags in CORLv1 font."""
    if flag_glyph_names is None:
        flag_glyph_names = flag_ligature_glyphs(font)

    colr_glyphs = {
        rec.BaseGlyph: rec
        for rec in font["COLR"].table.BaseGlyphList.BaseGlyphPaintRecord
    }
    cpal = font["CPAL"]

    for flag_name in flag_glyph_names:
        flag = colr_glyphs[flag_name]
        flag.Paint = _paint_composite(
            source=_paint_composite(
                source=_soft_light_gradient(cpal),
                mode=ot.CompositeMode.SRC_IN,
                backdrop=flag.Paint,
            ),
            mode=ot.CompositeMode.SOFT_LIGHT,
            backdrop=flag.Paint,
        )


def main():
    try:
        input_file, output_file = sys.argv[1:]
    except ValueError:
        print("usage: colrv1_add_soft_light_to_flags.py INPUT_FONT OUTPUT_FONT")
        return 2

    font = ttLib.TTFont(input_file)

    if "COLR" not in font or font["COLR"].version != 1:
        print("error: missing required COLRv1 table")
        return 1

    add_soft_light_to_flags(font)

    font.save(output_file)


if __name__ == "__main__":
    sys.exit(main())