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_u1f199.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_u1f202.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
import functools
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
from fontTools import ttLib
from fontTools.ttLib.tables import _g_l_y_f as glyf
from fontTools.ttLib.tables import otTables as ot
import map_pua_emoji
from nototools import add_vs_cmap
from nototools import font_data
from nototools import unicode_data
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}")
def _map_missing_flag_tag_chars_to_empty_glyphs(colr_font):
# Add all tag characters used in flags
tag_cps = set(range(0xE0030, 0xE0039 + 1)) | set(range(0xE0061, 0xE007A + 1))
FLAG_TAGS = set(range(0xE0030, 0xE0039 + 1)) | set(range(0xE0061, 0xE007A + 1))
CANCEL_TAG = 0xE007F
# 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
tag_cps -= set(_Cmap(colr_font).keys())
@ -143,28 +146,6 @@ def _Cmap(ttfont):
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):
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
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):
if len(argv) != 3:
raise ValueError(
@ -233,12 +320,12 @@ def main(argv):
_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_vertical_layout_tables(cbdt_font, colr_font)
_add_fallback_subs_for_unknown_flags(colr_font)
out_file = Path(_OUTPUT_FILE[colr_file.name]).absolute()
print("Writing", out_file)
colr_font.save(out_file)