Skip to content

Commit

Permalink
Describe how to fix; show unique fails/fixes; report nearly-supported…
Browse files Browse the repository at this point in the history
… languages
  • Loading branch information
simoncozens committed Sep 2, 2024
1 parent 7ec280c commit d69513e
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 75 deletions.
32 changes: 25 additions & 7 deletions Lib/shaperglot/checks/no_orphaned_marks.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,18 @@ def execute(self, checker) -> None:
# good to hear specifically what glyphs we tried to test here.
if not checker.lang.get("exemplarChars"):
message += " when shaping " + self.input.describe()

checker.results.fail(
check_name="no-orphaned-marks",
result_code="notdef-produced",
message=message,
context={"text": self.input.check_yaml},
)
checker.results.fail(
check_name="no-orphaned-marks",
result_code="notdef-produced",
message=message,
context={"text": self.input.check_yaml},
fixes=[
{
"type": "add_codepoint",
"thing": self.input.text[info.cluster],
},
],
)
break
if _simple_mark_check(checker.codepoint_for(glyphname)):
# Was the previous glyph dotted circle?
Expand All @@ -65,6 +70,13 @@ def execute(self, checker) -> None:
message="Shaper produced a dotted circle when shaping "
+ self.input.describe(),
context={"text": self.input.check_yaml},
fixes=[
{
"type": "add_feature",
"thing": "to avoid a dotted circle shaping "
+ self.input.describe(),
},
],
)
elif pos.x_offset == 0 and pos.y_offset == 0: # Suspicious
passed = False
Expand All @@ -77,6 +89,12 @@ def execute(self, checker) -> None:
"mark": glyphname,
"base": previous,
},
fixes=[
{
"type": "add_anchor",
"thing": f"{previous}/{glyphname}",
},
],
)
previous = glyphname
if passed:
Expand Down
28 changes: 25 additions & 3 deletions Lib/shaperglot/checks/orthographies.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
from typing import Optional

from strictyaml import Map

Expand Down Expand Up @@ -33,10 +34,10 @@ def __init__(self, lang) -> None:
self.bases = set(bases) - self.marks
self.aux = set(aux) - self.marks

def should_skip(self, checker) -> bool:
return False
def should_skip(self, _checker) -> Optional[str]:
return

def describe(self):
def describe(self) -> str:
return "that the following glyphs are in the font: " + and_join(
f"'{g}'" for g in self.all_glyphs
)
Expand All @@ -56,6 +57,13 @@ def execute(self, checker) -> None:
result_code="bases-missing",
message=f"Some base glyphs were missing: {', '.join(missing)}",
context={"glyphs": missing},
fixes=[
{
"type": "add_codepoint",
"thing": g,
}
for g in missing
],
)
else:
checker.results.okay(
Expand All @@ -74,6 +82,13 @@ def execute(self, checker) -> None:
result_code="marks-missing",
message=f"Some mark glyphs were missing: {missing_str}",
context={"glyphs": missing},
fixes=[
{
"type": "add_codepoint",
"thing": g,
}
for g in missing
],
)
else:
checker.results.okay(
Expand All @@ -91,6 +106,13 @@ def execute(self, checker) -> None:
result_code="aux-missing",
message=f"Some auxiliary glyphs were missing: {', '.join(missing)}",
context={"glyphs": missing},
fixes=[
{
"type": "add_codepoint",
"thing": g,
}
for g in missing
],
)
else:
checker.results.okay(
Expand Down
29 changes: 23 additions & 6 deletions Lib/shaperglot/checks/shaping_differs.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,17 @@ def execute(self, checker) -> None:
# won't mean anything
reported_missing = set()
for result in checker.results:
if (
result.result_code == "bases-missing"
or result.result_code == "marks-missing"
):
if result.result_code in ["bases-missing", "marks-missing"]:
reported_missing.update(result.context["glyphs"])
for text in self.inputs:
missing_glyphs = [f"'{g}'" for g in text.text if g in reported_missing]
if missing_glyphs:
checker.results.skip(
check_name="shaping-differs",
message="Differs check could not run because some characters (%s) were missing from the font."
% ", ".join(missing_glyphs),
message=(
"Differs check could not run because some characters"
f" ({', '.join(missing_glyphs)}) were missing from the font."
),
)
return

Expand All @@ -77,6 +76,12 @@ def execute(self, checker) -> None:
"input1": self.inputs[0].check_yaml,
"input2": self.inputs[0].check_yaml,
},
fixes=[
{
"type": "add_feature",
"thing": "A rule so " + self.describe(),
}
],
)
return
# We are looking for a specific difference
Expand All @@ -95,6 +100,12 @@ def execute(self, checker) -> None:
"input1": self.inputs[0].check_yaml,
"input2": self.inputs[0].check_yaml,
},
fixes=[
{
"type": "add_feature",
"thing": "A rule such " + self.describe(),
}
],
)
return
glyphs.append((buffer[glyph_ix][0].codepoint, buffer[glyph_ix][1]))
Expand All @@ -115,4 +126,10 @@ def execute(self, checker) -> None:
"input1": self.inputs[0].check_yaml,
"input2": self.inputs[0].check_yaml,
},
fixes=[
{
"type": "add_feature",
"thing": "A rule such " + self.describe(),
}
],
)
40 changes: 36 additions & 4 deletions Lib/shaperglot/checks/unencoded_variants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,58 @@ class UnencodedVariantsCheck(ShaperglotCheck):
)

