mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 11:11:30 +00:00
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:
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user