Merge pull request #6326 from JonRC/fix/eps-missing-fonts-warning

Fix #6320: EPS import warns about fonts that aren't available
This commit is contained in:
BFlat
2026-11-09 23:12:31 +07:00
committed by GitHub
7 changed files with 57 additions and 7 deletions
+7
View File
@@ -98,6 +98,10 @@ const LETTER: [f64; 4] = [0.0, 0.0, 612.0, 792.0];
/// Largest page side read (points).
const MAX_SIDE: f64 = 1e5;
/// The start of the warning that type was set in fonts that aren't available, before ": " and
/// their families: PDF import's words, as type in those fonts comes in the same way.
pub(crate) const MISSING_FONTS_NOTE: &str = "fonts that aren't available show in the fallback font until they are";
/// A read file.
#[derive(Debug)]
pub struct Imported {
@@ -364,6 +368,9 @@ fn finish_with(out: Out, layers: Vec<Arc<Node>>) -> Imported {
}
doc.layers = layers;
let mut warnings = out.warnings;
if !out.missing_fonts.is_empty() {
warnings.push(format!("{MISSING_FONTS_NOTE}: {}", out.missing_fonts.join(", ")));
}
doc.limit_depth_noting(&mut warnings);
Imported { document: doc, warnings, preview: false }
}
+14
View File
@@ -115,6 +115,9 @@ impl Default for GState {
}
}
/// Most families listed in the warning about fonts that aren't available.
const MAX_MISSING_FONTS: usize = 64;
/// What the program draws.
pub(crate) struct Out {
/// The document being made: ids, images and swatches.
@@ -152,6 +155,9 @@ pub(crate) struct Out {
pub(super) tilings: HashMap<u64, Vec<Tiling>>,
/// How deep art drawn apart (a pattern's cell, a Type 3 glyph) is nested.
pub apart: u32,
/// The families type was set in that aren't available (it shows in the fallback font), each
/// once in the order first set, for the import's warning.
pub missing_fonts: Vec<String>,
}
fn sane(r: Rect) -> bool {
@@ -201,6 +207,14 @@ impl Out {
shadings: HashSet::new(),
tilings: HashMap::new(),
apart: 0,
missing_fonts: vec![],
}
}
/// Note that type was set in `family`, which isn't available (at most [`MAX_MISSING_FONTS`]).
pub fn missing_font(&mut self, family: &str) {
if !family.is_empty() && self.missing_fonts.len() < MAX_MISSING_FONTS && !self.missing_fonts.iter().any(|f| f == family) {
self.missing_fonts.push(family.to_string());
}
}
+8 -2
View File
@@ -737,7 +737,7 @@ fn embedded_font_programs_are_skipped() {
let r = read(&program);
let NodeKind::Text(t) = &objects(&r.document)[0].kind else { panic!("{program}: {:?}", r.warnings) };
assert_eq!(t.runs[0].style.font_family, "My Font");
assert!(r.warnings.is_empty(), "{program}: {:?}", r.warnings);
assert!(program_warnings(&r.warnings).is_empty(), "{program}: {:?}", r.warnings);
}
}
@@ -833,12 +833,18 @@ fn device_settings_are_accepted_and_change_nothing() {
}
/// Check that `body` draws one square, blue: it sets blue when what it checks holds, else red.
/// `warnings` without the note naming fonts that aren't available: which those are depends on the
/// fonts installed where the tests run, not on the program.
pub(super) fn program_warnings(warnings: &[String]) -> Vec<&String> {
warnings.iter().filter(|w| !w.starts_with(super::MISSING_FONTS_NOTE)).collect()
}
fn blue(body: &str) {
let r = read(&format!("{body} 10 10 80 80 rectfill"));
let o = objects(&r.document);
assert_eq!(o.len(), 1, "{body}: {:?}", r.warnings);
assert_eq!(fill(&o[0]), Some(&Paint::solid(Color::rgb(0.0, 0.0, 1.0))), "{body}");
assert!(r.warnings.is_empty(), "{body}: {:?}", r.warnings);
assert!(program_warnings(&r.warnings).is_empty(), "{body}: {:?}", r.warnings);
}
/// Check that PostScript `cond` leaves `true`.
+1 -1
View File
@@ -392,7 +392,7 @@ fn named_cidfonts_over_unicode_cmaps_set_their_characters_as_type() {
.collect()
};
let r = super::tests::read("/Ryumin-Light-UniJIS-UCS2-H findfont 20 scalefont setfont 10 20 moveto <4e2d0041> show");
assert!(r.warnings.is_empty(), "{:?}", r.warnings);
assert!(super::tests::program_warnings(&r.warnings).is_empty(), "{:?}", r.warnings);
let t = texts(&r);
assert_eq!(t.len(), 1, "{:?}", all(&r.document));
assert_eq!(t[0].plain_text(), "\u{4e2d}A");
+1 -1
View File
@@ -215,7 +215,7 @@ fn text_and_its_strokes_come_from_the_page_into_the_layer_of_its_text_object() {
assert_eq!(kinds(&r.document.layers[0]), ["path"]);
// The text and the path of its stroke, where the text object is (no group of their own).
assert_eq!(kinds(&r.document.layers[1]), ["text", "path"], "{:?}", r.warnings);
assert!(r.warnings.is_empty(), "{:?}", r.warnings);
assert!(super::tests::program_warnings(&r.warnings).is_empty(), "{:?}", r.warnings);
}
/// A point type object in the legacy format (`To` … `TO`), as an EPS saved for Illustrator 10
+18 -2
View File
@@ -5,7 +5,7 @@
use vectorcraft_doc::{NodeKind, TextObject};
use super::tests::{all, bounds, check, read};
use super::tests::{all, bounds, check, program_warnings, read};
use crate::import::import;
/// A Type 1 charstring number (§6.2).
@@ -115,7 +115,7 @@ fn program(encoding: &str, hex: bool, body: &str) -> crate::Imported {
fn embedded_type1_type_keeps_the_fonts_widths() {
for hex in [true, false] {
let r = program(OWN, hex, "/TestOne findfont 50 scalefont setfont 10 20 moveto (AB) show");
assert!(r.warnings.is_empty(), "{:?}", r.warnings);
assert!(program_warnings(&r.warnings).is_empty(), "{:?}", r.warnings);
let t = texts(&r);
assert_eq!(t.len(), 1, "{:?}", all(&r.document));
assert_eq!(t[0].plain_text(), "AB");
@@ -126,6 +126,22 @@ fn embedded_type1_type_keeps_the_fonts_widths() {
}
}
/// Type in an embedded font whose family isn't available keeps the font's name and shows in the
/// fallback font, and the import says so as PDF import does: each such family once, in the order
/// its type comes. A family that is available isn't named.
#[test]
fn type_in_fonts_that_arent_available_is_named_in_a_warning() {
let r = program(
OWN,
false,
"/TestOne findfont 50 scalefont setfont 10 20 moveto (AB) show 10 60 moveto (BA) show \
/Nowhere-Bold 12 selectfont 10 80 moveto (x) show /Inter-Regular 12 selectfont 10 90 moveto (y) show",
);
assert_eq!(texts(&r).iter().map(|t| t.runs[0].style.font_family.as_str()).collect::<Vec<_>>(), ["Test One", "Test One", "Nowhere", "Inter"]);
// PDF import's words.
assert_eq!(r.warnings, ["fonts that aren't available show in the fallback font until they are: Test One, Nowhere"]);
}
#[test]
fn embedded_type1_fonts_measure_and_outline_their_glyphs() {
let fonts = String::from_utf8(font(OWN, true)).unwrap();
+8 -1
View File
@@ -489,7 +489,14 @@ impl Interp<'_> {
}
let (family, font_style) = family_style(&name);
// The available family's own spelling: `MicrosoftYaHei` is Microsoft YaHei, not "Microsoft Ya Hei".
let font_family = vectorcraft_text::FontDb::global().find_family(&family).unwrap_or(family);
// A family that isn't available keeps its name and shows in the fallback font.
let font_family = match vectorcraft_text::FontDb::global().find_family(&family) {
Some(f) => f,
None => {
self.out.missing_font(&family);
family
}
};
let fill = if self.g.paint.is_none() { Paint::solid(vectorcraft_color::Color::BLACK) } else { self.g.paint.clone() };
let style = CharStyle { font_family, font_style, size, fill, stroke: Paint::None, ..CharStyle::default() };
let mut t = TextObject::point(Point::ZERO, text, style);