def describe(self) -> str:
return f"that, when {self.input.describe()}, an unencoded variant glyph is substituted used the `locl` feature"
return (
f"that, when {self.input.describe()}, an unencoded variant glyph "
"is substituted used the `locl` feature"
)

def execute(self, checker) -> None:
if len(self.input.text) > 1:
raise ValueError(
f"Please only pass one codepoint at a time to the unencoded variants check (not '{self.input.text}')"
"Please only pass one codepoint at a time to the unencoded "
f"variants check (not '{self.input.text}')"
)
self.input.features["locl"] = False
buffer = self.input.shape(checker)
if buffer.glyph_infos[0].codepoint == 0:
checker.results.fail(
check_name="unencoded-variants",
result_code="notdef-produced",
message="Shaper produced a .notdef",
context={"text": self.input.check_yaml},
fixes=[
{
"type": "add_codepoint",
"thing": self.input.text[buffer.glyph_infos[0].cluster],
},
],
)
return
glyphname = checker.glyphorder[buffer.glyph_infos[0].codepoint]
# Are there variant versions of this glyph?
variants = [
glyph for glyph in checker.glyphorder if glyph.startswith(glyphname + ".")
]

if not self.input.language:
self.input.language = checker.lang["language"]

if not variants:
checker.results.warn(
check_name="unencoded-variants",
result_code="no-variant",
message="No variant glyphs were found for " + glyphname,
context={"text": self.input.check_yaml, "glyph": glyphname},
fixes=[
{
"type": "add_glyph",
"thing": glyphname + ".locl" + self.input.language.upper(),
}
],
)
return
# Try it again with locl on, set the language to the one we're
# looking for see if something happens.
if not self.input.language:
self.input.language = checker.lang["language"]
self.input.features["locl"] = True
buffer2 = self.input.shape(checker)
glyphname2 = checker.glyphorder[buffer2.glyph_infos[0].codepoint]
Expand All @@ -45,6 +71,12 @@ def execute(self, checker) -> None:
result_code="unchanged-after-locl",
message=f"The locl feature did not affect {glyphname}",
context={"text": self.input.check_yaml, "glyph": glyphname},
fixes=[
{
"type": "add_feature",
"thing": f"a locale rule to substitute {glyphname} with a variant glyph",
}
],
)
else:
checker.results.okay(
Expand Down
12 changes: 12 additions & 0 deletions Lib/shaperglot/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ def main(args=None) -> None:

parser_check = subparsers.add_parser('check', help=check.__doc__)
parser_check.add_argument('--verbose', '-v', action='count')
parser_check.add_argument(
'--nearly',
type=int,
help="Number of fixes left to be considered nearly supported",
default=5,
)
parser_check.add_argument('font', metavar='FONT', help='the font file')
parser_check.add_argument(
'lang', metavar='LANG', help='one or more ISO639-3 language codes', nargs="+"
Expand All @@ -31,6 +37,12 @@ def main(args=None) -> None:
parser_report = subparsers.add_parser('report', help=report.__doc__)
parser_report.add_argument('font', metavar='FONT', help='the font file')
parser_report.add_argument('--verbose', '-v', action='count')
parser_report.add_argument(
'--nearly',
type=int,
help="Number of fixes left to be considered nearly supported",
default=5,
)
parser_report.add_argument('--csv', action='store_true', help="Output as CSV")
parser_report.add_argument(
'--group', action='store_true', help="Group by success/failure"
Expand Down
23 changes: 22 additions & 1 deletion Lib/shaperglot/cli/check.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from collections import defaultdict
from typing import Optional

from shaperglot.checker import Checker
from shaperglot.languages import Languages
from shaperglot.reporter import Reporter


def find_lang(lang, langs):
def find_lang(lang: str, langs: Languages) -> Optional[str]:
# Find the language in the languages list; could be by ID, by name, etc.
if lang in langs:
return lang
Expand All @@ -15,12 +19,14 @@ def find_lang(lang, langs):
or lang_info.get("autonym", "").lower() == lang.lower()
):
return lang_id
return None


def check(options) -> None:
"""Check a particular language or languages are supported"""
checker = Checker(options.font)
langs = Languages()
fixes_needed = defaultdict(set)
for orig_lang in options.lang:
lang = find_lang(orig_lang, langs)
if not lang:
Expand All @@ -31,6 +37,10 @@ def check(options) -> None:

if results.is_unknown:
print(f"Cannot determine whether font supports language '{lang}'")
elif results.is_nearly_success(options.nearly):
print(f"Font nearly supports language '{lang}'")
for fixtype, things in results.unique_fixes().items():
fixes_needed[fixtype].update(things)
elif results.is_success:
print(f"Font supports language '{lang}'")
else:
Expand All @@ -42,3 +52,14 @@ def check(options) -> None:
elif options.verbose or not results.is_success:
for message in results.fails:
print(f" * {message}")

if fixes_needed:
show_how_to_fix(fixes_needed)


def show_how_to_fix(fixes: Reporter):
print("\nTo add full support to nearly-supported languages:")
for category, fixes in fixes.items():
plural = "s" if len(fixes) > 1 else ""
print(f" * {category.replace("_", ' ').capitalize()}{plural}: ", end="")
print("; ".join(sorted(fixes)))
Loading

0 comments on commit d69513e

Please sign in to comment.