Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for dotted circle #3602

Merged
merged 5 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ A more detailed list of changes is available in the corresponding milestones for
- Include most of the `googlefonts` profile checks. (PR #3579)
- **[com.fontwerk/check/vendor_id]:** Vendor ID must for 'WERK' on FontWerk fonts. (PR #3579)
- **[com.fontwerk/check/weight_class_fvar]:** usWeightclass must match fvar default value. (PR #3579)
#### Added to the Universal Profile
- **[com.google.fonts/check/dotted_circle]:** Check dotted circle is present and correct (issue #3600)

### Changes to existing checks
#### On the Google Fonts Profile
Expand Down
75 changes: 74 additions & 1 deletion Lib/fontbakery/profiles/universal.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
'com.google.fonts/check/unreachable_glyphs',
'com.google.fonts/check/contour_count',
'com.google.fonts/check/cjk_chws_feature',
'com.google.fonts/check/transformed_components'
'com.google.fonts/check/transformed_components',
'com.google.fonts/check/dotted_circle'
]


Expand Down Expand Up @@ -1413,5 +1414,77 @@ def com_google_fonts_check_transformed_components(ttFont):
yield PASS, "No glyphs had components with scaling or rotation"


@check(
id = 'com.google.fonts/check/dotted_circle',
conditions = ['is_ttf'],
severity = 3,
rationale = """
The dotted circle character (U+25CC) is inserted by shaping engines before mark glyphs which do not have an associated base, especially in the context of broken syllabic clusters.

For fonts containing combining marks, it is recommended that the dotted circle character be included so that these isolated marks can be displayed properly; for fonts supporting complex scripts, this should be considered mandatory.

Additionally, when a dotted circle glyph is present, it should be able to display all marks correctly, meaning that it should contain anchors for all attaching marks.
""",
proposal = 'https://github.com/googlefonts/fontbakery/issues/3600',
)
def com_google_fonts_check_dotted_circle(ttFont, config):
"""Ensure dotted circle glyph is present and can attach marks."""
from fontbakery.utils import bullet_list, is_complex_shaper_font

mark_glyphs = []
if "GDEF" in ttFont and hasattr(ttFont["GDEF"].table, "GlyphClassDef"):
mark_glyphs = [k for k, v in ttFont["GDEF"].table.GlyphClassDef.classDefs.items() if v == 3]
# Only check for encoded
mark_glyphs = set(mark_glyphs) & set(ttFont.getBestCmap().values())
nonspacing_mark_glyphs = [g for g in mark_glyphs if ttFont["hmtx"][g][0] == 0]

if not nonspacing_mark_glyphs:
yield SKIP, "Font has no nonspacing mark glyphs."
return

if 0x25CC not in ttFont.getBestCmap():
# How bad is this?
if is_complex_shaper_font(ttFont):
yield FAIL, Message('missing-dotted-circle-complex',
"No dotted circle glyph present and font uses a complex shaper")
else:
yield WARN, Message('missing-dotted-circle',
"No dotted circle glyph present")
return

# Check they all attach to dotted circle if they attach to
# something else
dotted_circle = ttFont.getBestCmap()[0x25CC]
attachments = {dotted_circle: []}
does_attach = {}
if "GPOS" in ttFont and ttFont["GPOS"].table.LookupList:
def find_mark_base(lookup, attachments):
if lookup.LookupType == 9:
for xt in lookup.SubTable:
xt.SubTable = [xt.ExtSubTable]
xt.LookupType = xt.ExtSubTable.LookupType
find_mark_base(xt, attachments)
if lookup.LookupType == 4:
# Assume all-to-all
for st in lookup.SubTable:
for base in st.BaseCoverage.glyphs:
for mark in st.MarkCoverage.glyphs:
attachments.setdefault(base,[]).append(mark)
does_attach[mark] = True

for lookup in ttFont["GPOS"].table.LookupList.Lookup:
find_mark_base(lookup, attachments)

unattached = []
for g in nonspacing_mark_glyphs:
if g in does_attach and g not in attachments[dotted_circle]:
unattached.append(g)
if unattached:
yield FAIL, Message("unattached-dotted-circle-marks",
"The following glyphs could not be attached to the dotted circle glyph:\n"+ bullet_list(config, unattached))
else:
yield PASS, "All marks were anchored to dotted circle"


profile.auto_register(globals())
profile.test_expected_checks(UNIVERSAL_PROFILE_CHECKS, exclusive=True)
15 changes: 15 additions & 0 deletions Lib/fontbakery/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import sys

from fontTools.ttLib import TTFont
from fontTools.unicodedata import ot_tag_to_script
from typing import Text, Optional
from fontbakery.constants import NO_COLORS_THEME, DARK_THEME, LIGHT_THEME
from ufo2ft.constants import INDIC_SCRIPTS, USE_SCRIPTS
from vharfbuzz import Vharfbuzz


Expand Down Expand Up @@ -544,3 +546,16 @@ def _invertClassDef(a, font):
for right in class2[ix2]:
rules.append((left, right, c2.Value1, c2.Value2))
return rules


def is_complex_shaper_font(ttFont):
for table in ["GSUB", "GPOS"]:
if table not in ttFont:
continue
for rec in ttFont[table].table.ScriptList.ScriptRecord:
script = ot_tag_to_script(rec.ScriptTag)
if script in USE_SCRIPTS or script in INDIC_SCRIPTS:
return True
if script in ["Khmr", "Mymr", "Hang"]:
return True
return False
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ toml==0.10.2
ufolint==1.2.0
unicodedata2==14.0.0
vharfbuzz==0.1.1
ufo2ft==2.25.2
-e .
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
'stringbrewer',
'toml',
'ufolint',
'ufo2ft>=2.25.2', # 2.25.2 updated the script lists for Unicode 14.0
'unicodedata2',
'vharfbuzz',
],
Expand Down
18 changes: 18 additions & 0 deletions tests/profiles/universal_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -905,3 +905,21 @@ def test_check_transformed_components():
font = TEST_FILE("dm-sans-v1.100/DMSans-Regular.ttf")
assert_results_contain(check(font),
FAIL, 'transformed-components')


def test_check_dotted_circle():
"""Ensure dotted circle glyph is present and can attach marks."""
check = CheckTester(universal_profile,
"com.google.fonts/check/dotted_circle")

font = TEST_FILE("mada/Mada-Regular.ttf")
assert_PASS(check(font),
"with a good font...")

font = TEST_FILE("cabin/Cabin-Regular.ttf")
assert_results_contain(check(font),
"WARN", "missing-dotted-circle")

font = TEST_FILE("broken_markazitext/MarkaziText-VF.ttf")
assert_results_contain(check(font),
"FAIL", "unattached-dotted-circle-marks")