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())
|