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