Merge pull request #3251 from MarekSpodBiedry/issue-3229

Warn on export when a font style is drawn in another style (#3229)
This commit is contained in:
BFlat
2026-10-21 15:41:57 +07:00
committed by GitHub
+35 -10
View File
@@ -301,7 +301,8 @@ fn drawn_runs(t: &vectorcraft_doc::TextObject) -> (Vec<&TextRun>, bool) {
}
/// What an export that draws type (as outlines, embedded fonts or pixels) says when `d` uses font
/// families that aren't available: their type is drawn in the fallback font, not the font it names.
/// families that aren't available (their type is drawn in the fallback font, not the font it
/// names) or styles that its family doesn't have (the closest style is drawn).
pub(super) fn substitution_warning(d: &Document) -> Option<String> {
substitution_warning_of(used_fonts(d))
}
@@ -309,20 +310,34 @@ pub(super) fn substitution_warning(d: &Document) -> Option<String> {
/// [`substitution_warning`] for type set in `fonts`.
pub(super) fn substitution_warning_of(fonts: BTreeSet<UsedFont>) -> Option<String> {
let db = vectorcraft_text::FontDb::global();
let mut missing: Vec<String> = fonts
.into_iter()
.filter(|(family, style, _)| db.resolve(family, style).is_none_or(|(_, m)| m == vectorcraft_text::FontMatch::Missing))
.map(|(family, _, _)| family)
.collect();
let mut missing: Vec<String> = Vec::new();
let mut styles: BTreeSet<String> = BTreeSet::new();
for (family, style, _) in fonts {
match db.resolve(&family, &style) {
None | Some((_, vectorcraft_text::FontMatch::Missing)) => missing.push(family),
Some((face, vectorcraft_text::FontMatch::Style)) => {
styles.insert(format!("{family} {style} was drawn in {} {}", face.family, face.style));
}
Some(_) => {}
}
}
// Sorted by family: its styles and versions are neighbours.
missing.dedup();
(!missing.is_empty()).then(|| {
format!(
let mut parts = Vec::new();
if !missing.is_empty() {
parts.push(format!(
"fonts that aren't available were written in the fallback font, {}: {} (install them, or replace them with Find Font)",
vectorcraft_text::FALLBACK_FAMILY,
missing.join(", ")
)
})
));
}
if !styles.is_empty() {
parts.push(format!(
"font styles that aren't available were written in the closest style: {} (pick a style the family has)",
styles.into_iter().collect::<Vec<_>>().join(", ")
));
}
(!parts.is_empty()).then(|| parts.join("; "))
}
fn text(d: &Document, id: NodeId) -> Option<&vectorcraft_doc::TextObject> {
@@ -585,6 +600,16 @@ mod open_tests {
assert_eq!(inter["missingGlyphs"], 2, "雅 and 楽 come from a fallback font: {inter}");
}
#[test]
fn export_warns_when_a_style_is_drawn_in_another_style() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
s.execute("text.create", &json!({"x": 10, "y": 20, "text": "Hi", "font": "Source Sans 3", "style": "Black Wide"})).unwrap();
let out = s.execute("document.export", &json!({"format": "pdf"})).unwrap();
let warnings = out["warnings"].to_string();
assert!(warnings.contains("Source Sans 3 Black Wide was drawn in Source Sans 3"), "{out}");
}
#[test]
fn opened_documents_have_exact_text_bounds() {
let mut s = Session::new();