diff --git a/shaperglot-cli/src/check.rs b/shaperglot-cli/src/check.rs
index 24efa22..73eef31 100644
--- a/shaperglot-cli/src/check.rs
+++ b/shaperglot-cli/src/check.rs
@@ -37,7 +37,7 @@ pub fn check_command(args: &CheckArgs, language_database: shaperglot::Languages)
println!("{}", serde_json::to_string(&results).unwrap());
continue;
}
- println!("{}", results.to_summary_string(args.nearly, language));
+ println!("{}", results.to_summary_string(language));
show_result(&results, args.verbose);
if args.fix {
for (category, fixes) in results.unique_fixes() {
diff --git a/shaperglot-cli/src/report.rs b/shaperglot-cli/src/report.rs
index 08292e9..3f16908 100644
--- a/shaperglot-cli/src/report.rs
+++ b/shaperglot-cli/src/report.rs
@@ -39,6 +39,6 @@ pub fn report_command(args: &ReportArgs, language_database: shaperglot::Language
if results.is_unknown() {
continue;
}
- println!("{}", results.to_summary_string(args.nearly, language));
+ println!("{}", results.to_summary_string(language));
}
}
diff --git a/shaperglot-lib/src/checker.rs b/shaperglot-lib/src/checker.rs
index e3af781..8686fb8 100644
--- a/shaperglot-lib/src/checker.rs
+++ b/shaperglot-lib/src/checker.rs
@@ -4,6 +4,7 @@ use crate::{
font::{feature_tags, glyph_names},
language::Language,
reporter::Reporter,
+ ResultCode,
};
use rustybuzz::Face;
use skrifa::{raw::ReadError, FontRef, GlyphId, MetadataProvider};
@@ -52,7 +53,11 @@ impl<'a> Checker<'a> {
// let toml = toml::to_string(&check_object).unwrap();
// println!("Running check:\n{}", toml);
let checkresult = check_object.execute(self);
+ let status = checkresult.status;
results.add(checkresult);
+ if status == ResultCode::StopNow {
+ break;
+ }
}
results
}
diff --git a/shaperglot-lib/src/checks/codepoint_coverage.rs b/shaperglot-lib/src/checks/codepoint_coverage.rs
index 9aa7dac..83a77e9 100644
--- a/shaperglot-lib/src/checks/codepoint_coverage.rs
+++ b/shaperglot-lib/src/checks/codepoint_coverage.rs
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashSet;
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct CodepointCoverage {
strings: HashSet,
code: String,
@@ -50,6 +50,9 @@ impl CheckImplementation for CodepointCoverage {
missing_things.join(", ")
),
);
+ if missing_things.len() == self.strings.len() {
+ fail.terminal = true;
+ }
fail.context = json!({"glyphs": missing_things});
fail.fixes.extend(missing_things.iter().map(|x| Fix {
fix_type: "add_codepoint".to_string(),
diff --git a/shaperglot-lib/src/checks/mod.rs b/shaperglot-lib/src/checks/mod.rs
index 26620e5..cb74536 100644
--- a/shaperglot-lib/src/checks/mod.rs
+++ b/shaperglot-lib/src/checks/mod.rs
@@ -21,13 +21,13 @@ pub trait CheckImplementation {
fn execute(&self, checker: &Checker) -> (Vec, usize);
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum ScoringStrategy {
Continuous,
AllOrNothing,
}
-#[derive(Delegate, Serialize, Deserialize)]
+#[derive(Delegate, Serialize, Deserialize, Debug)]
#[delegate(CheckImplementation)]
#[serde(tag = "type")]
pub enum CheckType {
@@ -36,7 +36,7 @@ pub enum CheckType {
ShapingDiffers(ShapingDiffers),
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct Check {
pub name: String,
pub severity: ResultCode,
@@ -103,6 +103,10 @@ impl Check {
ResultCode::Skip
} else if problems.is_empty() {
ResultCode::Pass
+ } else if self.scoring_strategy == ScoringStrategy::AllOrNothing
+ && problems.iter().any(|p| p.terminal)
+ {
+ ResultCode::StopNow
} else {
self.severity
},
diff --git a/shaperglot-lib/src/checks/no_orphaned_marks.rs b/shaperglot-lib/src/checks/no_orphaned_marks.rs
index 721b054..c441b61 100644
--- a/shaperglot-lib/src/checks/no_orphaned_marks.rs
+++ b/shaperglot-lib/src/checks/no_orphaned_marks.rs
@@ -8,7 +8,7 @@ use itertools::Itertools;
use serde::{Deserialize, Serialize};
use unicode_properties::{GeneralCategory, UnicodeGeneralCategory};
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct NoOrphanedMarks {
test_strings: Vec,
has_orthography: bool,
@@ -67,6 +67,7 @@ impl CheckImplementation for NoOrphanedMarks {
check_name: self.name(),
message: format!("Shaper produced a dotted circle when {}", string),
code: "dotted-circle-produced".to_string(),
+ terminal: false,
context: serde_json::json!({
"text": previous,
"mark": codepoint.glyph_id,
@@ -96,6 +97,7 @@ impl CheckImplementation for NoOrphanedMarks {
.unwrap_or_else(|| format!("Glyph #{}", codepoint.glyph_id));
let fail = Problem {
check_name: self.name(),
+ terminal: false,
message: format!(
"Shaper didn't attach {} to {} when {}",
this_name, previous_name, string
diff --git a/shaperglot-lib/src/checks/shaping_differs.rs b/shaperglot-lib/src/checks/shaping_differs.rs
index 1212825..441ffd1 100644
--- a/shaperglot-lib/src/checks/shaping_differs.rs
+++ b/shaperglot-lib/src/checks/shaping_differs.rs
@@ -10,7 +10,7 @@ use itertools::Itertools;
use rustybuzz::SerializeFlags;
use serde::{Deserialize, Serialize};
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct ShapingDiffers {
pairs: Vec<(ShapingInput, ShapingInput)>,
features_optional: bool,
diff --git a/shaperglot-lib/src/lib.rs b/shaperglot-lib/src/lib.rs
index d7fc534..d9fa574 100644
--- a/shaperglot-lib/src/lib.rs
+++ b/shaperglot-lib/src/lib.rs
@@ -10,5 +10,5 @@ pub use crate::{
checker::Checker,
language::Languages,
providers::Provider,
- reporter::{Reporter, ResultCode},
+ reporter::{Reporter, ResultCode, SupportLevel},
};
diff --git a/shaperglot-lib/src/providers/positional.rs b/shaperglot-lib/src/providers/positional.rs
index 91717d6..75ccf4f 100644
--- a/shaperglot-lib/src/providers/positional.rs
+++ b/shaperglot-lib/src/providers/positional.rs
@@ -19,6 +19,9 @@ pub struct PositionalProvider;
impl Provider for PositionalProvider {
fn checks_for(&self, language: &Language) -> Vec {
+ if language.script() != "Arab" {
+ return vec![];
+ }
// let marks = language
// .marks
// .iter()
diff --git a/shaperglot-lib/src/reporter.rs b/shaperglot-lib/src/reporter.rs
index 6f17fea..6219f8e 100644
--- a/shaperglot-lib/src/reporter.rs
+++ b/shaperglot-lib/src/reporter.rs
@@ -18,6 +18,7 @@ pub enum ResultCode {
Warn,
Fail,
Skip,
+ StopNow,
}
impl Display for ResultCode {
@@ -28,6 +29,7 @@ impl Display for ResultCode {
ResultCode::Warn => "WARN".yellow(),
ResultCode::Fail => "FAIL".red(),
ResultCode::Skip => "SKIP".blue(),
+ ResultCode::StopNow => "STOP".red(),
};
#[cfg(not(feature = "colored"))]
let to_string = match self {
@@ -35,6 +37,7 @@ impl Display for ResultCode {
ResultCode::Warn => "WARN",
ResultCode::Fail => "FAIL",
ResultCode::Skip => "SKIP",
+ ResultCode::StopNow => "STOP",
};
write!(f, "{}", to_string)
}
@@ -51,6 +54,7 @@ pub struct Problem {
pub check_name: String,
pub message: String,
pub code: String,
+ pub terminal: bool,
#[serde(skip_serializing_if = "Value::is_null")]
pub context: Value,
#[serde(skip_serializing_if = "Vec::is_empty")]
@@ -164,6 +168,25 @@ impl Reporter {
total_score / f32::from(total_weight) * 100.0
}
+ pub fn support_level(&self) -> SupportLevel {
+ if self.0.iter().any(|r| r.status == ResultCode::StopNow) {
+ return SupportLevel::None;
+ }
+ if self.is_unknown() {
+ return SupportLevel::Indeterminate;
+ }
+ if self.is_success() {
+ return SupportLevel::Complete;
+ }
+ if self.0.iter().any(|r| r.status == ResultCode::Fail) {
+ return SupportLevel::Unsupported;
+ }
+ if self.0.iter().any(|r| r.status == ResultCode::Warn) {
+ return SupportLevel::Incomplete;
+ }
+ SupportLevel::Supported
+ }
+
pub fn is_success(&self) -> bool {
self.0.iter().all(|r| r.problems.is_empty())
}
@@ -171,31 +194,67 @@ impl Reporter {
self.0.iter().map(|r| r.total_checks).sum::() == 0
}
+ pub fn fixes_required(&self) -> usize {
+ self.unique_fixes().values().map(|v| v.len()).sum::()
+ }
+
pub fn is_nearly_success(&self, nearly: usize) -> bool {
- self.unique_fixes().values().map(|v| v.len()).sum::() <= nearly
+ self.fixes_required() <= nearly
}
- pub fn to_summary_string(&self, nearly: usize, language: &Language) -> String {
- let score = if self.is_unknown() {
- ""
- } else {
- &format!(": {:.0}%", self.score())
- };
- let status = if self.is_unknown() {
- "Cannot determine whether font supports "
- } else if self.is_success() {
- "Font supports "
- } else if self.is_nearly_success(nearly) {
- "Font nearly supports "
- } else {
- "Font does not fully support "
- };
- format!(
- "{}language {} ({}){}",
- status,
- language.id(),
- language.name(),
- score
- )
+ pub fn to_summary_string(&self, language: &Language) -> String {
+ match self.support_level() {
+ SupportLevel::Complete => {
+ format!(
+ "Font has complete support for {} ({}): 100%",
+ language.id(),
+ language.name()
+ )
+ }
+ SupportLevel::Supported => format!(
+ "Font fully supports {} ({}): {:.0}%",
+ language.id(),
+ language.name(),
+ self.score()
+ ),
+ SupportLevel::Incomplete => format!(
+ "Font partially supports {} ({}): {:.0}% ({} fixes required)",
+ language.id(),
+ language.name(),
+ self.score(),
+ self.fixes_required()
+ ),
+ SupportLevel::Unsupported => format!(
+ "Font does not support {} ({}): {:.0}% ({} fixes required)",
+ language.id(),
+ language.name(),
+ self.score(),
+ self.fixes_required()
+ ),
+ SupportLevel::None => {
+ format!(
+ "Font does not attempt to support {} ({})",
+ language.id(),
+ language.name()
+ )
+ }
+ SupportLevel::Indeterminate => {
+ format!(
+ "Cannot determine whether font supports {} ({})",
+ language.id(),
+ language.name()
+ )
+ }
+ }
}
}
+
+#[derive(Debug, Serialize, PartialEq)]
+pub enum SupportLevel {
+ Complete, // Nothing can be improved.
+ Supported, // No FAILs or WARNS, but some optional SKIPs
+ Incomplete, // No FAILs
+ Unsupported, // There were FAILs
+ None, // Didn't even try
+ Indeterminate, // No checks
+}
diff --git a/shaperglot-web/src/lib.rs b/shaperglot-web/src/lib.rs
index a96d90a..5679d4a 100644
--- a/shaperglot-web/src/lib.rs
+++ b/shaperglot-web/src/lib.rs
@@ -47,13 +47,7 @@ pub fn check_font(font_data: &[u8]) -> Result {
}
results.push((
serde_json::to_value(&language.proto).map_err(|e| e.to_string())?,
- if result.is_success() {
- "supported"
- } else if result.is_nearly_success(5) {
- "nearly-supported"
- } else {
- "unsupported"
- },
+ serde_json::to_value(result.support_level()).map_err(|e| e.to_string())?,
serde_json::to_value(&result).map_err(|e| e.to_string())?,
));
}
diff --git a/shaperglot-web/www/index.js b/shaperglot-web/www/index.js
index f5bc9bf..a578dae 100644
--- a/shaperglot-web/www/index.js
+++ b/shaperglot-web/www/index.js
@@ -4,6 +4,14 @@ const fix_descriptions = {
add_codepoint: "Add the following codepoints to the font",
add_feature: "Add the following features to the font",
};
+const STATUS_INT = {
+ "Complete": 5,
+ "Supported": 4,
+ "Incomplete": 3,
+ "Unsupported": 2,
+ "None": 1,
+ "Indeterminate": 0,
+};
function commify(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
@@ -76,7 +84,7 @@ class Shaperglot {
issues_by_script[language.script] =
issues_by_script[language.script] || [];
issues_by_script[language.script].push([language, result, problems]);
- if (result === "supported") {
+ if (result === "Supported" || result === "Complete" || result === "Incomplete") {
count_supported_by_script[language.script] =
(count_supported_by_script[language.script] || 0) + 1;
}
@@ -121,7 +129,7 @@ class Shaperglot {
}
for (let [language, result, problems] of languages.sort(
- (a, b) => a[2].score - b[2].score || a[0].name.localeCompare(b[0].name)
+ (a, b) => STATUS_INT[a[1]] - STATUS_INT[b[1]] || a[0].name.localeCompare(b[0].name)
)) {
var thispill = $(`
`);
} else {
dd.append(``);
}
@@ -221,7 +248,7 @@ class Shaperglot {
let { check_name, message, fixes } = problem;
dd.find("ul").append(`${message}`);
- for (var fix of fixes) {
+ for (var fix of fixes || []) {
let { fix_type, fix_thing } = fix;
fixes_needed[fix_type] = fixes_needed[fix_type] || [];
fixes_needed[fix_type].push(fix_thing);
@@ -240,7 +267,6 @@ class Shaperglot {
}
result.append(problem_html);
- // result.append(`${JSON.stringify(problemSet)}
`);
// result.append(`${JSON.stringify(language)}
`);
}
diff --git a/shaperglot-web/www/style.css b/shaperglot-web/www/style.css
index 4f2bc1a..868238f 100644
--- a/shaperglot-web/www/style.css
+++ b/shaperglot-web/www/style.css
@@ -54,18 +54,26 @@ h4 {
outline: 5px dashed #ffffffff;
}
-.status-supported {
+.status-Complete {
background-color: #ddffdd !important;
}
-.status-nearly-supported {
- background-color: #ffffdd !important;
+.status-Supported {
+ background-color: #a8f4a9 !important;
}
-.status-unsupported {
+.status-Incomplete {
+ background-color: #f1ebae !important;
+}
+
+.status-Unsupported {
background-color: #ffdddd !important;
}
+.status-None {
+ background-color: #a8a8a8 !important;
+}
+
.flex-scroll {
height: 100vh;
overflow-y: auto;