mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 20:03:56 +00:00
Merge pull request #5076 from dotCooCoo/fix/ai-page-art-moves-patterns
.ai import: page art moved to its artboard takes its pattern tiles along
This commit is contained in:
@@ -15,6 +15,10 @@ pub(super) enum Page {
|
||||
Artboard,
|
||||
}
|
||||
|
||||
/// How art moved from a page to its place in the layers' document transforms: its pattern tiles
|
||||
/// move with it.
|
||||
pub(super) const PAGE_MOVE: Scaling = Scaling { strokes: false, effects: None, keep_type_strokes: false, keep_corners: false, patterns: true };
|
||||
|
||||
/// The page (`visible`, the file read as its page) and the place of its area in the layers'
|
||||
/// document.
|
||||
pub(super) struct Frame<'a> {
|
||||
@@ -177,7 +181,7 @@ pub(super) fn build_from(
|
||||
None => shift,
|
||||
};
|
||||
for n in object.iter_mut() {
|
||||
Arc::make_mut(n).transform(shift, false);
|
||||
Arc::make_mut(n).transform(shift, PAGE_MOVE);
|
||||
}
|
||||
}
|
||||
// An EPS saved as its artboard has the art outside it left out of its page.
|
||||
@@ -681,7 +685,7 @@ fn replace_unread(
|
||||
for &i in &selected {
|
||||
let mut copy = Node::clone(&art[i].node);
|
||||
if let Some(shift) = moves.get(page) {
|
||||
copy.transform(*shift, false);
|
||||
copy.transform(*shift, PAGE_MOVE);
|
||||
}
|
||||
found.push(Arc::new(reid_page_art(doc, ©)));
|
||||
claimed.insert(i, art[i].end);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::build::{Page, build, build_from};
|
||||
use super::build::{PAGE_MOVE, Page, build, build_from};
|
||||
use super::slots::LAYERS_UNREAD;
|
||||
use super::{Affine, Arc, Cow, Document, Imported, Node, NodeKind, PathData, Rect, Vec2, ai};
|
||||
|
||||
@@ -204,7 +204,7 @@ fn place_art(layer: &mut Node, pages: &[Rect], moves: &[Vec2]) {
|
||||
let fit = |p: &Rect| (p.intersect(r).area().max(0.0), -(p.center() - r.center()).hypot());
|
||||
let Some((i, _)) = pages.iter().map(fit).enumerate().max_by(|(_, a), (_, b)| a.0.total_cmp(&b.0).then(a.1.total_cmp(&b.1))) else { continue };
|
||||
if let Some(v) = moves.get(i).filter(|v| **v != Vec2::ZERO) {
|
||||
Arc::make_mut(child).transform(Affine::translate(*v), false);
|
||||
Arc::make_mut(child).transform(Affine::translate(*v), PAGE_MOVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,6 +832,43 @@ fn unread_object_on_second_artboard_takes_second_page_art() {
|
||||
assert_eq!(bounds, vectorcraft_geom::Rect::new(50.0, 196.0, 54.0, 200.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_art_from_second_artboard_moves_its_pattern_tile() {
|
||||
let boards = "%_/Document :\n%_/Dictionary :\n%_/Array :\n\
|
||||
%_/Dictionary :\n%_0 100 /RealPointRelToROrigin\n%_ (PositionPoint1) ,\n%_100 0 /RealPointRelToROrigin\n%_ (PositionPoint2) ,\n%_; ,\n\
|
||||
%_/Dictionary :\n%_0 -50 /RealPointRelToROrigin\n%_ (PositionPoint1) ,\n%_100 -150 /RealPointRelToROrigin\n%_ (PositionPoint2) ,\n%_; ,\n\
|
||||
%_; (ArtboardArray) ,\n%_; /NotRecorded ,\n%_;\n";
|
||||
let dots = "%AI3_BeginPattern: (Dots)\n(Dots) 0 0 4 4\n%_0 0 0 1 k\n%_1 1 m\n%_3 1 L\n%_3 3 L\n%_1 3 L\n%_f\nE\n%AI3_EndPattern\n";
|
||||
let second = "u\n0 0 1 0 k\n50 -100 m\n54 -100 L\n54 -96 L\n50 -96 L\nf\nZZ\nU";
|
||||
let art = native(&(boards.to_string() + dots + &layer("Live", true, &square(10, 10)) + &layer("Mixed", true, second)));
|
||||
let (editing, _) = crate::ai_alone(&private(&native(&format!("{dots}{}", layer("Live", true, &square(10, 10)))))).unwrap();
|
||||
let source = editing.pattern("Dots").unwrap();
|
||||
let mut page = pdf_part(10.0, 86.0);
|
||||
let mut pattern = source.clone();
|
||||
pattern.art = source.art.iter().map(|node| std::sync::Arc::new(page.reid(node))).collect();
|
||||
page.patterns.push(pattern);
|
||||
let mut board = page.artboards[0].clone();
|
||||
(board.id, board.rect) = (2, vectorcraft_geom::Rect::new(120.0, 0.0, 220.0, 100.0));
|
||||
page.artboards.push(board);
|
||||
let path = vectorcraft_geom::shapes::rectangle(vectorcraft_geom::Rect::new(170.0, 46.0, 174.0, 50.0));
|
||||
let paint = vectorcraft_color::Paint::Pattern { pattern: "Dots".into(), xf: vectorcraft_geom::Affine::IDENTITY };
|
||||
let appearance = vectorcraft_doc::Appearance {
|
||||
items: vec![vectorcraft_doc::AppearanceItem::Fill(vectorcraft_doc::FillLayer::new(paint))],
|
||||
..Default::default()
|
||||
};
|
||||
let node = Node::path(page.alloc_id(), path, appearance);
|
||||
let layer = page.layers[0].id;
|
||||
page.insert(Some(layer), 1, node).unwrap();
|
||||
let (doc, notes, read) = crate::layered_ai(&private(&art), page, vec![], false, false, 1.0);
|
||||
assert!(read, "{notes:?}");
|
||||
let replacement = &doc.layers[1].children().unwrap()[0].children().unwrap()[0];
|
||||
assert_eq!(replacement.visual_bounds().unwrap(), vectorcraft_geom::Rect::new(50.0, 196.0, 54.0, 200.0));
|
||||
let Some(vectorcraft_color::Paint::Pattern { xf, .. }) = replacement.appearance.fill().map(|f| &f.paint) else {
|
||||
panic!("the page art's pattern fill is kept: {:?}", replacement.appearance);
|
||||
};
|
||||
assert_eq!(*xf, vectorcraft_geom::Affine::translate(vectorcraft_geom::Vec2::new(-120.0, 150.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_operator_between_objects_falls_back() {
|
||||
let body = format!("{}\nZZ\n{}", square(50, 10), square(70, 10));
|
||||
@@ -1443,6 +1480,38 @@ fn an_ai_file_read_as_its_pdf_part_keeps_its_artboard_layout() {
|
||||
assert!((moved[1] - vectorcraft_geom::Point::new(100.0, 1600.0)).hypot() < 1e-6, "the second's art goes with its page: {moved:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn art_moved_with_its_page_moves_its_pattern_tile() {
|
||||
// Two 100-point artboards, the second 50 below the first; an operator this doesn't know, so
|
||||
// the layers aren't read.
|
||||
let boards = "%_/Document :\n%_/Dictionary :\n%_/Array :\n\
|
||||
%_/Dictionary :\n%_0 100 /RealPointRelToROrigin\n%_ (PositionPoint1) ,\n%_100 0 /RealPointRelToROrigin\n%_ (PositionPoint2) ,\n%_; ,\n\
|
||||
%_/Dictionary :\n%_0 -50 /RealPointRelToROrigin\n%_ (PositionPoint1) ,\n%_100 -150 /RealPointRelToROrigin\n%_ (PositionPoint2) ,\n%_; ,\n\
|
||||
%_; (ArtboardArray) ,\n%_; /NotRecorded ,\n%_;\n";
|
||||
let art = native(&(boards.to_string() + &layer("Art", true, &(square(10, 10) + "\n1 Zz"))));
|
||||
// The PDF part: the second page 20 points right of the first, with a pattern-filled square.
|
||||
let mut pdf = pdf_part(10.0, 10.0);
|
||||
let mut page = pdf.artboards[0].clone();
|
||||
(page.id, page.rect) = (2, vectorcraft_geom::Rect::new(120.0, 0.0, 220.0, 100.0));
|
||||
pdf.artboards.push(page);
|
||||
let path = vectorcraft_geom::shapes::rectangle(vectorcraft_geom::Rect::new(130.0, 10.0, 134.0, 14.0));
|
||||
let paint = vectorcraft_color::Paint::Pattern { pattern: "Dots".into(), xf: vectorcraft_geom::Affine::IDENTITY };
|
||||
let appearance = vectorcraft_doc::Appearance {
|
||||
items: vec![vectorcraft_doc::AppearanceItem::Fill(vectorcraft_doc::FillLayer::new(paint))],
|
||||
..Default::default()
|
||||
};
|
||||
let (id, layer_id) = (pdf.alloc_id(), pdf.layers[0].id);
|
||||
pdf.insert(Some(layer_id), 1, Node::path(id, path, appearance)).unwrap();
|
||||
let (d, w, read) = crate::layered_ai(&private(&art), pdf, vec![], false, false, 1.0);
|
||||
assert!(!read, "{w:?}");
|
||||
let square = &d.layers[0].children().unwrap()[1];
|
||||
assert_eq!(square.visual_bounds().unwrap().origin(), vectorcraft_geom::Point::new(10.0, 160.0));
|
||||
let Some(vectorcraft_color::Paint::Pattern { xf, .. }) = square.appearance.fill().map(|f| &f.paint) else {
|
||||
panic!("the square keeps its pattern fill: {:?}", square.appearance);
|
||||
};
|
||||
assert_eq!(*xf, vectorcraft_geom::Affine::translate(vectorcraft_geom::Vec2::new(-120.0, 150.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_gray_is_a_grayscale_colour() {
|
||||
// `g` is a gray: 0.25 is 75 % of the black ink, as the page's own CMYK black says; the colour
|
||||
@@ -2126,6 +2195,41 @@ fn type_on_a_later_artboard_of_an_ai_file_goes_to_its_text_object() {
|
||||
assert!(!w.iter().any(|w| w.contains("with another's") || w.contains("doesn't draw")), "{w:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_type_on_a_later_artboard_moves_its_pattern_tile() {
|
||||
// As above, with a frame this doesn't read, so the text object takes the page's type, which
|
||||
// has a pattern fill.
|
||||
let boards = "%_/Document :\n%_/Dictionary :\n%_/Array :\n\
|
||||
%_/Dictionary :\n%_0 100 /RealPointRelToROrigin\n%_ (PositionPoint1) ,\n%_100 0 /RealPointRelToROrigin\n%_ (PositionPoint2) ,\n%_; ,\n\
|
||||
%_/Dictionary :\n%_0 -50 /RealPointRelToROrigin\n%_ (PositionPoint1) ,\n%_100 -150 /RealPointRelToROrigin\n%_ (PositionPoint2) ,\n%_; ,\n\
|
||||
%_; (ArtboardArray) ,\n%_; /NotRecorded ,\n%_;\n";
|
||||
let doc = last_frame_unread(&text_document(&[("Hi there\r", 0, (8200.0, 8341.5), (0.0, 0.0), 0.0)]));
|
||||
let art = native_with_text(&(boards.to_string() + &layer("Art", true, &(square(10, 10) + &text_object(0)))), &doc);
|
||||
let mut pdf = pdf_part(10.0, 86.0);
|
||||
let mut page = pdf.artboards[0].clone();
|
||||
(page.id, page.rect) = (2, vectorcraft_geom::Rect::new(120.0, 0.0, 220.0, 100.0));
|
||||
pdf.artboards.push(page);
|
||||
let style = vectorcraft_doc::CharStyle { size: 14.0, ..Default::default() };
|
||||
let text = vectorcraft_doc::TextObject::point(vectorcraft_geom::Point::new(178.5, 50.0), "Hi there", style);
|
||||
let (id, layer_id) = (pdf.alloc_id(), pdf.layers[0].id);
|
||||
let mut node = Node::new(id, NodeKind::Text(Box::new(text)));
|
||||
let paint = vectorcraft_color::Paint::Pattern { pattern: "Dots".into(), xf: vectorcraft_geom::Affine::IDENTITY };
|
||||
node.appearance = vectorcraft_doc::Appearance {
|
||||
items: vec![vectorcraft_doc::AppearanceItem::Fill(vectorcraft_doc::FillLayer::new(paint))],
|
||||
..Default::default()
|
||||
}
|
||||
.into();
|
||||
pdf.insert(Some(layer_id), 1, node).unwrap();
|
||||
let (d, w, read) = crate::layered_ai(&private(&art), pdf, vec![], false, false, 1.0);
|
||||
assert!(read, "{w:?}");
|
||||
let texts: Vec<&Node> = d.layers[0].children().unwrap().iter().filter(|n| matches!(n.kind, NodeKind::Text(_))).map(|n| &**n).collect();
|
||||
assert_eq!(texts.len(), 1, "{w:?}");
|
||||
let Some(vectorcraft_color::Paint::Pattern { xf, .. }) = texts[0].appearance.fill().map(|f| &f.paint) else {
|
||||
panic!("the page's type keeps its pattern fill: {:?}", texts[0].appearance);
|
||||
};
|
||||
assert_eq!(*xf, vectorcraft_geom::Affine::translate(vectorcraft_geom::Vec2::new(-120.0, 150.0)));
|
||||
}
|
||||
|
||||
/// A global process colour (`Xk`) or a pattern fill on a sublayer no longer sends a `.ai` to its
|
||||
/// PDF part, which has only the top-level layers (#1025).
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user