Merge pull request #6334 from JonRC/fix/eps-missing-fonts-of-the-document

EPS import names the missing fonts of the document's type (#6326 follow-up)
This commit is contained in:
BFlat
2026-11-10 00:49:58 +07:00
committed by GitHub
5 changed files with 72 additions and 28 deletions
+42 -5
View File
@@ -74,7 +74,7 @@ use std::sync::Arc;
use vectorcraft_color::Paint;
use vectorcraft_color::swatch::{REGISTRATION, Swatch};
use vectorcraft_doc::{ColorMode, Document, LayerColor, Node};
use vectorcraft_doc::{ColorMode, Document, LayerColor, Node, NodeKind};
use vectorcraft_geom::{Affine, Rect};
use graphics::{GState, Out};
@@ -101,6 +101,8 @@ 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";
/// Most families named in that warning.
const MAX_MISSING_FONTS: usize = 64;
/// A read file.
#[derive(Debug)]
@@ -269,8 +271,46 @@ pub fn import_trusting(bytes: &[u8], editing_data: bool, trust: bool) -> Result<
/// [`import_trusting`], with the names in the editing copy that aren't UTF-8 (layers, objects,
/// artboards, colors, gradients, patterns, graphic styles, swatches, brushes, symbols, variables
/// and data sets) read in `names`.
/// and data sets) read in `names`. The type of the document it gives that is set in families that
/// aren't available shows in the fallback font, and a warning names them.
pub fn import_names(bytes: &[u8], editing_data: bool, trust: bool, names: NameEncoding) -> Result<Imported, String> {
let mut r = read_names(bytes, editing_data, trust, names)?;
let missing = missing_fonts(&r.document);
if !missing.is_empty() {
r.warnings.push(format!("{MISSING_FONTS_NOTE}: {}", missing.join(", ")));
}
Ok(r)
}
/// The families of the type in `d` (its layers and its symbols) that aren't available, each once
/// in the order the type comes, at most [`MAX_MISSING_FONTS`]: the document's, whether its type
/// came from the page or from an editing copy, and none for type only measured or outlined.
fn missing_fonts(d: &Document) -> Vec<String> {
let db = vectorcraft_text::FontDb::global();
let mut checked: HashSet<String> = HashSet::new();
let mut missing: Vec<String> = Vec::new();
let mut add = |n: &Node| {
let NodeKind::Text(t) = &n.kind else { return };
for run in &t.runs {
let family = run.style.font_family.as_str();
if missing.len() >= MAX_MISSING_FONTS || family.is_empty() || checked.contains(family) {
continue;
}
checked.insert(family.to_string());
if db.find_family(family).is_none() {
missing.push(family.to_string());
}
}
};
d.walk(&mut add);
for s in &d.symbols {
s.art.walk(&mut add);
}
missing
}
/// [`import_names`] before the type's fonts are checked.
fn read_names(bytes: &[u8], editing_data: bool, trust: bool, names: NameEncoding) -> Result<Imported, String> {
let ps = program(bytes)?;
let dsc = Dsc::read(ps);
// A legacy `.ai` VectorCraft wrote: its prolog prints the art plainly (gradients flat, no
@@ -368,9 +408,6 @@ 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,9 +115,6 @@ 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.
@@ -155,9 +152,6 @@ 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 {
@@ -207,14 +201,6 @@ 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());
}
}
+9
View File
@@ -1270,6 +1270,15 @@ fn shading_functions_in_shared_arrays_stop_at_the_limits() {
assert!(r.warnings.iter().any(|w| w.contains("the program runs too long")), "{:?}", r.warnings);
}
/// Type only measured (`stringwidth`) or drawn as outlines (`charpath`) adds no type to the
/// document, so its font isn't named as one that shows in the fallback font.
#[test]
fn fonts_only_measured_or_outlined_are_not_named_as_missing() {
let r = read("/Nowhere-Bold 12 selectfont (Hi) stringwidth pop pop newpath 10 10 moveto (Hi) true charpath fill 10 10 80 80 rectfill");
assert!(!objects(&r.document).iter().any(|n| matches!(n.kind, NodeKind::Text(_))));
assert!(!r.warnings.iter().any(|w| w.starts_with(super::MISSING_FONTS_NOTE)), "{:?}", r.warnings);
}
#[test]
fn cshow_runs_its_procedure_for_each_character() {
check("/n 0 def { pop pop pop /n n 1 add def } (abc) cshow n 3 eq");
+20 -1
View File
@@ -245,6 +245,25 @@ fn type_in_the_legacy_format_is_the_layers_own_and_the_page_has_text() {
assert!(r.warnings.iter().any(|w| w.starts_with("1 text objects of the page are outlines on the layers")), "{:?}", r.warnings);
}
/// The warning about fonts that aren't available names the fonts of the type the document keeps,
/// wherever it came from: type the editing copy sets in a font that isn't available is named
/// though the page sets it in one that is, and type only the page sets in such a font is not.
#[test]
fn the_missing_fonts_warning_names_the_fonts_of_the_documents_type() {
let page = |font: &str| format!("{}\n0 0 0 1 setcmykcolor /{font} findfont 12 scalefont setfont 10 50 moveto (Hi) show", page_square(10, 10));
// `legacy_text` in `font`.
let words = |font: &str| format!("0 To\n1 0 0 1 10 50 0 Tp\nTP\n0 Tr\n0 0 0 1 k\n/_{font} 12 Tf\n(Hi) Tx\nTO");
for (on_page, in_copy, named) in [("Inter-Regular", "Nowhere-Bold", Some("Nowhere")), ("Nowhere-Bold", "Inter-Regular", None)] {
let art = native(&(layer("Art", true, &square(10, 10)) + &layer("Words", true, &words(in_copy))));
let r = import(&eps(&page(on_page), &art)).unwrap();
assert_eq!(names(&r.document), ["Art", "Words"], "{:?}", r.warnings);
assert_eq!(text_of(&r.document.layers[1]).len(), 1, "{:?}", r.warnings);
let notes: Vec<&String> = r.warnings.iter().filter(|w| w.starts_with(super::MISSING_FONTS_NOTE)).collect();
let want: Vec<String> = named.iter().map(|f| format!("{}: {f}", super::MISSING_FONTS_NOTE)).collect();
assert_eq!(notes, want.iter().collect::<Vec<_>>(), "page {on_page}, editing copy {in_copy}: {:?}", r.warnings);
}
}
#[test]
fn a_damaged_editing_copy_leaves_the_page() {
// Not ASCII85, ASCII85 of something that isn't Zstandard, Zstandard of something that isn't art.
@@ -5203,7 +5222,7 @@ fn envelope_options_keep_the_source_text_hidden_under_its_outlined_appearance()
}
});
assert_eq!(texts, [("CHONKYCATS".to_string(), false)], "{:?}", r.warnings);
assert!(r.warnings.is_empty(), "{:?}", r.warnings);
assert!(super::tests::program_warnings(&r.warnings).is_empty(), "{:?}", r.warnings);
}
/// A live envelope that draws a little apart from its drawn art, in layers that already differ
+1 -8
View File
@@ -489,14 +489,7 @@ impl Interp<'_> {
}
let (family, font_style) = family_style(&name);
// The available family's own spelling: `MicrosoftYaHei` is Microsoft YaHei, not "Microsoft Ya Hei".
// 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 font_family = vectorcraft_text::FontDb::global().find_family(&family).unwrap_or(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);