Merge pull request #378 from googlefonts/colrv1-handle-unknown-flags

Colrv1 handle unknown flags and don't include region indicators in noflags build
feblend-soft-light-flags
rsheeter 2022-02-03 19:16:10 -08:00 committed by GitHub
commit e667644ec5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 116 additions and 55 deletions

View File

@ -54,32 +54,6 @@ srcs = [
"../svg/emoji_u1f198.svg", "../svg/emoji_u1f198.svg",
"../svg/emoji_u1f199.svg", "../svg/emoji_u1f199.svg",
"../svg/emoji_u1f19a.svg", "../svg/emoji_u1f19a.svg",
"../svg/emoji_u1f1e6.svg",
"../svg/emoji_u1f1e7.svg",
"../svg/emoji_u1f1e8.svg",
"../svg/emoji_u1f1e9.svg",
"../svg/emoji_u1f1ea.svg",
"../svg/emoji_u1f1eb.svg",
"../svg/emoji_u1f1ec.svg",
"../svg/emoji_u1f1ed.svg",
"../svg/emoji_u1f1ee.svg",
"../svg/emoji_u1f1ef.svg",
"../svg/emoji_u1f1f0.svg",
"../svg/emoji_u1f1f1.svg",
"../svg/emoji_u1f1f2.svg",
"../svg/emoji_u1f1f3.svg",
"../svg/emoji_u1f1f4.svg",
"../svg/emoji_u1f1f5.svg",
"../svg/emoji_u1f1f6.svg",
"../svg/emoji_u1f1f7.svg",
"../svg/emoji_u1f1f8.svg",
"../svg/emoji_u1f1f9.svg",
"../svg/emoji_u1f1fa.svg",
"../svg/emoji_u1f1fb.svg",
"../svg/emoji_u1f1fc.svg",
"../svg/emoji_u1f1fd.svg",
"../svg/emoji_u1f1fe.svg",
"../svg/emoji_u1f1ff.svg",
"../svg/emoji_u1f201.svg", "../svg/emoji_u1f201.svg",
"../svg/emoji_u1f202.svg", "../svg/emoji_u1f202.svg",
"../svg/emoji_u1f21a.svg", "../svg/emoji_u1f21a.svg",

View File

@ -5,11 +5,13 @@ For now substantially based on copying from a correct bitmap build.
""" """
from absl import app from absl import app
import functools import functools
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
from fontTools import ttLib from fontTools import ttLib
from fontTools.ttLib.tables import _g_l_y_f as glyf from fontTools.ttLib.tables import _g_l_y_f as glyf
from fontTools.ttLib.tables import otTables as ot from fontTools.ttLib.tables import otTables as ot
import map_pua_emoji import map_pua_emoji
from nototools import add_vs_cmap from nototools import add_vs_cmap
from nototools import font_data
from nototools import unicode_data from nototools import unicode_data
from pathlib import Path from pathlib import Path
@ -95,12 +97,13 @@ def _add_cmap_entries(colr_font, codepoint, glyph_name):
print(f"Map 0x{codepoint:04x} to {glyph_name}, format {table.format}") print(f"Map 0x{codepoint:04x} to {glyph_name}, format {table.format}")
def _map_missing_flag_tag_chars_to_empty_glyphs(colr_font): FLAG_TAGS = set(range(0xE0030, 0xE0039 + 1)) | set(range(0xE0061, 0xE007A + 1))
# Add all tag characters used in flags CANCEL_TAG = 0xE007F
tag_cps = set(range(0xE0030, 0xE0039 + 1)) | set(range(0xE0061, 0xE007A + 1))
# Cancel tag
tag_cps |= {0xE007F} def _map_missing_flag_tag_chars_to_empty_glyphs(colr_font):
# Add all tag characters used in flags + cancel tag
tag_cps = FLAG_TAGS | {CANCEL_TAG}
# Anything already cmap'd is fine # Anything already cmap'd is fine
tag_cps -= set(_Cmap(colr_font).keys()) tag_cps -= set(_Cmap(colr_font).keys())
@ -143,28 +146,6 @@ def _Cmap(ttfont):
return functools.reduce(_Reducer, unicode_cmaps, {}) return functools.reduce(_Reducer, unicode_cmaps, {})
def _map_empty_flag_tag_to_black_flag(colr_font):
# fontchain_lint wants direct support for empty flag tags
# so map them to the default flag to match cbdt behavior
# if the emoji font starts using extensions this code will require revision
cmap = _Cmap(colr_font)
black_flag_glyph = cmap[0x1F3F4]
cancel_tag_glyph = cmap[0xE007F]
lookup_list = colr_font["GSUB"].table.LookupList
liga_set = _ligaset_for_glyph(lookup_list, black_flag_glyph)
assert liga_set is not None, "There should be existing ligatures using black flag"
# Map black flag + cancel tag to just black flag
# Since this is the ligature set for black flag, component is just cancel tag
# Since we only have one component its safe to put our rule at the front
liga = ot.Ligature()
liga.Component = [cancel_tag_glyph]
liga.LigGlyph = black_flag_glyph
liga_set.insert(0, liga)
def _add_vertical_layout_tables(cbdt_font, colr_font): def _add_vertical_layout_tables(cbdt_font, colr_font):
upem_scale = colr_font["head"].unitsPerEm / cbdt_font["head"].unitsPerEm upem_scale = colr_font["head"].unitsPerEm / cbdt_font["head"].unitsPerEm
@ -201,6 +182,112 @@ def _add_vertical_layout_tables(cbdt_font, colr_font):
vmtx.metrics[gn] = height, 0 vmtx.metrics[gn] = height, 0
UNKNOWN_FLAG_PUA = 0xFE82B
BLACK_FLAG = 0x1F3F4
REGIONAL_INDICATORS = set(range(0x1F1E6, 0x1F1FF + 1))
def _add_fallback_subs_for_unknown_flags(colr_font):
"""Add GSUB lookups to replace unsupported flag sequences with the 'unknown flag'.
In order to locate the unknown flag, the glyph must be mapped to 0xFE82B PUA code;
the latter is removed from the cmap table after the GSUB has been updated.
"""
cmap = _Cmap(colr_font)
unknown_flag = cmap[UNKNOWN_FLAG_PUA]
black_flag = cmap[BLACK_FLAG]
cancel_tag = cmap[CANCEL_TAG]
flag_tags = sorted(cmap[cp] for cp in FLAG_TAGS)
# in the *-noflags.ttf font there are no region flags thus this list is empty
regional_indicators = sorted(cmap[cp] for cp in REGIONAL_INDICATORS if cp in cmap)
classes = f'@FLAG_TAGS = [{" ".join(flag_tags)}];\n'
if regional_indicators:
classes += f"""
@REGIONAL_INDICATORS = [{" ".join(regional_indicators)}];
@UNKNOWN_FLAG = [{" ".join([unknown_flag] * len(regional_indicators))}];
"""
lookups = (
# the first lookup is a dummy that stands for the emoji sequences ligatures
# from the destination font; we only use it to ensure the lookup indices match.
# We can't leave it empty otherwise feaLib optimizes it away.
f"""
lookup placeholder {{
sub {unknown_flag} {unknown_flag} by {unknown_flag};
}} placeholder;
"""
+ "\n".join(
["lookup delete_glyph {"]
+ [f" sub {g} by NULL;" for g in sorted(regional_indicators + flag_tags)]
+ ["} delete_glyph;"]
)
+ (
"""
lookup replace_with_unknown_flag {
sub @REGIONAL_INDICATORS by @UNKNOWN_FLAG;
} replace_with_unknown_flag;
"""
if regional_indicators
else "\n"
)
)
features = (
"languagesystem DFLT dflt;\n"
+ classes
+ lookups
+ "feature ccmp {"
+ f"""
lookup placeholder;
sub {black_flag} @FLAG_TAGS' lookup delete_glyph;
sub {black_flag} {cancel_tag} by {unknown_flag};
"""
+ (
"""
sub @REGIONAL_INDICATORS' lookup replace_with_unknown_flag
@REGIONAL_INDICATORS' lookup delete_glyph;
"""
if regional_indicators
else ""
)
+ "} ccmp;"
)
# feaLib always builds a new GSUB table (can't update one in place) so we have to
# use an empty TTFont and then update our GSUB with the newly built lookups
temp_font = ttLib.TTFont()
temp_font.setGlyphOrder(colr_font.getGlyphOrder())
addOpenTypeFeaturesFromString(temp_font, features)
temp_gsub = temp_font["GSUB"].table
# sanity check
assert len(temp_gsub.FeatureList.FeatureRecord) == 1
assert temp_gsub.FeatureList.FeatureRecord[0].FeatureTag == "ccmp"
temp_ccmp = temp_gsub.FeatureList.FeatureRecord[0].Feature
colr_gsub = colr_font["GSUB"].table
ccmps = [
r.Feature for r in colr_gsub.FeatureList.FeatureRecord if r.FeatureTag == "ccmp"
]
assert len(ccmps) == 1, f"expected only 1 'ccmp' feature record, found {len(ccmps)}"
colr_ccmp = ccmps[0]
colr_lookups = colr_gsub.LookupList.Lookup
assert (
len(colr_lookups) == 1
), f"expected only 1 lookup in COLRv1's GSUB.LookupList, found {len(colr_lookups)}"
assert (
colr_lookups[0].LookupType == 4
), f"expected Lookup[0] of type 4 in COLRv1, found {colr_lookups[0].LookupType}"
colr_lookups.extend(temp_gsub.LookupList.Lookup[1:])
colr_gsub.LookupList.LookupCount = len(colr_lookups)
colr_ccmp.LookupListIndex = temp_ccmp.LookupListIndex
colr_ccmp.LookupCount = len(colr_ccmp.LookupListIndex)
# get rid of the Unknown Flag private codepoint as no longer needed
font_data.delete_from_cmap(colr_font, [UNKNOWN_FLAG_PUA])
def main(argv): def main(argv):
if len(argv) != 3: if len(argv) != 3:
raise ValueError( raise ValueError(
@ -233,12 +320,12 @@ def main(argv):
_map_missing_flag_tag_chars_to_empty_glyphs(colr_font) _map_missing_flag_tag_chars_to_empty_glyphs(colr_font)
_map_empty_flag_tag_to_black_flag(colr_font)
add_soft_light_to_flags(colr_font) add_soft_light_to_flags(colr_font)
_add_vertical_layout_tables(cbdt_font, colr_font) _add_vertical_layout_tables(cbdt_font, colr_font)
_add_fallback_subs_for_unknown_flags(colr_font)
out_file = Path(_OUTPUT_FILE[colr_file.name]).absolute() out_file = Path(_OUTPUT_FILE[colr_file.name]).absolute()
print("Writing", out_file) print("Writing", out_file)
colr_font.save(out_file) colr_font.save(out_file)