mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 05:55:32 +00:00
Merge pull request #7730 from dotCooCoo/fix/hpgl-9slice-art-bounds
Export: the HP-GL plot of the art's bounds places 9-slice and filtered symbol art before measuring
This commit is contained in:
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
use serde::Deserialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
use vectorcraft_doc::{Artboard, Document, Node, NodeKind, SymbolOverride};
|
||||
use vectorcraft_doc::{Artboard, Document, Node, NodeKind, SYMBOL_SIZES, SymbolOverride};
|
||||
use vectorcraft_geom::Rect;
|
||||
use vectorcraft_render::AntiAlias;
|
||||
use vectorcraft_render::encode::jpeg::{self, JpegOptions};
|
||||
@@ -454,7 +454,12 @@ pub fn export_art_bounds(doc: &Document, format: &str) -> Option<Rect> {
|
||||
/// make the same appearance changes to its art are measured on a copy of the symbol of their own,
|
||||
/// in which each object is judged with those changes ([`as_drawn`]); the objects taken out of
|
||||
/// a dynamic symbol's art leave empty groups in their places, by which instances find the objects
|
||||
/// they change.
|
||||
/// they change. Each copy keeps its symbol's 9-slice guides and natural size. An instance whose art
|
||||
/// goes through 9-slice guides at any depth is measured as its art placed through them
|
||||
/// ([`placed_instances`]), with the objects `left_out` picks hidden after placing: the guides cut
|
||||
/// the box of the whole art. Where an instance stays one, the art of a symbol with 9-slice guides,
|
||||
/// and of the symbols whose instances are in that art at any depth ([`sliced_art`]), is measured
|
||||
/// whole.
|
||||
fn bounds_without(doc: &Document, left_out: &dyn Fn(&Node) -> bool) -> Option<Rect> {
|
||||
// The symbols instances show (the first of each name).
|
||||
let mut names = HashSet::new();
|
||||
@@ -492,12 +497,15 @@ fn bounds_without(doc: &Document, left_out: &dyn Fn(&Node) -> bool) -> Option<Re
|
||||
let Some(s) = d.symbols.get(*i).cloned() else { continue };
|
||||
for (k, c) in sets.get(s.name.as_str()).into_iter().flatten().flatten().enumerate() {
|
||||
copies.push((d.symbols.len(), c.as_slice()));
|
||||
d.symbols.push(vectorcraft_doc::Symbol { name: format!("{}\u{0}{k}", s.name), ..s.clone() });
|
||||
let name = format!("{}\u{0}{k}", s.name);
|
||||
copy_placement(&mut d, &s.name, &name);
|
||||
d.symbols.push(vectorcraft_doc::Symbol { name, ..s.clone() });
|
||||
}
|
||||
}
|
||||
d
|
||||
});
|
||||
let src = remapped_doc.as_ref().unwrap_or(doc);
|
||||
let whole = sliced_art(src);
|
||||
// The symbols to measure with the changes their instances make (`None` for a static symbol),
|
||||
// without the dynamic symbols measured whole.
|
||||
let entries: Vec<(usize, Option<&[&SymbolOverride]>)> = shown
|
||||
@@ -517,11 +525,25 @@ fn bounds_without(doc: &Document, left_out: &dyn Fn(&Node) -> bool) -> Option<Re
|
||||
}
|
||||
let left_out =
|
||||
&|n: &Node| left_out(n) || matches!(&n.kind, NodeKind::SymbolInstance { symbol, .. } if empty.contains(symbol.as_str()) && !has_effects(n));
|
||||
// The instances whose art goes through 9-slice guides, at any depth, are measured as their art
|
||||
// placed by those guides without the objects `left_out` picks, and without a clip group that
|
||||
// holds only such objects.
|
||||
let layers = hidden_nodes(&src.layers, left_out);
|
||||
let placing = holding(src, &whole);
|
||||
let layers = if placing.is_empty() {
|
||||
layers
|
||||
} else {
|
||||
let picked = &|n: &Node| {
|
||||
left_out(n) || matches!(n.kind, NodeKind::Group { clip: true, .. }) && all_left_out(n, left_out, &empty, None, &mut Vec::new())
|
||||
};
|
||||
let mut budget = 0;
|
||||
let from = layers.as_deref().unwrap_or(&src.layers);
|
||||
Some(from.iter().map(|l| placed_instances(src, l, &placing, picked, &mut budget, 0).unwrap_or_else(|| l.clone())).collect())
|
||||
};
|
||||
let symbols: Vec<(usize, Arc<Node>)> = entries
|
||||
.iter()
|
||||
.filter_map(|(i, c)| {
|
||||
let s = src.symbols.get(*i).filter(|s| !empty.contains(s.name.as_str()))?;
|
||||
let s = src.symbols.get(*i).filter(|s| !empty.contains(s.name.as_str()) && !whole.contains(s.name.as_str()))?;
|
||||
without_nodes(&s.art, left_out, *c, &mut Vec::new()).map(|art| (*i, art))
|
||||
})
|
||||
.collect();
|
||||
@@ -540,6 +562,135 @@ fn bounds_without(doc: &Document, left_out: &dyn Fn(&Node) -> bool) -> Option<Re
|
||||
vectorcraft_render::encode::art_bounds(&d)
|
||||
}
|
||||
|
||||
/// Give symbol `to` of `d` the 9-slice guides and the natural size that symbol `from` has.
|
||||
fn copy_placement(d: &mut Document, from: &str, to: &str) {
|
||||
if let Some(guides) = d.symbol_nine_slice(from) {
|
||||
d.set_symbol_nine_slice(to, Some(guides));
|
||||
}
|
||||
if let Some(size) = d.unknown.get(SYMBOL_SIZES).and_then(|m| m.get(from)).cloned()
|
||||
&& let Some(sizes) = d.unknown.get_mut(SYMBOL_SIZES).and_then(Value::as_object_mut)
|
||||
{
|
||||
sizes.insert(to.to_string(), size);
|
||||
}
|
||||
}
|
||||
|
||||
/// The names of the symbols of `doc` with 9-slice guides, and of the symbols whose instances are in
|
||||
/// the art of those at any depth.
|
||||
fn sliced_art(doc: &Document) -> HashSet<&str> {
|
||||
let mut arts: HashMap<&str, &Arc<Node>> = HashMap::new();
|
||||
for s in &doc.symbols {
|
||||
arts.entry(s.name.as_str()).or_insert(&s.art);
|
||||
}
|
||||
let mut todo: Vec<&str> = arts.keys().copied().filter(|name| doc.symbol_nine_slice(name).is_some()).collect();
|
||||
let mut found = HashSet::new();
|
||||
while let Some(name) = todo.pop() {
|
||||
if !found.insert(name) {
|
||||
continue;
|
||||
}
|
||||
if let Some(&art) = arts.get(name) {
|
||||
art.walk(&mut |n| {
|
||||
if let NodeKind::SymbolInstance { symbol, .. } = &n.kind {
|
||||
todo.push(symbol.as_str());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
/// The names of the symbols of `doc` in `sliced`, and of the symbols whose art holds an instance of
|
||||
/// one of those at any depth.
|
||||
fn holding<'a>(doc: &'a Document, sliced: &HashSet<&'a str>) -> HashSet<&'a str> {
|
||||
let mut held_by: HashMap<&str, Vec<&str>> = HashMap::new();
|
||||
let mut seen = HashSet::new();
|
||||
for s in doc.symbols.iter().filter(|s| seen.insert(s.name.as_str())) {
|
||||
s.art.walk(&mut |n| {
|
||||
if let NodeKind::SymbolInstance { symbol, .. } = &n.kind {
|
||||
held_by.entry(symbol.as_str()).or_default().push(s.name.as_str());
|
||||
}
|
||||
});
|
||||
}
|
||||
let mut found = HashSet::new();
|
||||
let mut todo: Vec<&str> = sliced.iter().copied().collect();
|
||||
while let Some(name) = todo.pop() {
|
||||
if found.insert(name) {
|
||||
todo.extend(held_by.get(name).into_iter().flatten().copied());
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
/// The most instances deep [`placed_instances`] places symbol art.
|
||||
const MAX_PLACED_DEPTH: usize = 32;
|
||||
|
||||
/// `n` with each instance of the `placing` symbols in it, and in its layers and groups at any depth,
|
||||
/// made a group with the instance's appearance that holds the instance's art (with its changes and
|
||||
/// stain) as `doc` places it, through the symbol's 9-slice guides when it has them, without the
|
||||
/// objects `picked` picks ([`without_nodes`]; hidden when it picks the whole art); `None` when `n`
|
||||
/// holds no such instance, or is hidden. An instance on a perspective grid, one
|
||||
/// [`MAX_PLACED_DEPTH`] instances deep, one in another kind of object (a blend, for example), and
|
||||
/// one whose art would take the objects placed in all (`budget`) past
|
||||
/// [`vectorcraft_doc::MAX_SYMBOL_NODES`] stay instances.
|
||||
/// Their symbols' art is measured whole, as taking objects out of it would change the box the
|
||||
/// guides cut, and the page can come out larger than the plot.
|
||||
fn placed_instances(
|
||||
doc: &Document,
|
||||
n: &Arc<Node>,
|
||||
placing: &HashSet<&str>,
|
||||
picked: &dyn Fn(&Node) -> bool,
|
||||
budget: &mut u64,
|
||||
depth: usize,
|
||||
) -> Option<Arc<Node>> {
|
||||
// Hidden art is not plotted, and placing it would take from the budget of the art that is.
|
||||
if !n.visible {
|
||||
return None;
|
||||
}
|
||||
match &n.kind {
|
||||
NodeKind::SymbolInstance { symbol, xf } => {
|
||||
if !placing.contains(symbol.as_str()) || depth >= MAX_PLACED_DEPTH || n.projection().is_some() {
|
||||
return None;
|
||||
}
|
||||
let sym = doc.symbols.iter().find(|s| s.name == *symbol)?;
|
||||
let art = vectorcraft_doc::symbol_overrides::instance_art(&sym.art, n);
|
||||
let most = doc.symbol_slice_map(symbol, *xf).map_or(art.count_with_masks() as u64, |m| m.most_placed(&art, doc));
|
||||
if budget.saturating_add(most) > vectorcraft_doc::MAX_SYMBOL_NODES {
|
||||
return None;
|
||||
}
|
||||
let art = Arc::new(doc.place_symbol_art(symbol, &art, *xf)?);
|
||||
*budget = budget.saturating_add(art.count_with_masks() as u64);
|
||||
let art = placed_instances(doc, &art, placing, picked, budget, depth + 1).unwrap_or(art);
|
||||
let art = if picked(&art) {
|
||||
let mut hidden = (*art).clone();
|
||||
hidden.visible = false;
|
||||
Arc::new(hidden)
|
||||
} else {
|
||||
without_nodes(&art, picked, None, &mut Vec::new()).unwrap_or(art)
|
||||
};
|
||||
let mut g = (**n).clone();
|
||||
g.kind = NodeKind::Group { children: vec![art], clip: false };
|
||||
Some(Arc::new(g))
|
||||
}
|
||||
NodeKind::Layer { children, .. } | NodeKind::Group { children, .. } => {
|
||||
let mut out: Option<Vec<Arc<Node>>> = None;
|
||||
for (i, c) in children.iter().enumerate() {
|
||||
if let Some(new) = placed_instances(doc, c, placing, picked, budget, depth)
|
||||
&& let Some(slot) = out.get_or_insert_with(|| children.clone()).get_mut(i)
|
||||
{
|
||||
*slot = new;
|
||||
}
|
||||
}
|
||||
out.map(|c| {
|
||||
let mut m = (**n).clone();
|
||||
if let Some(children) = m.children_mut() {
|
||||
*children = c;
|
||||
}
|
||||
Arc::new(m)
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The appearance changes instance `n` makes to its symbol's art, without those that change nothing.
|
||||
fn own_changes(n: &Node) -> Vec<&SymbolOverride> {
|
||||
n.extras().symbol_overrides.iter().filter(|o| !o.is_empty()).collect()
|
||||
@@ -600,9 +751,10 @@ fn all_left_out(n: &Node, left_out: &dyn Fn(&Node) -> bool, empty: &HashSet<&str
|
||||
|
||||
/// Add the sets of appearance changes that the instances in `n` and its subtree make to the art of
|
||||
/// the symbols `sets` names, each set once, and those of the instances in the art of the symbols the
|
||||
/// instances show (`walked`, the symbols already looked in). The instances that `left_out` picks or
|
||||
/// that are in an object it picks are left out, and with `hidden` (in a layer) the hidden ones and
|
||||
/// those in a hidden object. A symbol with more than [`MAX_ART_COPIES`] sets gets `None`.
|
||||
/// instances show (`walked`, the symbols already looked in). With `hidden` (in a layer), the
|
||||
/// instances that are hidden or that `left_out` picks, and those in such an object, are left out.
|
||||
/// In symbol art every instance counts, since an instance of the symbol can change the objects
|
||||
/// that hold it. A symbol with more than [`MAX_ART_COPIES`] sets gets `None`.
|
||||
fn drawn_changes<'a>(
|
||||
doc: &'a Document,
|
||||
n: &'a Node,
|
||||
@@ -611,7 +763,7 @@ fn drawn_changes<'a>(
|
||||
walked: &mut HashSet<&'a str>,
|
||||
sets: &mut HashMap<&'a str, Option<Vec<Vec<&'a SymbolOverride>>>>,
|
||||
) {
|
||||
if hidden && !n.visible || left_out(n) {
|
||||
if hidden && (!n.visible || left_out(n)) {
|
||||
return;
|
||||
}
|
||||
if let NodeKind::SymbolInstance { symbol, .. } = &n.kind {
|
||||
|
||||
@@ -338,6 +338,152 @@ fn a_plot_of_the_arts_bounds_leaves_out_images_in_dynamic_symbol_art() {
|
||||
assert!(e.contains("no visible art"), "{e}");
|
||||
}
|
||||
|
||||
/// A rectangle filled black, or one that paints nothing.
|
||||
fn plot_rect(d: &mut vectorcraft_doc::Document, r: vectorcraft_geom::Rect, painted: bool) -> std::sync::Arc<vectorcraft_doc::Node> {
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
let fill = if painted { Paint::solid(Color::BLACK) } else { Paint::None };
|
||||
let appearance = vectorcraft_doc::Appearance::basic(fill, Paint::None, 0.0);
|
||||
std::sync::Arc::new(vectorcraft_doc::Node::path(d.alloc_id(), vectorcraft_geom::shapes::rectangle(r), appearance))
|
||||
}
|
||||
|
||||
/// An instance of symbol `symbol` placed by `xf`.
|
||||
fn plot_instance(d: &mut vectorcraft_doc::Document, symbol: &str, xf: vectorcraft_geom::Affine) -> vectorcraft_doc::Node {
|
||||
vectorcraft_doc::Node::new(d.alloc_id(), NodeKind::SymbolInstance { symbol: symbol.into(), xf })
|
||||
}
|
||||
|
||||
/// Symbol `name` holding `art` in a group.
|
||||
fn plot_symbol(d: &mut vectorcraft_doc::Document, name: &str, art: Vec<std::sync::Arc<vectorcraft_doc::Node>>, dynamic: bool) {
|
||||
let group = vectorcraft_doc::Node::group(d.alloc_id(), art);
|
||||
d.symbols.push(vectorcraft_doc::Symbol { dynamic, name: name.into(), art: std::sync::Arc::new(group) });
|
||||
}
|
||||
|
||||
/// Puts `n` on the first layer of `d`.
|
||||
fn plot_on_layer(d: &mut vectorcraft_doc::Document, n: vectorcraft_doc::Node) {
|
||||
std::sync::Arc::make_mut(&mut d.layers[0]).children_mut().unwrap().push(std::sync::Arc::new(n));
|
||||
}
|
||||
|
||||
/// The art of a symbol with 9-slice guides, static or dynamic, is placed whole and measured
|
||||
/// without what the plot leaves out: a frame that paints nothing sets where the guides place the
|
||||
/// painted art inside it, and is not measured itself.
|
||||
#[test]
|
||||
fn a_plot_of_the_arts_bounds_places_9_slice_symbol_art_by_all_of_it() {
|
||||
use vectorcraft_geom::{Affine, Rect};
|
||||
// Static and dynamic, and with the instance's fill staining the art.
|
||||
for (dynamic, stained) in [(false, false), (true, false), (false, true)] {
|
||||
let mut d = vectorcraft_doc::Document::new(400.0, 400.0);
|
||||
let frame = plot_rect(&mut d, Rect::new(0.0, 0.0, 100.0, 100.0), false);
|
||||
let square = plot_rect(&mut d, Rect::new(40.0, 40.0, 60.0, 60.0), true);
|
||||
plot_symbol(&mut d, "Panel", vec![frame, square], dynamic);
|
||||
d.set_symbol_nine_slice("Panel", Some(vectorcraft_doc::SymbolNineSlice::default()));
|
||||
let mut wide = plot_instance(&mut d, "Panel", Affine::scale_non_uniform(2.0, 1.0));
|
||||
if stained {
|
||||
let red = vectorcraft_color::Paint::solid(vectorcraft_color::Color::rgb(1.0, 0.0, 0.0));
|
||||
wide.appearance = vectorcraft_doc::Appearance::basic(red, vectorcraft_color::Paint::None, 0.0).into();
|
||||
}
|
||||
plot_on_layer(&mut d, wide);
|
||||
// Scaled 2 times across, the middle column maps x to 3x - 50: the square draws from x 70
|
||||
// to 130.
|
||||
let b = super::export_art_bounds(&d, "hpgl").unwrap();
|
||||
let exact = [(b.x0, 70.0), (b.y0, 40.0), (b.x1, 130.0), (b.y1, 60.0)].iter().all(|(a, e)| (a - e).abs() < 1e-6);
|
||||
assert!(exact, "dynamic: {dynamic}, stained: {stained}, {b:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// In art placed through 9-slice guides, a group with a fill of its own keeps the path it paints,
|
||||
/// which paints nothing by itself.
|
||||
#[test]
|
||||
fn a_plot_of_the_arts_bounds_keeps_what_a_group_paints_in_9_slice_symbol_art() {
|
||||
use vectorcraft_geom::{Affine, Rect};
|
||||
let mut d = vectorcraft_doc::Document::new(400.0, 400.0);
|
||||
let frame = plot_rect(&mut d, Rect::new(0.0, 0.0, 100.0, 100.0), false);
|
||||
let bare = plot_rect(&mut d, Rect::new(40.0, 40.0, 60.0, 60.0), false);
|
||||
let mut group = vectorcraft_doc::Node::group(d.alloc_id(), vec![bare]);
|
||||
let black = vectorcraft_color::Paint::solid(vectorcraft_color::Color::BLACK);
|
||||
group.appearance = vectorcraft_doc::Appearance::basic(black, vectorcraft_color::Paint::None, 0.0).into();
|
||||
plot_symbol(&mut d, "Panel", vec![frame, std::sync::Arc::new(group)], false);
|
||||
d.set_symbol_nine_slice("Panel", Some(vectorcraft_doc::SymbolNineSlice::default()));
|
||||
let wide = plot_instance(&mut d, "Panel", Affine::scale_non_uniform(2.0, 1.0));
|
||||
plot_on_layer(&mut d, wide);
|
||||
let b = super::export_art_bounds(&d, "hpgl").expect("the group's path is plotted");
|
||||
let exact = [(b.x0, 70.0), (b.y0, 40.0), (b.x1, 130.0), (b.y1, 60.0)].iter().all(|(a, e)| (a - e).abs() < 1e-6);
|
||||
assert!(exact, "{b:?}");
|
||||
}
|
||||
|
||||
/// A symbol without guides in the art of a symbol with 9-slice guides is measured without what the
|
||||
/// plot leaves out, in the placed art and in an instance of its own.
|
||||
#[test]
|
||||
fn a_plot_of_the_arts_bounds_leaves_out_unplotted_art_of_symbols_in_9_slice_art() {
|
||||
use vectorcraft_geom::{Affine, Rect};
|
||||
let mut d = vectorcraft_doc::Document::new(400.0, 400.0);
|
||||
let far = plot_rect(&mut d, Rect::new(45.0, -500.0, 55.0, -480.0), false);
|
||||
let dot = plot_rect(&mut d, Rect::new(45.0, 45.0, 55.0, 55.0), true);
|
||||
plot_symbol(&mut d, "Dot", vec![far, dot], false);
|
||||
let frame = plot_rect(&mut d, Rect::new(0.0, 0.0, 100.0, 100.0), false);
|
||||
let inner = std::sync::Arc::new(plot_instance(&mut d, "Dot", Affine::IDENTITY));
|
||||
plot_symbol(&mut d, "Panel", vec![frame, inner], false);
|
||||
d.set_symbol_nine_slice("Panel", Some(vectorcraft_doc::SymbolNineSlice::default()));
|
||||
let wide = plot_instance(&mut d, "Panel", Affine::scale_non_uniform(2.0, 1.0));
|
||||
plot_on_layer(&mut d, wide);
|
||||
let near = |b: Rect, e: [f64; 4]| [b.x0, b.y0, b.x1, b.y1].iter().zip(e).all(|(a, e)| (a - e).abs() < 1e-6);
|
||||
// The dot's box is in the middle column, which maps x to 3x - 50.
|
||||
let b = super::export_art_bounds(&d, "hpgl").unwrap();
|
||||
assert!(near(b, [85.0, 45.0, 115.0, 55.0]), "{b:?}");
|
||||
let own = plot_instance(&mut d, "Dot", Affine::translate((200.0, 0.0)));
|
||||
plot_on_layer(&mut d, own);
|
||||
let b = super::export_art_bounds(&d, "hpgl").unwrap();
|
||||
assert!(near(b, [85.0, 45.0, 255.0, 55.0]), "{b:?}");
|
||||
}
|
||||
|
||||
/// The copy of a dynamic symbol that an instance's changes are measured on keeps the symbol's
|
||||
/// 9-slice guides.
|
||||
#[test]
|
||||
fn a_plot_of_the_arts_bounds_measures_changed_9_slice_instances_by_their_guides() {
|
||||
use vectorcraft_geom::{Affine, Rect};
|
||||
let mut d = vectorcraft_doc::Document::new(400.0, 400.0);
|
||||
let bar = plot_rect(&mut d, Rect::new(10.0, 40.0, 70.0, 60.0), true);
|
||||
plot_symbol(&mut d, "Bar", vec![bar], false);
|
||||
let frame = plot_rect(&mut d, Rect::new(0.0, 0.0, 100.0, 100.0), false);
|
||||
let inner = std::sync::Arc::new(plot_instance(&mut d, "Bar", Affine::IDENTITY));
|
||||
plot_symbol(&mut d, "Panel", vec![frame, inner], true);
|
||||
d.set_symbol_nine_slice("Panel", Some(vectorcraft_doc::SymbolNineSlice::default()));
|
||||
let mut wide = plot_instance(&mut d, "Panel", Affine::scale_non_uniform(2.0, 1.0));
|
||||
wide.extras_mut().symbol_overrides =
|
||||
vec![vectorcraft_doc::SymbolOverride { path: vec![0], fill: Some(vectorcraft_color::Paint::None), ..Default::default() }];
|
||||
plot_on_layer(&mut d, wide);
|
||||
// The bar crosses the left guide and is placed by the middle column's map, x to 3x - 50: it
|
||||
// draws from x -20 to 160.
|
||||
let b = super::export_art_bounds(&d, "hpgl").unwrap();
|
||||
let exact = |b: Rect| [(b.x0, -20.0), (b.y0, 40.0), (b.x1, 160.0), (b.y1, 60.0)].iter().all(|(a, e)| (a - e).abs() < 1e-6);
|
||||
assert!(exact(b), "{b:?}");
|
||||
// The same instance in the art of a symbol without guides.
|
||||
let wide = std::sync::Arc::make_mut(&mut d.layers[0]).children_mut().unwrap().pop().unwrap();
|
||||
plot_symbol(&mut d, "Holder", vec![wide], false);
|
||||
let holder = plot_instance(&mut d, "Holder", Affine::IDENTITY);
|
||||
plot_on_layer(&mut d, holder);
|
||||
let b = super::export_art_bounds(&d, "hpgl").unwrap();
|
||||
assert!(exact(b), "{b:?}");
|
||||
}
|
||||
|
||||
/// An instance in a dynamic symbol's art, in a group at 0% opacity that an instance of the symbol
|
||||
/// brings back to full opacity, is measured with the paint it gives its own symbol's art.
|
||||
#[test]
|
||||
fn a_plot_of_the_arts_bounds_measures_instances_that_an_instance_brings_back() {
|
||||
use vectorcraft_geom::{Affine, Rect};
|
||||
let mut d = vectorcraft_doc::Document::new(400.0, 400.0);
|
||||
let bare = plot_rect(&mut d, Rect::new(300.0, 300.0, 320.0, 320.0), false);
|
||||
plot_symbol(&mut d, "Inner", vec![bare], true);
|
||||
let mut inner = plot_instance(&mut d, "Inner", Affine::IDENTITY);
|
||||
let black = vectorcraft_color::Paint::solid(vectorcraft_color::Color::BLACK);
|
||||
inner.extras_mut().symbol_overrides = vec![vectorcraft_doc::SymbolOverride { path: vec![0], fill: Some(black), ..Default::default() }];
|
||||
plot_symbol(&mut d, "Outer", vec![std::sync::Arc::new(inner)], true);
|
||||
std::sync::Arc::make_mut(&mut d.symbols[1].art).opacity = 0.0;
|
||||
let mut outer = plot_instance(&mut d, "Outer", Affine::IDENTITY);
|
||||
outer.extras_mut().symbol_overrides = vec![vectorcraft_doc::SymbolOverride { path: vec![], opacity: Some(1.0), ..Default::default() }];
|
||||
plot_on_layer(&mut d, outer);
|
||||
let b = super::export_art_bounds(&d, "hpgl").expect("the painted rectangle is plotted");
|
||||
assert!((b.x0 - 300.0).abs() < 1e-6 && (b.x1 - 320.0).abs() < 1e-6, "{b:?}");
|
||||
}
|
||||
|
||||
/// A placed image with a Vector Halftone effect is measured when the effect makes dots, and left
|
||||
/// out when it would take more cells than the effect makes.
|
||||
#[test]
|
||||
|
||||
+2
-1
@@ -1129,7 +1129,8 @@ gradient meshes whose every point is at 0% opacity, and paths without effects wh
|
||||
An image or a mesh with a Crop Marks effect, or with a Vector Halftone effect that makes dots, is measured.
|
||||
Such objects in the art of a symbol are left out of its instances too, and an instance whose symbol holds nothing
|
||||
else is left out. In the art of a dynamic symbol, an object that an instance's fill, stroke or opacity makes
|
||||
the plot draw is measured.
|
||||
the plot draw is measured. The art of an instance that goes through 9-slice guides is placed through them first, and
|
||||
such objects are then left out of the placed art.
|
||||
A document whose only visible art is left out is refused, and Export As refuses an HP-GL plot of it before the save
|
||||
dialog. A PDF of that page, from either command, carries no Preserve Editing data; when Preserve Editing is on (as it
|
||||
is in VectorCraft Default), the export comes back with a warning that says so. A `.ai` export with
|
||||
|
||||
Reference in New Issue
Block a user