diff --git a/crates/eps/src/ai_editing/tests.rs b/crates/eps/src/ai_editing/tests.rs index 67ca443a7b..2af2ecd394 100644 --- a/crates/eps/src/ai_editing/tests.rs +++ b/crates/eps/src/ai_editing/tests.rs @@ -4366,6 +4366,19 @@ fn dynamic_text_is_written_at_its_drawn_sizes() { assert!(!texts[0].dynamic_text); let k = texts[0].xf.as_coeffs()[0]; assert_eq!(r3(texts[0].runs[0].style.size * k), r3(drawn)); + // Type written as outlines (here on an open path whose ends meet) loses the setting too, and + // says so. + let mut doc = Document::new(600.0, 300.0); + let layer = doc.layers[0].id; + let mut t = TextObject::point(vectorcraft_geom::Point::ZERO, "Grow", style(12.0, Color::BLACK)); + let ends_meet = [(50.0, 200.0).into(), (550.0, 200.0).into(), (300.0, 50.0).into(), (50.0, 200.0).into()]; + t.kind = TextKind::OnPath { path: PathData::new(vec![vectorcraft_geom::SubPath::polyline(&ends_meet, false)]), start: 0.0, end: None }; + t.xf = Affine::IDENTITY; + t.dynamic_text = true; + let node = text_node(&mut doc, t); + doc.insert(Some(layer), usize::MAX, node).unwrap(); + let out = export_ai_editing(&doc, &opts()).unwrap(); + assert!(out.warnings.iter().any(|w| w == writer::TYPE) && out.warnings.iter().any(|w| w == writer::DYNAMIC_TEXT), "{:?}", out.warnings); } /// See-through type with the knockout shape flag comes back with the flag and writes no warning. diff --git a/crates/eps/src/ai_editing/writer.rs b/crates/eps/src/ai_editing/writer.rs index afa6412561..dea4ad0c24 100644 --- a/crates/eps/src/ai_editing/writer.rs +++ b/crates/eps/src/ai_editing/writer.rs @@ -1257,6 +1257,10 @@ impl<'a> Writer<'a> { return; } self.warn(TYPE); + // The outlines keep the sizes Dynamic Text draws the type at, but not the setting. + if matches!(&n.kind, NodeKind::Text(t) if t.dynamic_text) { + self.warn(DYNAMIC_TEXT); + } let doc = self.doc; let resolved = match &n.kind { NodeKind::Text(t) => Some(doc.inline_resolved(t).into_owned()), diff --git a/crates/eps/src/legacy_ai/tests.rs b/crates/eps/src/legacy_ai/tests.rs index 988cd9a1f0..14059d33b8 100644 --- a/crates/eps/src/legacy_ai/tests.rs +++ b/crates/eps/src/legacy_ai/tests.rs @@ -1263,6 +1263,44 @@ fn dynamic_text_is_written_at_its_drawn_sizes_with_a_warning() { assert!(out.warnings.iter().any(|w| w == DYNAMIC_TEXT), "{:?}", out.warnings); } +/// Dynamic Text fits type whose size measures the cap height at the size the Character panel +/// shows: 1,000 pt measuring a 0.7 em cap height on a long path grows to the 1,296 pt limit and is +/// written at the em size drawn there, as text and as outlines. +#[test] +fn dynamic_text_with_a_font_height_reference_is_written_at_its_drawn_size() { + use vectorcraft_doc::{TextKind, TextObject}; + use vectorcraft_text::test_fonts::{HEIGHT_FAMILY, height_font}; + let db = vectorcraft_text::FontDb::global(); + db.add_font(height_font(true).unwrap()); + let mut t = TextObject::point(vectorcraft_geom::Point::ZERO, "HE", style(1000.0, red())); + let st = &mut t.runs[0].style; + st.font_family = HEIGHT_FAMILY.into(); + st.font_height_reference = vectorcraft_doc::FontHeightReference::CapHeight; + let mut line = PathData::new(vec![]); + line.subpaths.push(vectorcraft_geom::SubPath::polyline(&[(0.0, 2000.0).into(), (100_000.0, 2000.0).into()], false)); + t.kind = TextKind::OnPath { path: line, start: 0.0, end: None }; + t.xf = Affine::IDENTITY; + t.dynamic_text = true; + let lay = vectorcraft_text::layout(db, &t); + assert!((lay.fit_scale - 1.296).abs() < 1e-9, "{}", lay.fit_scale); + let em = 1296.0 / 0.7; + let mut glyphs = kurbo::BezPath::new(); + for g in &lay.glyphs { + glyphs.extend(g.outline.iter()); + } + let want = glyphs.bounding_box(); + let mut doc = Document::new(300.0, 200.0); + let layer = doc.layers[0].id; + let n = Node::new(doc.alloc_id(), NodeKind::Text(Box::new(t))); + doc.insert(Some(layer), usize::MAX, n).unwrap(); + let back = crate::import(&write(&doc).bytes).unwrap().document; + let size = texts(&back)[0].1.runs[0].style.size; + assert!((size - em).abs() < 1e-3, "{size} vs {em}"); + let back = crate::import(&write_with(&doc, LegacyAiOptions { outline_text: true, ..LegacyAiOptions::default() }).bytes).unwrap().document; + let b = children(&children(&back.layers[0])[0])[0].visual_bounds().unwrap(); + assert!((b.height() - want.height()).abs() < 0.05 && (b.width() - want.width()).abs() < 0.05, "{b:?}, laid out at {want:?}"); +} + /// Characters with Auto leading are written with their paragraph's Auto Leading share of the /// size: 18 pt type in paragraphs at 150% is written with 27 pt leading. #[test] diff --git a/crates/eps/src/legacy_ai/writer.rs b/crates/eps/src/legacy_ai/writer.rs index f71018996c..062ea3e9d8 100644 --- a/crates/eps/src/legacy_ai/writer.rs +++ b/crates/eps/src/legacy_ai/writer.rs @@ -657,15 +657,11 @@ impl<'a> Writer<'a> { // ---------- type ---------- /// Type: a text object when the format can hold it as text, else its outlines (type with - /// rotated characters among them, which keeps their rotation). A run whose - /// font size measures a height other than the em box is written at the em size it is drawn - /// at ([`vectorcraft_text::FontDb::font_heights_resolved`]). + /// rotated characters among them, which keeps their rotation). fn text(&mut self, t: &TextObject) { if !invertible(t.xf) { return; } - let resolved = vectorcraft_text::FontDb::global().font_heights_resolved(t); - let t = &*resolved; // Written as text or as outlines, the type keeps its drawn sizes but not the setting. if t.dynamic_text && matches!(t.kind, TextKind::OnPath { .. }) { self.warn(DYNAMIC_TEXT); @@ -687,14 +683,18 @@ impl<'a> Writer<'a> { /// A text object (§15): `type To`, the text path (`a b c d tx ty startPt Tp`, an area's frame /// or the path type runs along, `TP`), then each run's style and characters, `TO`. The matrix /// takes text space (y up, the first baseline's start for point type) to art space; the frame - /// and path are in art space, as the specification's examples have them (§15.5). + /// and path are in art space, as the specification's examples have them (§15.5). Type that + /// Shrink Text to Fit or Dynamic Text scales is written in the styles it draws in, fitted at + /// the sizes the Character panel shows; then a run whose font size measures a height other + /// than the em box is written at the em size it is drawn at + /// ([`vectorcraft_text::FontDb::font_heights_resolved`]). fn text_object(&mut self, t: &TextObject) { if lost_attributes(t) { self.warn(TYPE_ATTRIBUTES); } - // Type that Shrink Text to Fit or Dynamic Text scales is written in the styles it draws in. let fitted = vectorcraft_text::drawn(t); - let t = &*fitted; + let resolved = vectorcraft_text::FontDb::global().font_heights_resolved(&fitted); + let t = &*resolved; let m = self.to_art * t.xf * Affine::new([1.0, 0.0, 0.0, -1.0, 0.0, 0.0]); let (kind, path, start) = match &t.kind { TextKind::Point => (0, None, 0.0),