From 564e18c8deb779f47474411927e6bdbe3427500b Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Thu, 14 Nov 2024 18:06:24 +0000 Subject: [PATCH] =?UTF-8?q?New=20=E2=80=9Cbase=20has=20width=E2=80=9D=20ch?= =?UTF-8?q?eck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/checks/base_has_width.rs | 68 +++++++++++++++++++ profile-universal/src/lib.rs | 1 + 2 files changed, 69 insertions(+) create mode 100644 profile-universal/src/checks/base_has_width.rs diff --git a/profile-universal/src/checks/base_has_width.rs b/profile-universal/src/checks/base_has_width.rs new file mode 100644 index 0000000..4386de3 --- /dev/null +++ b/profile-universal/src/checks/base_has_width.rs @@ -0,0 +1,68 @@ +use std::collections::HashMap; + +use fontspector_checkapi::{constants::GlyphClass, prelude::*, testfont, FileTypeConvert}; +use read_fonts::TableProvider; +use skrifa::{GlyphId, MetadataProvider}; +use unicode_properties::{GeneralCategory, UnicodeGeneralCategory}; + +fn is_space(c: char) -> bool { + matches!( + c.general_category(), + GeneralCategory::SpaceSeparator + | GeneralCategory::LineSeparator + | GeneralCategory::ParagraphSeparator + | GeneralCategory::Format + | GeneralCategory::NonspacingMark + | GeneralCategory::Control + ) +} + +#[check( + id = "base_has_width", + rationale = "Base characters should have non-zero advance width.", + proposal = "Rod on chat", + title = "Check base characters have non-zero advance width." +)] +fn base_has_width(f: &Testable, context: &Context) -> CheckFnResult { + let font = testfont!(f); + let hmtx = font.font().hmtx()?; + let mut problems = vec![]; + let reverse_charmap: HashMap<_, _> = font + .font() + .charmap() + .mappings() + .map(|(c, g)| (g, c)) + .collect(); + for (gid, metric) in hmtx.h_metrics().iter().enumerate() { + let gid = GlyphId::new(gid as u16); + if metric.advance() == 0 && font.gdef_class(gid) != Some(GlyphClass::Mark) { + let codepoint = reverse_charmap.get(&gid); + if codepoint == Some(&0) || codepoint.is_none() { + continue; + } + if codepoint + .and_then(|c| char::from_u32(*c)) + .map_or(false, is_space) + { + continue; + } + #[allow(clippy::unwrap_used)] + let name = font.glyph_name_for_id(gid, true).unwrap(); + if name == "NULL" { + continue; + } + problems.push(format!("{} ({:?})", name, codepoint)); + } + } + if problems.is_empty() { + Ok(Status::just_one_pass()) + } else { + Ok(Status::just_one_fail( + "zero-width-bases", + &format!( + "The following glyphs had zero advance width:\n{}", + bullet_list(context, problems), + ), + )) + } +} diff --git a/profile-universal/src/lib.rs b/profile-universal/src/lib.rs index 47b94ae..144c530 100644 --- a/profile-universal/src/lib.rs +++ b/profile-universal/src/lib.rs @@ -7,6 +7,7 @@ pub struct Universal; impl fontspector_checkapi::Plugin for Universal { fn register(&self, cr: &mut Registry) -> Result<(), String> { cr.register_check(checks::arabic_spacing_symbols::arabic_spacing_symbols); + cr.register_check(checks::base_has_width::base_has_width); cr.register_check(checks::case_mapping::case_mapping); cr.register_check(checks::cmap_format_12::cmap_format_12); cr.register_check(checks::colorfont_tables::colorfont_tables);