.ai save: an open frame whose ends meet is written as outlines

Area type whose open frame ends where it starts was written as text and
read back in a closed frame. It is now written as its outlines, alone or
as a frame of a thread.
This commit is contained in:
dotCooCoo
2026-11-07 05:15:16 +07:00
committed by bflatastic
parent 0c5ad8e242
commit 63dbb0f0dd
2 changed files with 45 additions and 2 deletions
+37
View File
@@ -3479,6 +3479,43 @@ fn a_thread_of_horizontal_and_vertical_frames_is_written_as_its_outlines() {
assert!(!program(&doc).contains("/AI11Text"));
}
/// Area type in an open frame whose ends meet is written as its outlines, alone or as a frame of a
/// thread: as text it would be read back in a closed frame.
#[test]
fn an_open_frame_whose_ends_meet_is_written_as_its_outlines() {
use vectorcraft_doc::{TextKind, TextObject};
let open = |r: Rect| {
let mut frame = shapes::rectangle(r);
frame.subpaths[0].closed = false;
let start = frame.subpaths[0].anchors[0].clone();
frame.subpaths[0].anchors.push(start);
frame
};
let mut alone = TextObject::point(vectorcraft_geom::Point::ZERO, "Open", style(10.0, Color::BLACK));
alone.kind = TextKind::Area { frame: open(Rect::new(10.0, 10.0, 90.0, 40.0)) };
alone.xf = Affine::IDENTITY;
let mut doc = Document::new(200.0, 100.0);
let layer = doc.layers[0].id;
let n = text_node(&mut doc, alone.clone());
doc.insert(Some(layer), usize::MAX, n).unwrap();
assert_eq!(export_ai_editing(&doc, &opts()).unwrap().warnings, [writer::TYPE]);
assert!(!program(&doc).contains("/AI11Text"));
let mut first = alone;
first.kind = TextKind::Area { frame: shapes::rectangle(Rect::new(10.0, 10.0, 90.0, 40.0)) };
let mut second = first.clone();
second.runs[0].text.clear();
second.kind = TextKind::Area { frame: open(Rect::new(110.0, 10.0, 190.0, 90.0)) };
let mut doc = Document::new(200.0, 100.0);
let layer = doc.layers[0].id;
let (first, second) = (text_node(&mut doc, first), text_node(&mut doc, second));
doc.text_threads.push(vec![first.id, second.id]);
doc.insert(Some(layer), usize::MAX, first).unwrap();
doc.insert(Some(layer), usize::MAX, second).unwrap();
assert_eq!(export_ai_editing(&doc, &opts()).unwrap().warnings, [writer::TYPE]);
assert!(!program(&doc).contains("/AI11Text"));
}
/// A thread with a frame in art cut to a clipping object that isn't a path is written as its
/// outlines, every frame of it; so is type whose size is past what the reader reads.
#[test]
+8 -2
View File
@@ -197,7 +197,7 @@ pub(in crate::ai_editing) fn holds(n: &Node, t: &TextObject) -> bool {
let xf = t.xf.as_coeffs();
let shape_ok = match &t.kind {
TextKind::Point => xf.iter().all(|v| v.is_finite()) && t.xf.determinant() > 1e-12,
TextKind::Area { .. } => shape(t).as_ref().and_then(segments).is_some_and(|s| closed(&s)),
TextKind::Area { .. } => closed_frame(t),
// An open path whose ends meet would be read back closed.
TextKind::OnPath { path, .. } => {
let open = path.subpaths.iter().any(|sp| !sp.anchors.is_empty() && !sp.closed);
@@ -214,6 +214,12 @@ pub(in crate::ai_editing) fn holds(n: &Node, t: &TextObject) -> bool {
&& within_reader(t, story_scale(t))
}
/// Is the frame of area type `t` one closed part? An open frame whose ends meet would be read back
/// closed.
fn closed_frame(t: &TextObject) -> bool {
shape(t).filter(|p| p.subpaths.iter().all(|sp| sp.anchors.is_empty() || sp.closed)).as_ref().and_then(segments).is_some_and(|s| closed(&s))
}
/// Document points to a unit of the story of type `t`: its scale is put into its lengths.
fn story_scale(t: &TextObject) -> f64 {
1.0 / t.xf.determinant().abs().sqrt()
@@ -839,7 +845,7 @@ impl TextDoc {
&& runs_held(t)
&& (story_scale(t) / scale - 1.0).abs() <= 1e-9
&& within_reader(t, scale)
&& shape(t).as_ref().and_then(segments).is_some_and(|s| closed(&s))
&& closed_frame(t)
}
_ => false,
};