mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 20:36:02 +00:00
Merge pull request #7146 from jmverges/fix/7141-page-check-typed-envelopes
.ai and EPS import: envelopes of type stay live where other art differs from the page
This commit is contained in:
@@ -39,6 +39,7 @@ pub(crate) use envelope::DRAWN as ENVELOPE_DRAWN;
|
||||
pub(crate) use envelope::MAX_CHECKED;
|
||||
pub(crate) use envelope::TYPE_DRAWN as ENVELOPE_TYPE_DRAWN;
|
||||
pub(crate) use envelope::{GridPoint as EnvelopeGridPoint, PLUGIN as DEFORM_PLUGIN, outline as envelope_outline, upright as envelope_upright};
|
||||
pub(crate) use envelope::{MAX_ANY as ENVELOPE_MAX_ANY, MAX_SOLID as ENVELOPE_MAX_SOLID};
|
||||
pub(crate) use envelope::{WARPED_TYPE_DRAWN, WarpedType};
|
||||
pub(crate) use frames::text_frames;
|
||||
pub use obj::{NameEncoding, Names};
|
||||
|
||||
@@ -73,9 +73,9 @@ const MOVED_SLACK: f64 = 0.5;
|
||||
const KNOWN_BITS: i64 = 0b111 | 1 << 16 | 1 << 17;
|
||||
/// More of the inked pixels than this differing, each with all its neighbours, and the envelope
|
||||
/// doesn't come out as drawn.
|
||||
const MAX_SOLID: f64 = 0.03;
|
||||
pub(crate) const MAX_SOLID: f64 = 0.03;
|
||||
/// More of the inked pixels than this differing at all, and the envelope doesn't come out as drawn.
|
||||
const MAX_ANY: f64 = 0.1;
|
||||
pub(crate) const MAX_ANY: f64 = 0.1;
|
||||
|
||||
/// The whole number of entry `key`: `None` when it is missing, `Some(None)` when it isn't one.
|
||||
fn whole(o: &Obj, key: &str) -> Option<Option<i64>> {
|
||||
@@ -648,6 +648,9 @@ pub(crate) enum TypeKept {
|
||||
Budget,
|
||||
/// Its live envelope doesn't draw as its drawn art.
|
||||
Differs,
|
||||
/// A page that the layers, which don't draw as it, were compared with holds it against them
|
||||
/// ([`WarpedType::open_alone`]).
|
||||
Page,
|
||||
}
|
||||
|
||||
/// What [`WarpedType::settle_envelopes`] did.
|
||||
@@ -657,9 +660,10 @@ pub(crate) struct TypedSettled {
|
||||
pub(crate) live: usize,
|
||||
/// Why each of the others kept its drawn art.
|
||||
pub(crate) kept: Vec<TypeKept>,
|
||||
/// Each live envelope's id, with what takes its place again when the layers with it don't draw
|
||||
/// as the page: its hidden type, then its drawn art.
|
||||
pub(crate) put_back: Vec<(NodeId, Vec<Node>)>,
|
||||
/// Each live envelope's place among the envelopes of type (in the order they were read) and
|
||||
/// id, with what takes its place again when the layers with it don't draw as the page: its
|
||||
/// hidden type, then its drawn art.
|
||||
pub(crate) put_back: Vec<(usize, NodeId, Vec<Node>)>,
|
||||
}
|
||||
|
||||
/// The text objects read with a Warp and the envelopes of type, and what the import has left of the
|
||||
@@ -672,6 +676,9 @@ pub struct WarpedType {
|
||||
pub(super) checks: usize,
|
||||
pub(super) spent: Spent,
|
||||
pub(super) page: Option<Rect>,
|
||||
/// The envelopes of type that may open live in layers built without a page, by their place
|
||||
/// among them ([`Self::open_alone`]): none when `None`.
|
||||
pub(super) alone: Option<BTreeSet<usize>>,
|
||||
}
|
||||
|
||||
impl WarpedType {
|
||||
@@ -682,14 +689,33 @@ impl WarpedType {
|
||||
|
||||
/// These, for layers `compared` with a page. Layers that aren't leave the envelopes of type
|
||||
/// their drawn art and their hidden type, with the note ([`TYPE_DRAWN`]) added to `notes`: a
|
||||
/// live envelope opens only where the page chooses it.
|
||||
/// live envelope opens only where the page chooses it. Unless [`Self::open_alone`] names some:
|
||||
/// then they all wait, as with a page, and only those named may stay live.
|
||||
pub(crate) fn compared(mut self, compared: bool, notes: &mut Vec<String>) -> Self {
|
||||
if !compared && !std::mem::take(&mut self.envelopes).is_empty() && !notes.iter().any(|n| n == TYPE_DRAWN) {
|
||||
if !compared && self.alone.is_none() && !std::mem::take(&mut self.envelopes).is_empty() && !notes.iter().any(|n| n == TYPE_DRAWN) {
|
||||
notes.push(TYPE_DRAWN.into());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Let the envelopes of type at `places` (among them, in the order they were read) open live in
|
||||
/// layers built without a page, where they draw as their drawn art: a page these same layers,
|
||||
/// read the same way, were compared with weighed them and didn't hold them against the layers'
|
||||
/// difference from it. All the envelopes of type are settled as they were then, in the same
|
||||
/// order and with the same checks, so each that opens live draws as it was weighed; then the
|
||||
/// others get their drawn art back ([`Self::settle_envelopes`]). `count` is how many envelopes
|
||||
/// of type that reading had: a reading with another number of them names none.
|
||||
pub(crate) fn open_alone(&mut self, places: &[usize], count: usize) {
|
||||
if self.envelopes.len() == count {
|
||||
self.alone = Some(places.iter().copied().filter(|i| *i < count).collect());
|
||||
}
|
||||
}
|
||||
|
||||
/// How many envelopes of type wait for their type.
|
||||
pub(crate) fn envelope_count(&self) -> usize {
|
||||
self.envelopes.len()
|
||||
}
|
||||
|
||||
/// Make each envelope of type live where it draws as its drawn art, its contents' text slots
|
||||
/// holding their type: `type_of` gives, for a slot's id, the hidden text object it became in
|
||||
/// `doc` and the type the contents take in its place (the slot's visibility and lock are the
|
||||
@@ -713,16 +739,28 @@ impl WarpedType {
|
||||
doc.layers.iter().for_each(|l| collect_shown(l, &mut shown));
|
||||
let envelopes = std::mem::take(&mut self.envelopes);
|
||||
let mut waiting = envelopes.len();
|
||||
for envelope in envelopes {
|
||||
for (place, envelope) in envelopes.into_iter().enumerate() {
|
||||
waiting -= 1;
|
||||
match self.typed_live(doc, envelope, &type_of, &shown, waiting) {
|
||||
Ok(put_back) => {
|
||||
Ok((id, put_back)) => {
|
||||
settled.live += 1;
|
||||
settled.put_back.push(put_back);
|
||||
settled.put_back.push((place, id, put_back));
|
||||
}
|
||||
Err(why) => settled.kept.push(why),
|
||||
}
|
||||
}
|
||||
// Built without a page, the envelopes [`Self::open_alone`] doesn't name get their drawn art
|
||||
// and hidden type back. One inside another that got its own back went with that art.
|
||||
if let Some(free) = &self.alone {
|
||||
let (stay, back): (Vec<_>, Vec<_>) = std::mem::take(&mut settled.put_back).into_iter().partition(|(place, ..)| free.contains(place));
|
||||
for (_, id, nodes) in back {
|
||||
if doc.insert_beside(vec![(id, nodes)], true).is_ok() || doc.node(id).is_none() {
|
||||
settled.live = settled.live.saturating_sub(1);
|
||||
settled.kept.push(TypeKept::Page);
|
||||
}
|
||||
}
|
||||
settled.put_back = stay;
|
||||
}
|
||||
settled
|
||||
}
|
||||
|
||||
|
||||
@@ -1367,6 +1367,7 @@ impl<'a> Reader<'a> {
|
||||
checks: self.envelope_checks,
|
||||
spent: self.envelope_spent,
|
||||
page,
|
||||
alone: None,
|
||||
},
|
||||
styles_read,
|
||||
text_patterns,
|
||||
|
||||
@@ -8280,7 +8280,7 @@ fn warped_type_on_the_page_spends_what_is_left_of_the_budget() {
|
||||
let copy = |w: &super::envelope::WarpedLook| super::envelope::WarpedLook { slot: w.slot, look: w.look.clone(), left_out: None };
|
||||
let page = Some(Rect::new(0.0, 60.0, 60.0, 100.0));
|
||||
let settle = |w: &super::envelope::WarpedLook, spent| {
|
||||
let warped = super::envelope::WarpedType { looks: vec![copy(w)], envelopes: vec![], checks: 1, spent, page };
|
||||
let warped = super::envelope::WarpedType { looks: vec![copy(w)], envelopes: vec![], checks: 1, spent, page, alone: None };
|
||||
warped.settle(&mut doc.clone(), Some, &Default::default(), &mut vec![])
|
||||
};
|
||||
let spent = || super::envelope::Spent { any: MAX_DIFFERENCE / 2.0, solid: MAX_SOLID_DIFFERENCE / 2.0 };
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::compare::{
|
||||
MAX_DIFFERENCE, MAX_PAGES_COMPARED, MAX_PIXELS_COMPARED, MAX_SOLID_DIFFERENCE, PageView, compare, comparison_samples, difference, pixels_compared,
|
||||
MAX_DIFFERENCE, MAX_PAGES_COMPARED, MAX_PIXELS_COMPARED, MAX_SOLID_DIFFERENCE, PAST_THE_PAGE, PageView, compare, comparison_samples, difference,
|
||||
pixels_compared,
|
||||
};
|
||||
use super::plan::{made_only, make_slots, plan, readable_all};
|
||||
use super::slots::{
|
||||
@@ -230,6 +231,24 @@ pub(super) fn build_from(
|
||||
outlined: bool,
|
||||
trust: bool,
|
||||
unit: f64,
|
||||
) -> Result<(Document, Vec<String>), String> {
|
||||
build_weighing(structure, data, visible, page, outlined, trust, unit, &mut None)
|
||||
}
|
||||
|
||||
/// [`build_from`]. When the layers with their live envelopes don't draw as the page, and don't with
|
||||
/// the drawn art of every live envelope put back either, `free` is set to the envelopes of type
|
||||
/// (by their place among them, as read) that opened live and that the page doesn't hold against
|
||||
/// them ([`held_back`]), whether or not the layers are then used, and however.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn build_weighing(
|
||||
structure: Structure,
|
||||
data: &[u8],
|
||||
visible: Option<&Document>,
|
||||
page: Page,
|
||||
outlined: bool,
|
||||
trust: bool,
|
||||
unit: f64,
|
||||
free: &mut Option<Vec<usize>>,
|
||||
) -> Result<(Document, Vec<String>), String> {
|
||||
let Structure {
|
||||
mut doc,
|
||||
@@ -468,12 +487,14 @@ pub(super) fn build_from(
|
||||
let warped_type = warped_type.compared(frame.is_some(), &mut notes);
|
||||
let bound = [&bound_text, &bound_visible];
|
||||
let typed = settle_warped_type(warped_type, &mut doc, &filled, &threaded_frames, bound, &mut restyled, &mut notes)?;
|
||||
// Each live envelope of type's place among the envelopes of type, by its id.
|
||||
let places: Vec<(NodeId, usize)> = typed.iter().map(|(place, id, _)| (*id, *place)).collect();
|
||||
// The live envelopes and what takes the place of each again, when the layers with them don't
|
||||
// draw as the page, with the note that says so.
|
||||
let envelopes_drawn: Vec<(NodeId, Vec<Node>, &str)> = envelopes_drawn
|
||||
.into_iter()
|
||||
.map(|(id, art)| (id, vec![art], ai::ENVELOPE_DRAWN))
|
||||
.chain(typed.into_iter().map(|(id, nodes)| (id, nodes, ai::ENVELOPE_TYPE_DRAWN)))
|
||||
.chain(typed.into_iter().map(|(_, id, nodes)| (id, nodes, ai::ENVELOPE_TYPE_DRAWN)))
|
||||
.collect();
|
||||
if empty > 0 {
|
||||
notes.push(format!("{empty} {}", if frame.is_some() { TEXT_LEFT_OUT } else { TYPE_LEFT_OUT }));
|
||||
@@ -525,20 +546,21 @@ pub(super) fn build_from(
|
||||
notes.extend(unseen_note(unseen).into_iter().chain(note));
|
||||
}
|
||||
other => match drawn_or_trusted(&frame, &mut view, &mut doc, &envelopes_drawn, ¬es, unseen, trust, why, reflow) {
|
||||
Ok(more) => notes.extend(more),
|
||||
Ok((more, held)) => {
|
||||
*free = held.map(|held| free_places(&places, &held));
|
||||
notes.extend(more);
|
||||
}
|
||||
// The objects that draw differently from the page come in as its art, from
|
||||
// the placement that draws closer to it. The layers don't draw as the page:
|
||||
// envelopes of type keep their drawn art and their type, hidden.
|
||||
// the envelopes of type it holds against them keep their drawn art and their
|
||||
// type, hidden. Where no placement's differing objects can then come in as the
|
||||
// page's art (an object that holds type isn't replaced), every envelope of type
|
||||
// keeps its drawn art, and that is tried again.
|
||||
Err(why) => {
|
||||
let typed = type_drawn(&mut doc, &envelopes_drawn);
|
||||
let other = other.map(|(_, mut other, unseen)| {
|
||||
type_drawn(&mut other, &envelopes_drawn);
|
||||
(other, unseen)
|
||||
});
|
||||
let placements = closer_first(&frame, &mut view, (doc, unseen), other, reflow);
|
||||
let Some((kept, unseen, kept_notes)) = placements.into_iter().find_map(|(base, unseen)| {
|
||||
keep_differing(&base, &frame, &mut view, &threaded_frames, &shown, reflow).map(|(kept, n)| (kept, unseen, n))
|
||||
}) else {
|
||||
let other = other.map(|(_, other, unseen)| (other, unseen));
|
||||
let layers = Placements { doc: &doc, unseen, other: other.as_ref(), drawn: &envelopes_drawn, places: &places };
|
||||
let found = keep_differing_typed(&frame, &mut view, &layers, &threaded_frames, &shown, reflow, free);
|
||||
let Some((kept, unseen, kept_notes, typed)) = found else {
|
||||
return Err(why);
|
||||
};
|
||||
doc = kept;
|
||||
@@ -693,8 +715,9 @@ fn bound_slots(
|
||||
/// anchors. Before that, the envelopes of type open live where they draw as their drawn art, with
|
||||
/// the type of their text slots (made type, one text object for the slot, not threaded, bound to
|
||||
/// no text variable and no visibility variable, `bound`'s second set, and no wrap object); the
|
||||
/// others keep their drawn art, with a note. Returns each live envelope of type's id with what
|
||||
/// takes its place again (its hidden type, then its drawn art).
|
||||
/// others keep their drawn art, with a note. Returns each live envelope of type's place among the
|
||||
/// envelopes of type (in the order they were read) and id, with what takes its place again (its
|
||||
/// hidden type, then its drawn art).
|
||||
fn settle_warped_type(
|
||||
mut warped: ai::WarpedType,
|
||||
doc: &mut Document,
|
||||
@@ -703,7 +726,7 @@ fn settle_warped_type(
|
||||
bound: [&BTreeSet<NodeId>; 2],
|
||||
restyled: &mut Vec<Rect>,
|
||||
notes: &mut Vec<String>,
|
||||
) -> Result<Vec<(NodeId, Vec<Node>)>, String> {
|
||||
) -> Result<Vec<(usize, NodeId, Vec<Node>)>, String> {
|
||||
let [bound_text, bound_visible] = bound;
|
||||
let made = |slot| match filled.get(&slot).map(|f| f.text.as_slice()).filter(|_| !bound_text.contains(&slot)) {
|
||||
Some(&[one]) => Some(one),
|
||||
@@ -1152,15 +1175,15 @@ const MAX_DIFFERING: usize = 64;
|
||||
const MAX_PATCH_PAIRS: usize = 4_000_000;
|
||||
|
||||
/// The layers as read and at the placed files' other placement (each with its count of text
|
||||
/// objects the page doesn't draw), the one that draws closer to the page first. `reflow` as in
|
||||
/// [`PageView::difference`].
|
||||
fn closer_first(
|
||||
/// objects the page doesn't draw, and what goes with it), the one that draws closer to the page
|
||||
/// first. `reflow` as in [`PageView::difference`].
|
||||
fn closer_first<T>(
|
||||
frame: &Frame<'_>,
|
||||
view: &mut PageView,
|
||||
doc: (Document, usize),
|
||||
other: Option<(Document, usize)>,
|
||||
doc: (Document, usize, T),
|
||||
other: Option<(Document, usize, T)>,
|
||||
reflow: bool,
|
||||
) -> Vec<(Document, usize)> {
|
||||
) -> Vec<(Document, usize, T)> {
|
||||
match other {
|
||||
Some(other)
|
||||
if view.difference_on_every_page(&other.0, frame.rect, reflow).any < view.difference_on_every_page(&doc.0, frame.rect, reflow).any =>
|
||||
@@ -1278,6 +1301,73 @@ fn keep_differing(
|
||||
Some((kept, note.into_iter().chain([format!("{DREW_DIFFERENTLY}: {}", layers.join(", "))]).collect()))
|
||||
}
|
||||
|
||||
/// The places (among the envelopes of type, as read) of the live envelopes of type in `places`
|
||||
/// (each with its id) that `held` doesn't list.
|
||||
fn free_places(places: &[(NodeId, usize)], held: &BTreeSet<NodeId>) -> Vec<usize> {
|
||||
places.iter().filter(|(id, _)| !held.contains(id)).map(|(_, place)| *place).collect()
|
||||
}
|
||||
|
||||
/// The layers of [`keep_differing_typed`]: `doc`, `unseen` text objects of it taken out; the placed
|
||||
/// files' other placement of it, when it has one, with how many of its text objects were; its live
|
||||
/// envelopes, as [`drawn_or_trusted`] takes them; and the place of each live envelope of type
|
||||
/// among the envelopes of type, by its id.
|
||||
struct Placements<'a> {
|
||||
doc: &'a Document,
|
||||
unseen: usize,
|
||||
other: Option<&'a (Document, usize)>,
|
||||
drawn: &'a [(NodeId, Vec<Node>, &'a str)],
|
||||
places: &'a [(NodeId, usize)],
|
||||
}
|
||||
|
||||
/// [`keep_differing`] of the placement of `layers` that draws closer to the page, the envelopes of
|
||||
/// type the page holds against that placement ([`held_back`], each placement weighed on its own: the
|
||||
/// other one draws placed files elsewhere, under or over the envelopes) having their drawn art and
|
||||
/// hidden type back; and where none of the placements' differing objects can then come in as the
|
||||
/// page's art, every envelope of type: the document, how many of its text objects were taken out,
|
||||
/// its notes, and whether an envelope of type got its drawn art back. A placement that draws as the
|
||||
/// page with only the envelopes the page holds put back is used as it is, as one with all the live
|
||||
/// envelopes' drawn art put back would have been ([`drawn_or_trusted`]): nothing in it differs to
|
||||
/// come in as the page's art. `free` is set to the places of those the page doesn't hold against
|
||||
/// the layers as read.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn keep_differing_typed(
|
||||
frame: &Frame<'_>,
|
||||
view: &mut PageView,
|
||||
layers: &Placements<'_>,
|
||||
threaded_frames: &BTreeSet<NodeId>,
|
||||
shown: &[Vec<Arc<Node>>],
|
||||
reflow: bool,
|
||||
free: &mut Option<Vec<usize>>,
|
||||
) -> Option<(Document, usize, Vec<String>, bool)> {
|
||||
let held = held_back(frame, view, layers.doc, layers.drawn, reflow);
|
||||
*free = Some(free_places(layers.places, &held));
|
||||
let held_other = layers.other.map(|(other, _)| held_back(frame, view, other, layers.drawn, reflow));
|
||||
let all: BTreeSet<NodeId> = layers.places.iter().map(|(id, _)| *id).collect();
|
||||
let fewer = held.len() < all.len() || held_other.as_ref().is_some_and(|h| h.len() < all.len());
|
||||
let mut tries = vec![];
|
||||
if fewer {
|
||||
tries.push((held, held_other));
|
||||
}
|
||||
tries.push((all.clone(), Some(all.clone())));
|
||||
tries.into_iter().find_map(|(held, held_other)| {
|
||||
// Each placement with what it put back: whether an envelope of type, and whether only some.
|
||||
let mut base = layers.doc.clone();
|
||||
let typed = (type_drawn(&mut base, layers.drawn, &held), held.len() < all.len());
|
||||
let other = layers.other.map(|(other, unseen)| {
|
||||
let (mut other, held) = (other.clone(), held_other.as_ref().unwrap_or(&held));
|
||||
let typed = (type_drawn(&mut other, layers.drawn, held), held.len() < all.len());
|
||||
(other, *unseen, typed)
|
||||
});
|
||||
let placements = closer_first(frame, view, (base, layers.unseen, typed), other, reflow);
|
||||
placements.into_iter().find_map(|(base, unseen, (typed, some))| {
|
||||
if some && let Ok(notes) = compare(frame, view, &base, reflow) {
|
||||
return Some((base, unseen, notes, typed));
|
||||
}
|
||||
keep_differing(&base, frame, view, threaded_frames, shown, reflow).map(|(kept, notes)| (kept, unseen, notes, typed))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Copy page drawing IDs, including mask art outside the ordinary child tree.
|
||||
pub(super) fn reid_page_art(doc: &mut Document, source: &Node) -> Node {
|
||||
let mut copy = doc.reid(source);
|
||||
@@ -1338,9 +1428,9 @@ fn slot_content(
|
||||
/// The layers `doc` don't draw as the page (`why`), `unseen` text objects of them taken out: the
|
||||
/// notes to add when `doc` with each live envelope `drawn` lists replaced by the drawn art it took
|
||||
/// the place of (see [`ai::Structure::envelopes_drawn`]) draws as the page, `doc` then being that;
|
||||
/// else, with `trust`, the notes for `doc` as it is but for its envelopes of type, which keep their
|
||||
/// drawn art ([`type_drawn`]). An error, `why`, otherwise. The envelope note is added unless
|
||||
/// `notes` has it.
|
||||
/// else, with `trust`, the notes for `doc` as it is but for the envelopes of type the page holds
|
||||
/// against it ([`held_back`]), which keep their drawn art ([`type_drawn`]), with those envelopes.
|
||||
/// An error, `why`, otherwise. The envelope note is added unless `notes` has it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn drawn_or_trusted(
|
||||
frame: &Frame<'_>,
|
||||
@@ -1352,7 +1442,7 @@ fn drawn_or_trusted(
|
||||
trust: bool,
|
||||
why: String,
|
||||
reflow: bool,
|
||||
) -> Result<Vec<String>, String> {
|
||||
) -> Result<(Vec<String>, Option<BTreeSet<NodeId>>), String> {
|
||||
let unseen_note = |n: usize| (n > 0).then(|| format!("{n} {UNSEEN_TEXT}"));
|
||||
let mut put_back = doc.clone();
|
||||
// Each envelope still there, in place of the art it took the place of.
|
||||
@@ -1379,26 +1469,143 @@ fn drawn_or_trusted(
|
||||
envelope.push(say.to_string());
|
||||
}
|
||||
}
|
||||
return Ok(envelope.into_iter().chain(unseen_note(unseen + more)).chain(note).collect());
|
||||
return Ok((envelope.into_iter().chain(unseen_note(unseen + more)).chain(note).collect(), None));
|
||||
}
|
||||
}
|
||||
if trust {
|
||||
let typed = type_drawn(doc, drawn).then(|| ai::ENVELOPE_TYPE_DRAWN.to_string()).filter(|n| !notes.contains(n));
|
||||
let held = held_back(frame, view, doc, drawn, reflow);
|
||||
let typed = type_drawn(doc, drawn, &held).then(|| ai::ENVELOPE_TYPE_DRAWN.to_string()).filter(|n| !notes.contains(n));
|
||||
let trusted = format!("{why}: they come from its editing data all the same");
|
||||
return Ok(typed.into_iter().chain(unseen_note(unseen)).chain([trusted]).collect());
|
||||
return Ok((typed.into_iter().chain(unseen_note(unseen)).chain([trusted]).collect(), Some(held)));
|
||||
}
|
||||
Err(why)
|
||||
}
|
||||
|
||||
/// The live envelopes of type in `drawn` (as [`drawn_or_trusted`] takes them) that the page holds
|
||||
/// against the layers `doc`, which don't draw as it even with every live envelope's drawn art put
|
||||
/// back: by their ids, those where the layers don't draw as the page. The envelope and its drawn art
|
||||
/// are drawn together on a layer of their own, without the rest of the layers, on the page check's
|
||||
/// drawing of each page they lie on: in the pixels they ink there, the layers with the envelope may
|
||||
/// differ from the page in no more than the envelope's own check allows against its drawn art (at
|
||||
/// all, and with all their neighbors, away from type: [`compare`]); and in the order they were
|
||||
/// read, the envelopes that stay live may differ there in no more of each page together than half
|
||||
/// of what the page check allows (a budget of the same size as the one their own checks share, kept
|
||||
/// apart from it). Art that differs elsewhere on the page doesn't hold them. One the layers don't
|
||||
/// draw where it is and as itself ([`drawn_in_place`]) is held, wherever it lies: drawn on its own,
|
||||
/// it would be weighed elsewhere than it draws, or on pixels a clip or mask hides, and what holds it
|
||||
/// can draw it onto a page from off it. Another envelope on no page stays live, as the page check
|
||||
/// doesn't see it, unless the page holds all the art that prints ([`Frame::whole`]), where art past
|
||||
/// it is compared too: there one reaching past the page is held. Past [`MAX_WEIGHED`] of them on
|
||||
/// the pages, or where the page check can't weigh one, they are all held.
|
||||
fn held_back(frame: &Frame<'_>, view: &mut PageView, doc: &Document, drawn: &[(NodeId, Vec<Node>, &str)], reflow: bool) -> BTreeSet<NodeId> {
|
||||
let typed: Vec<(NodeId, &Node, &Node)> = drawn
|
||||
.iter()
|
||||
.filter(|(_, _, say)| *say == ai::ENVELOPE_TYPE_DRAWN)
|
||||
.filter_map(|(id, art, _)| Some((*id, doc.node(*id)?, art.last()?)))
|
||||
.collect();
|
||||
let all: BTreeSet<NodeId> = typed.iter().map(|(id, ..)| *id).collect();
|
||||
if all.is_empty() {
|
||||
return all;
|
||||
}
|
||||
let pages = view.regions(frame.rect);
|
||||
let past = |b: Rect| frame.whole && !frame.rect.inflate(PAST_THE_PAGE, PAST_THE_PAGE).contains_rect(b);
|
||||
let mut held = BTreeSet::new();
|
||||
let mut in_place = std::collections::HashMap::new();
|
||||
drawn_in_place(&doc.layers, &all, true, &mut in_place);
|
||||
// Those the page check sees, by the boxes of the envelope and its drawn art.
|
||||
let mut seen = vec![];
|
||||
for (id, live, art) in typed {
|
||||
if !in_place.get(&id).copied().unwrap_or(false) {
|
||||
held.insert(id);
|
||||
continue;
|
||||
}
|
||||
match vectorcraft_geom::union_opt(live.visual_bounds(), art.visual_bounds()) {
|
||||
Some(b) if past(b) => {
|
||||
held.insert(id);
|
||||
}
|
||||
Some(b) if pages_touched(b, &pages, 1.0).is_empty() => {}
|
||||
_ => seen.push((id, live, art)),
|
||||
}
|
||||
}
|
||||
if seen.len() > MAX_WEIGHED {
|
||||
return all;
|
||||
}
|
||||
// The pixels of each page where the envelopes that stay live differ from it: at all, and with
|
||||
// all their neighbors.
|
||||
let mut spent = vec![(0usize, 0usize); pages.len()];
|
||||
for (id, live, art) in seen {
|
||||
// The envelope and its drawn art, shown, on a layer of their own in `doc`.
|
||||
let mut nodes: Vec<Arc<Node>> = [live, art].into_iter().cloned().map(Arc::new).collect();
|
||||
for n in &mut nodes {
|
||||
let n = Arc::make_mut(n);
|
||||
(n.visible, n.opacity) = (true, 1.0);
|
||||
}
|
||||
let mut one = doc.clone();
|
||||
let mut layer = Node::layer(NodeId(0), "", vectorcraft_doc::LayerColor::Preset(0));
|
||||
if let Some(children) = layer.children_mut() {
|
||||
*children = nodes;
|
||||
}
|
||||
one.layers = vec![Arc::new(layer)];
|
||||
let bounds = vectorcraft_render::encode::art_bounds(&one);
|
||||
if bounds.is_some_and(past) {
|
||||
held.insert(id);
|
||||
continue;
|
||||
}
|
||||
let on = bounds.map_or(vec![], |b| pages_touched(b, &pages, 1.0));
|
||||
let mut added = spent.clone();
|
||||
let mut fits = true;
|
||||
for (i, ink) in view.inked(&one, &on) {
|
||||
let Some((any, solid)) = view.differing_in(doc, i, &ink, reflow) else { return all };
|
||||
let inked = ink.iter().filter(|p| **p).count() as f64;
|
||||
let pixels = ink.len().max(1) as f64;
|
||||
let Some((a, s)) = added.get_mut(i) else { return all };
|
||||
*a += any;
|
||||
*s += solid;
|
||||
fits &= any as f64 <= inked * ai::ENVELOPE_MAX_ANY
|
||||
&& solid as f64 <= inked * ai::ENVELOPE_MAX_SOLID
|
||||
&& *a as f64 <= pixels * MAX_DIFFERENCE / 2.0
|
||||
&& *s as f64 <= pixels * MAX_SOLID_DIFFERENCE / 2.0;
|
||||
}
|
||||
if fits {
|
||||
spent = added;
|
||||
} else {
|
||||
held.insert(id);
|
||||
}
|
||||
}
|
||||
held
|
||||
}
|
||||
|
||||
/// Which of the objects `ids` among `nodes` (or what they hold) are drawn where they are and as
|
||||
/// themselves (`plain`: so far, above `nodes`), into `out` by id: under layers and groups that
|
||||
/// neither clip them, mask them nor have an appearance of their own (whose effects can move or copy
|
||||
/// them), and in no envelope, repeat, blend or other object that draws what it holds elsewhere or
|
||||
/// more than once.
|
||||
fn drawn_in_place(nodes: &[Arc<Node>], ids: &BTreeSet<NodeId>, plain: bool, out: &mut std::collections::HashMap<NodeId, bool>) {
|
||||
for n in nodes {
|
||||
if ids.contains(&n.id) {
|
||||
out.insert(n.id, plain);
|
||||
}
|
||||
let Some(children) = n.children() else { continue };
|
||||
let keeps = match n.kind {
|
||||
NodeKind::Layer { clip, .. } | NodeKind::Group { clip, .. } => {
|
||||
!clip && n.appearance == vectorcraft_doc::Appearance::default() && n.extras().mask.is_none()
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
drawn_in_place(children, ids, plain && keeps, out);
|
||||
}
|
||||
}
|
||||
|
||||
/// Put each live envelope of type in `drawn` (as [`drawn_or_trusted`] takes them) that `doc` still
|
||||
/// has back as its hidden type and its drawn art: whether one was. An envelope of type opens live
|
||||
/// only where the layers with it draw as the page, so where they don't, though they are used all
|
||||
/// the same (the editing data trusted, or the objects that differ come in as the page's art), it
|
||||
/// keeps its drawn art.
|
||||
fn type_drawn(doc: &mut Document, drawn: &[(NodeId, Vec<Node>, &str)]) -> bool {
|
||||
/// has and `held` lists back as its hidden type and its drawn art: whether one was. An envelope of
|
||||
/// type opens live only where the page doesn't hold it against the layers ([`held_back`]), so where
|
||||
/// they don't draw as the page though they are used all the same (the editing data trusted, or the
|
||||
/// objects that differ come in as the page's art), one the page holds against them keeps its drawn
|
||||
/// art.
|
||||
fn type_drawn(doc: &mut Document, drawn: &[(NodeId, Vec<Node>, &str)], held: &BTreeSet<NodeId>) -> bool {
|
||||
let typed: Vec<(NodeId, Vec<Node>)> = drawn
|
||||
.iter()
|
||||
.filter(|(id, _, say)| *say == ai::ENVELOPE_TYPE_DRAWN && doc.node(*id).is_some())
|
||||
.filter(|(id, _, say)| *say == ai::ENVELOPE_TYPE_DRAWN && held.contains(id) && doc.node(*id).is_some())
|
||||
.map(|(id, art, _)| (*id, art.clone()))
|
||||
.collect();
|
||||
if typed.is_empty() || doc.insert_beside(typed, true).is_err() {
|
||||
@@ -2562,4 +2769,359 @@ mod candidate_tests {
|
||||
replace_unread(&mut doc, &frame, &unread, &[], &BTreeSet::new(), &mut copied).unwrap();
|
||||
assert!(copied.contains(&nodes[0].id) && !copied.contains(&nodes[1].id));
|
||||
}
|
||||
|
||||
/// A rectangle on `rect` filled with `color`, with an id of `doc`'s.
|
||||
fn filled_rect(doc: &mut Document, rect: Rect, color: vectorcraft_color::Color) -> Node {
|
||||
let paint = vectorcraft_color::Paint::solid(color);
|
||||
Node::path(
|
||||
doc.alloc_id(),
|
||||
vectorcraft_geom::shapes::rectangle(rect),
|
||||
vectorcraft_doc::Appearance::basic(paint, vectorcraft_color::Paint::None, 0.0),
|
||||
)
|
||||
}
|
||||
|
||||
/// A live envelope as [`drawn_or_trusted`] takes it: its id, what takes its place again, its note.
|
||||
type Envelope = (NodeId, Vec<Node>, &'static str);
|
||||
|
||||
/// The layers `doc` with a black rectangle on each of `lives` standing in for a live envelope of
|
||||
/// type, its drawn art a black rectangle on the matching one of `arts`: the layers, and the
|
||||
/// envelopes as [`drawn_or_trusted`] takes them.
|
||||
fn stand_ins(lives: &[Rect], arts: &[Rect]) -> (Document, Vec<Envelope>) {
|
||||
let mut doc = Document::new(100.0, 100.0);
|
||||
let layer = doc.layers[0].id;
|
||||
let mut drawn = vec![];
|
||||
for (live, art) in lives.iter().zip(arts) {
|
||||
let n = filled_rect(&mut doc, *live, vectorcraft_color::Color::BLACK);
|
||||
let id = n.id;
|
||||
doc.insert(Some(layer), 0, n).unwrap();
|
||||
let art = filled_rect(&mut doc, *art, vectorcraft_color::Color::BLACK);
|
||||
drawn.push((id, vec![art], ai::ENVELOPE_TYPE_DRAWN));
|
||||
}
|
||||
(doc, drawn)
|
||||
}
|
||||
|
||||
/// The page holds an envelope of type against layers that don't draw as it only where the
|
||||
/// layers with the envelope differ from the page where it or its drawn art inks: not where the
|
||||
/// page draws other art that the layers don't have.
|
||||
#[test]
|
||||
fn the_page_holds_back_only_envelopes_of_type_where_it_differs_from_them() {
|
||||
let rect = Rect::new(0.0, 0.0, 100.0, 100.0);
|
||||
let mut page = Document::new(100.0, 100.0);
|
||||
let layer = page.layers[0].id;
|
||||
// The first two envelopes' drawn art, and a square the layers don't have.
|
||||
for r in [Rect::new(10.0, 10.0, 30.0, 30.0), Rect::new(60.0, 10.0, 80.0, 30.0), Rect::new(10.0, 60.0, 30.0, 80.0)] {
|
||||
let n = filled_rect(&mut page, r, vectorcraft_color::Color::BLACK);
|
||||
page.insert(Some(layer), 0, n).unwrap();
|
||||
}
|
||||
// Live where its drawn art is; moved off its drawn art; and where the page draws nothing.
|
||||
let (doc, drawn) = stand_ins(
|
||||
&[Rect::new(10.0, 10.0, 30.0, 30.0), Rect::new(66.0, 10.0, 86.0, 30.0), Rect::new(60.0, 60.0, 80.0, 80.0)],
|
||||
&[Rect::new(10.0, 10.0, 30.0, 30.0), Rect::new(60.0, 10.0, 80.0, 30.0), Rect::new(60.0, 60.0, 80.0, 80.0)],
|
||||
);
|
||||
let frame = Frame { page: &page, page_rect: rect, rect, whole: false, pages: vec![(rect, Affine::IDENTITY)], more: vec![] };
|
||||
let mut view = PageView::new(&frame);
|
||||
assert!(compare(&frame, &mut view, &doc, false).is_err());
|
||||
assert_eq!(held_back(&frame, &mut view, &doc, &drawn, false), BTreeSet::from([drawn[1].0, drawn[2].0]));
|
||||
// Only envelopes of type are weighed.
|
||||
let other: Vec<_> = drawn.iter().map(|(id, art, _)| (*id, art.clone(), ai::ENVELOPE_DRAWN)).collect();
|
||||
assert!(held_back(&frame, &mut view, &doc, &other, false).is_empty());
|
||||
}
|
||||
|
||||
/// The envelopes of type that stay live may together differ from the page where they ink in no
|
||||
/// more than half of what the page check allows: the next one past that is held back, though
|
||||
/// on its own it would stay.
|
||||
#[test]
|
||||
fn envelopes_of_type_stay_live_within_half_of_what_the_page_check_allows() {
|
||||
let rect = Rect::new(0.0, 0.0, 100.0, 100.0);
|
||||
let halves = [Rect::new(0.0, 0.0, 100.0, 25.0), Rect::new(0.0, 25.0, 100.0, 50.0)];
|
||||
let mut page = Document::new(100.0, 100.0);
|
||||
let layer = page.layers[0].id;
|
||||
for r in halves {
|
||||
let n = filled_rect(&mut page, r, vectorcraft_color::Color::BLACK);
|
||||
page.insert(Some(layer), 0, n).unwrap();
|
||||
}
|
||||
// White lines two pixels high over each, 6 points apart: 8% of what each inks differs, 2% of
|
||||
// the page, and none of it with all its neighbors.
|
||||
for (i, y) in [3.0, 9.0, 15.0, 21.0, 28.0, 34.0, 40.0, 46.0].into_iter().enumerate() {
|
||||
let n = filled_rect(&mut page, Rect::new(0.0, y, 100.0, y + 0.5), vectorcraft_color::Color::WHITE);
|
||||
page.insert(Some(layer), halves.len() + i, n).unwrap();
|
||||
}
|
||||
let (doc, drawn) = stand_ins(&halves, &halves);
|
||||
let frame = Frame { page: &page, page_rect: rect, rect, whole: false, pages: vec![(rect, Affine::IDENTITY)], more: vec![] };
|
||||
let mut view = PageView::new(&frame);
|
||||
assert_eq!(held_back(&frame, &mut view, &doc, &drawn, false), BTreeSet::from([drawn[1].0]));
|
||||
assert!(held_back(&frame, &mut view, &doc, &drawn[1..], false).is_empty());
|
||||
}
|
||||
|
||||
/// A page of 100 points with a black rectangle on each of `rects`, and its frame (`whole`: the
|
||||
/// page holds all the art that prints).
|
||||
fn black_page(rects: &[Rect]) -> Document {
|
||||
let mut page = Document::new(100.0, 100.0);
|
||||
let layer = page.layers[0].id;
|
||||
for r in rects {
|
||||
let n = filled_rect(&mut page, *r, vectorcraft_color::Color::BLACK);
|
||||
page.insert(Some(layer), 0, n).unwrap();
|
||||
}
|
||||
page
|
||||
}
|
||||
|
||||
/// The frame of `page`, its 100-point square (`whole`: the page holds all the art that prints).
|
||||
fn frame_of(page: &Document, whole: bool) -> Frame<'_> {
|
||||
let rect = Rect::new(0.0, 0.0, 100.0, 100.0);
|
||||
Frame { page, page_rect: rect, rect, whole, pages: vec![(rect, Affine::IDENTITY)], more: vec![] }
|
||||
}
|
||||
|
||||
/// An envelope of type is held back where the layers with it differ from the page in more of
|
||||
/// the pixels it and its drawn art ink than its own check allows against its drawn art, though
|
||||
/// that is far less of the page than the page check allows.
|
||||
#[test]
|
||||
fn an_envelope_of_type_differing_in_more_of_its_pixels_than_its_check_allows_is_held_back() {
|
||||
// Half a point off its drawn art, which the page draws: 22% of the pixels they ink differ,
|
||||
// a tenth of a percent of the page, and none with all their neighbors.
|
||||
let page = black_page(&[Rect::new(30.5, 10.0, 34.5, 20.0)]);
|
||||
let (doc, drawn) = stand_ins(&[Rect::new(30.0, 10.0, 34.0, 20.0)], &[Rect::new(30.5, 10.0, 34.5, 20.0)]);
|
||||
let frame = frame_of(&page, false);
|
||||
let mut view = PageView::new(&frame);
|
||||
assert_eq!(held_back(&frame, &mut view, &doc, &drawn, false), BTreeSet::from([drawn[0].0]));
|
||||
}
|
||||
|
||||
/// An envelope of type that the layers don't draw where it is and as itself is held back though
|
||||
/// it draws as the page, wherever it lies: one that a clip group or a clipping sublayer clips, an
|
||||
/// opacity mask masks, or that a group with an appearance of its own, another envelope, a repeat
|
||||
/// or a blend holds. Drawn on its own, it would be weighed on pixels a clip hides, or elsewhere
|
||||
/// than it draws, and what holds it can draw it onto the page from off it. One in a plain group
|
||||
/// is weighed, and one off the page in a plain group stays live.
|
||||
#[test]
|
||||
fn envelopes_of_type_the_layers_dont_draw_in_place_are_held_back() {
|
||||
let at = |i: usize| {
|
||||
let (x, y) = if i < 4 { (5.0 + 20.0 * i as f64, 5.0) } else { (5.0 + 20.0 * (i - 4) as f64, 30.0) };
|
||||
Rect::new(x, y, x + 15.0, y + 15.0)
|
||||
};
|
||||
let off = [Rect::new(120.0, 5.0, 135.0, 20.0), Rect::new(140.0, 5.0, 155.0, 20.0)];
|
||||
let page = black_page(&(0..8).map(at).collect::<Vec<_>>());
|
||||
let mut doc = Document::new(100.0, 100.0);
|
||||
let layer = doc.layers[0].id;
|
||||
let mut drawn = vec![];
|
||||
let mut stand_in = |doc: &mut Document, r: Rect| {
|
||||
let n = filled_rect(doc, r, vectorcraft_color::Color::BLACK);
|
||||
drawn.push((n.id, vec![filled_rect(doc, r, vectorcraft_color::Color::BLACK)], ai::ENVELOPE_TYPE_DRAWN));
|
||||
Arc::new(n)
|
||||
};
|
||||
let group = |doc: &mut Document, children: Vec<Arc<Node>>, clip: bool| Node::new(doc.alloc_id(), NodeKind::Group { children, clip });
|
||||
let fill = vectorcraft_doc::Appearance {
|
||||
items: vec![vectorcraft_doc::AppearanceItem::Fill(vectorcraft_doc::FillLayer::new(vectorcraft_color::Paint::solid(
|
||||
vectorcraft_color::Color::BLACK,
|
||||
)))],
|
||||
..Default::default()
|
||||
};
|
||||
let mut objects = vec![];
|
||||
// 0: in a plain group.
|
||||
let n = stand_in(&mut doc, at(0));
|
||||
objects.push(group(&mut doc, vec![n], false));
|
||||
// 1: in a clip group.
|
||||
let n = stand_in(&mut doc, at(1));
|
||||
let mut clip = filled_rect(&mut doc, at(1).inflate(2.0, 2.0), vectorcraft_color::Color::BLACK);
|
||||
if let NodeKind::Path { clipping, .. } = &mut clip.kind {
|
||||
*clipping = true;
|
||||
}
|
||||
objects.push(group(&mut doc, vec![Arc::new(clip), n], true));
|
||||
// 2: in another envelope.
|
||||
let n = stand_in(&mut doc, at(2));
|
||||
let r = at(2);
|
||||
let points = vec![kurbo::Point::new(r.x0, r.y0), kurbo::Point::new(r.x1, r.y0), kurbo::Point::new(r.x0, r.y1), kurbo::Point::new(r.x1, r.y1)];
|
||||
let kind = Box::new(vectorcraft_doc::live::EnvelopeKind::Mesh { rows: 1, cols: 1, points, handles: vec![] });
|
||||
let options = vectorcraft_doc::live::EnvelopeOptions::NEW;
|
||||
objects.push(Node::new(
|
||||
doc.alloc_id(),
|
||||
NodeKind::Envelope { content: vec![n], kind, fidelity: 50.0, editing: false, options, frame: Box::default() },
|
||||
));
|
||||
// 3: in a group with an appearance of its own.
|
||||
let n = stand_in(&mut doc, at(3));
|
||||
let mut styled = group(&mut doc, vec![n], false);
|
||||
styled.appearance = fill.clone().into();
|
||||
objects.push(styled);
|
||||
// 4: in a group with an opacity mask.
|
||||
let n = stand_in(&mut doc, at(4));
|
||||
let mut masked = group(&mut doc, vec![n], false);
|
||||
let mask = filled_rect(&mut doc, at(4).inflate(2.0, 2.0), vectorcraft_color::Color::WHITE);
|
||||
masked.extras_mut().mask = Some(Box::new(vectorcraft_doc::OpacityMask::new(mask, true)));
|
||||
objects.push(masked);
|
||||
// 5: in a clipping sublayer.
|
||||
let n = stand_in(&mut doc, at(5));
|
||||
let mut clip = filled_rect(&mut doc, at(5).inflate(2.0, 2.0), vectorcraft_color::Color::BLACK);
|
||||
if let NodeKind::Path { clipping, .. } = &mut clip.kind {
|
||||
*clipping = true;
|
||||
}
|
||||
let mut sublayer = Node::layer(doc.alloc_id(), "Clipped", vectorcraft_doc::LayerColor::Preset(1));
|
||||
if let NodeKind::Layer { clip: c, children, .. } = &mut sublayer.kind {
|
||||
(*c, *children) = (true, vec![Arc::new(clip), n]);
|
||||
}
|
||||
objects.push(sublayer);
|
||||
// 6: in a repeat; 7: in a blend.
|
||||
let n = stand_in(&mut doc, at(6));
|
||||
objects.push(Node::new(doc.alloc_id(), NodeKind::Repeat(vectorcraft_doc::RepeatSpec::grid(vec![n], 0.0, 0.0))));
|
||||
let n = stand_in(&mut doc, at(7));
|
||||
objects.push(Node::new(doc.alloc_id(), NodeKind::Blend { children: vec![n], spec: Box::default() }));
|
||||
// 8: off the page in a group with an appearance of its own (an effect could draw it onto the
|
||||
// page); 9: off the page in a plain group.
|
||||
let n = stand_in(&mut doc, off[0]);
|
||||
let mut styled = group(&mut doc, vec![n], false);
|
||||
styled.appearance = fill.into();
|
||||
objects.push(styled);
|
||||
let n = stand_in(&mut doc, off[1]);
|
||||
objects.push(group(&mut doc, vec![n], false));
|
||||
for n in objects {
|
||||
doc.insert(Some(layer), 0, n).unwrap();
|
||||
}
|
||||
let frame = frame_of(&page, false);
|
||||
let mut view = PageView::new(&frame);
|
||||
let held = held_back(&frame, &mut view, &doc, &drawn, false);
|
||||
assert_eq!(held, drawn[1..9].iter().map(|(id, ..)| *id).collect::<BTreeSet<_>>());
|
||||
}
|
||||
|
||||
/// On a page that holds all the art that prints, which the page check also compares past the
|
||||
/// page, an envelope of type reaching past the page, or lying off it, is held back; on another
|
||||
/// page, the part on the page is weighed, and one off it stays live.
|
||||
#[test]
|
||||
fn on_a_page_that_holds_all_the_art_an_envelope_of_type_past_it_is_held_back() {
|
||||
let on = Rect::new(10.0, 10.0, 30.0, 30.0);
|
||||
let across = Rect::new(90.0, 40.0, 110.0, 60.0);
|
||||
let off = Rect::new(120.0, 10.0, 140.0, 30.0);
|
||||
let page = black_page(&[on, Rect::new(90.0, 40.0, 100.0, 60.0)]);
|
||||
let (doc, drawn) = stand_ins(&[on, across, off], &[on, across, off]);
|
||||
for (whole, held) in [(true, BTreeSet::from([drawn[1].0, drawn[2].0])), (false, BTreeSet::new())] {
|
||||
let frame = frame_of(&page, whole);
|
||||
let mut view = PageView::new(&frame);
|
||||
assert_eq!(held_back(&frame, &mut view, &doc, &drawn, false), held, "whole: {whole}");
|
||||
}
|
||||
// On the page by its box, past it by what it draws: a Transform effect of its own draws a
|
||||
// copy of it 100 points to the right, off the page.
|
||||
let page = black_page(&[on]);
|
||||
let (mut doc, drawn) = stand_ins(&[on], &[on]);
|
||||
let effect =
|
||||
vectorcraft_doc::Effect { id: "distort.transform".into(), params: serde_json::json!({"moveH": 100, "copies": 1}), visible: true };
|
||||
doc.node_mut(drawn[0].0).unwrap().appearance.effects.push(effect);
|
||||
for (whole, held) in [(true, BTreeSet::from([drawn[0].0])), (false, BTreeSet::new())] {
|
||||
let frame = frame_of(&page, whole);
|
||||
let mut view = PageView::new(&frame);
|
||||
assert_eq!(held_back(&frame, &mut view, &doc, &drawn, false), held, "drawn past the page, whole: {whole}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Where the objects that draw differently from the page are to come in as its art and the
|
||||
/// envelopes of type the page doesn't hold stay live, but one of those holds the difference in
|
||||
/// its box (an object that holds type isn't replaced), every envelope of type gets its drawn art
|
||||
/// back and that is tried again: the layers are still used.
|
||||
#[test]
|
||||
fn objects_come_in_as_the_page_art_with_every_envelope_of_type_put_back_where_a_live_one_holds_a_difference() {
|
||||
let (a, b, dot, other) = (
|
||||
Rect::new(10.0, 10.0, 20.0, 20.0),
|
||||
Rect::new(40.0, 10.0, 50.0, 20.0),
|
||||
Rect::new(27.0, 12.0, 33.0, 18.0),
|
||||
Rect::new(70.0, 70.0, 90.0, 90.0),
|
||||
);
|
||||
// The page draws a dot in the gap between the two rectangles of the envelope's stand-in.
|
||||
let page = black_page(&[a, b, dot, other]);
|
||||
let mut doc = Document::new(100.0, 100.0);
|
||||
let layer = doc.layers[0].id;
|
||||
let square = filled_rect(&mut doc, other, vectorcraft_color::Color::BLACK);
|
||||
doc.insert(Some(layer), 0, square).unwrap();
|
||||
// The stand-in holds type, hidden; its drawn art is the two rectangles.
|
||||
let style = vectorcraft_doc::CharStyle::default();
|
||||
let mut text =
|
||||
Node::new(doc.alloc_id(), NodeKind::Text(Box::new(vectorcraft_doc::TextObject::point(kurbo::Point::new(12.0, 18.0), "A", style))));
|
||||
text.visible = false;
|
||||
let parts = vec![
|
||||
Arc::new(filled_rect(&mut doc, a, vectorcraft_color::Color::BLACK)),
|
||||
Arc::new(filled_rect(&mut doc, b, vectorcraft_color::Color::BLACK)),
|
||||
];
|
||||
let mut children = parts.clone();
|
||||
children.push(Arc::new(text));
|
||||
let live = Node::new(doc.alloc_id(), NodeKind::Group { children, clip: false });
|
||||
let id = live.id;
|
||||
doc.insert(Some(layer), 0, live).unwrap();
|
||||
let art = Node::new(doc.alloc_id(), NodeKind::Group { children: parts, clip: false });
|
||||
let drawn: Vec<Envelope> = vec![(id, vec![art], ai::ENVELOPE_TYPE_DRAWN)];
|
||||
let frame = frame_of(&page, false);
|
||||
let mut view = PageView::new(&frame);
|
||||
assert!(compare(&frame, &mut view, &doc, false).is_err());
|
||||
let layers = Placements { doc: &doc, unseen: 0, other: None, drawn: &drawn, places: &[(id, 0)] };
|
||||
let mut free = None;
|
||||
let found = keep_differing_typed(&frame, &mut view, &layers, &BTreeSet::new(), &[], false, &mut free);
|
||||
assert_eq!(free, Some(vec![0]), "the page doesn't hold the envelope");
|
||||
let Some((kept, _, notes, typed)) = found else { panic!("the layers aren't used") };
|
||||
assert!(typed, "the envelope got its drawn art back");
|
||||
assert!(kept.node(id).is_none());
|
||||
assert!(notes.iter().any(|n| n.starts_with(DREW_DIFFERENTLY)), "{notes:?}");
|
||||
}
|
||||
|
||||
/// The placed files' other placement is weighed on its own: an envelope of type that the layers
|
||||
/// as read draw as the page, but that a placed file moved over it at the other placement makes
|
||||
/// differ, gets its drawn art back there, where that placement's differing objects then come in
|
||||
/// as the page's art.
|
||||
#[test]
|
||||
fn the_other_placement_of_placed_files_is_weighed_on_its_own() {
|
||||
let black = vectorcraft_color::Color::BLACK;
|
||||
let gray = vectorcraft_color::Color::rgb(0.5, 0.5, 0.5);
|
||||
let (envelope, fillers) = (Rect::new(10.0, 10.0, 30.0, 30.0), [Rect::new(40.0, 80.0, 50.0, 90.0), Rect::new(70.0, 80.0, 80.0, 90.0)]);
|
||||
let placed = Rect::new(60.0, 60.0, 76.0, 76.0);
|
||||
// The page draws the envelope's drawn art in two halves.
|
||||
let page = black_page(&[Rect::new(10.0, 10.0, 20.0, 30.0), Rect::new(20.0, 10.0, 30.0, 30.0), placed, fillers[0], fillers[1]]);
|
||||
let (mut doc, drawn) = stand_ins(&[envelope], &[envelope]);
|
||||
let layer = doc.layers[0].id;
|
||||
for r in fillers {
|
||||
let n = filled_rect(&mut doc, r, black);
|
||||
doc.insert(Some(layer), 0, n).unwrap();
|
||||
}
|
||||
// Two placed files, standing in as rectangles: one 20 points above where the page has it, one
|
||||
// a gray rectangle off the art. At their other placement the first is where the page has it
|
||||
// and the gray one lies over half of the envelope.
|
||||
let image = filled_rect(&mut doc, Rect::new(60.0, 40.0, 76.0, 56.0), black);
|
||||
let strip = filled_rect(&mut doc, Rect::new(85.0, 40.0, 95.0, 60.0), gray);
|
||||
// On top of the layer: the strip covers the envelope where it lies over it.
|
||||
let top = doc.layers[0].children().map_or(0, Vec::len);
|
||||
let (image, strip) = (doc.insert(Some(layer), top, image).unwrap(), doc.insert(Some(layer), top + 1, strip).unwrap());
|
||||
let mut other = doc.clone();
|
||||
other.node_mut(image).unwrap().transform(Affine::translate(Vec2::new(0.0, 20.0)), PAGE_MOVE);
|
||||
other.node_mut(strip).unwrap().transform(Affine::translate(Vec2::new(-65.0, -30.0)), PAGE_MOVE);
|
||||
let frame = frame_of(&page, false);
|
||||
let mut view = PageView::new(&frame);
|
||||
assert!(held_back(&frame, &mut view, &doc, &drawn, false).is_empty());
|
||||
assert_eq!(held_back(&frame, &mut view, &other, &drawn, false), BTreeSet::from([drawn[0].0]));
|
||||
let other = (other, 0);
|
||||
let layers = Placements { doc: &doc, unseen: 0, other: Some(&other), drawn: &drawn, places: &[(drawn[0].0, 0)] };
|
||||
let mut free = None;
|
||||
let found = keep_differing_typed(&frame, &mut view, &layers, &BTreeSet::new(), &[], false, &mut free);
|
||||
assert_eq!(free, Some(vec![0]), "the layers as read don't hold the envelope");
|
||||
let Some((kept, _, notes, typed)) = found else { panic!("the layers aren't used") };
|
||||
assert!(notes.iter().any(|n| n.starts_with(DREW_DIFFERENTLY)), "{notes:?}");
|
||||
assert!(typed && kept.node(drawn[0].0).is_none(), "the envelope keeps its drawn art at the other placement");
|
||||
}
|
||||
|
||||
/// Where putting back only the envelopes of type the page holds makes the layers draw as the page,
|
||||
/// though putting back every live envelope's drawn art doesn't (an envelope of type the page
|
||||
/// doesn't hold has stale drawn art), the layers are used as they then are: nothing differs to
|
||||
/// come in as the page's art.
|
||||
#[test]
|
||||
fn layers_that_draw_as_the_page_with_the_held_envelopes_of_type_put_back_are_used_so() {
|
||||
let (held, kept_live) = (Rect::new(10.0, 10.0, 30.0, 30.0), Rect::new(60.0, 10.0, 80.0, 30.0));
|
||||
let page = black_page(&[held, kept_live]);
|
||||
// The first is drawn 6 points off its drawn art, which the page draws; the second draws as the
|
||||
// page, its drawn art 6 points off.
|
||||
let (doc, drawn) = stand_ins(&[Rect::new(16.0, 10.0, 36.0, 30.0), kept_live], &[held, Rect::new(66.0, 10.0, 86.0, 30.0)]);
|
||||
let frame = frame_of(&page, false);
|
||||
let mut view = PageView::new(&frame);
|
||||
assert!(compare(&frame, &mut view, &doc, false).is_err());
|
||||
let mut all = doc.clone();
|
||||
all.insert_beside(drawn.iter().map(|(id, art, _)| (*id, art.clone())).collect(), true).unwrap();
|
||||
assert!(compare(&frame, &mut view, &all, false).is_err(), "with every drawn art put back they don't draw as the page");
|
||||
let places = [(drawn[0].0, 0), (drawn[1].0, 1)];
|
||||
let layers = Placements { doc: &doc, unseen: 0, other: None, drawn: &drawn, places: &places };
|
||||
let mut free = None;
|
||||
let found = keep_differing_typed(&frame, &mut view, &layers, &BTreeSet::new(), &[], false, &mut free);
|
||||
assert_eq!(free, Some(vec![1]));
|
||||
let Some((kept, _, notes, typed)) = found else { panic!("the layers aren't used") };
|
||||
assert!(typed && kept.node(drawn[0].0).is_none() && kept.node(drawn[1].0).is_some());
|
||||
assert!(!notes.iter().any(|n| n.starts_with(DREW_DIFFERENTLY)), "{notes:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,6 +570,34 @@ impl PageView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Which pixels of each of the pages `on` `art` inks (draws over with an alpha above 8 of 255),
|
||||
/// drawn as the layers are drawn on them, row by row.
|
||||
pub(super) fn inked(&mut self, art: &Document, on: &[usize]) -> Vec<(usize, Vec<bool>)> {
|
||||
self.draw(art, on)
|
||||
.into_iter()
|
||||
.map(|(i, drawn)| (i, drawn.pixels.as_chunks::<4>().0.iter().map(|p| u32::from(p[3]) > WHITE).collect()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Of the pixels of page `i` that `marks` marks (row by row), how many `b` draws differently from
|
||||
/// the page, as [`compared`] counts them: those that differ (with `reflow`, those away from type),
|
||||
/// and those that differ with all their neighbors, away from type. `None` when `marks` isn't the
|
||||
/// page's size.
|
||||
pub(super) fn differing_in(&mut self, b: &Document, i: usize, marks: &[bool], reflow: bool) -> Option<(usize, usize)> {
|
||||
self.layered(b, &[i]);
|
||||
let printed = as_printed(b);
|
||||
let b = printed.as_ref().unwrap_or(b);
|
||||
let page = self.pages.get(i)?;
|
||||
let layered = self.drawn.last()?.pages.get(i)?.as_ref()?;
|
||||
let (differs, typed, solid) = differing_pixels(&page.drawn, &layered.pixels, &page.counted_type(b, page.region, reflow), &page.skipped())?;
|
||||
if marks.len() != differs.len() {
|
||||
return None;
|
||||
}
|
||||
let any = marks.iter().zip(&differs).zip(&typed).filter(|((m, d), t)| **m && **d && !(reflow && **t)).count();
|
||||
let solid = marks.iter().zip(&solid).filter(|(m, s)| **m && **s).count();
|
||||
Some((any, solid))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `w` × `h` pixels of `from` from pixel (`x`, `y`): `None` unless they lie in it, from a pixel.
|
||||
@@ -682,8 +710,27 @@ pub(super) fn compared(x: &vectorcraft_render::Rendered, y: &vectorcraft_render:
|
||||
/// [`compared`], and which of the pixels (row by row) are counted in its `solid` share: none when
|
||||
/// the drawings can't be compared.
|
||||
fn compared_with_solid(x: &vectorcraft_render::Rendered, y: &vectorcraft_render::Rendered, areas: &[Rect], skip: &[Rect]) -> (Difference, Vec<bool>) {
|
||||
if x.width == 0 || x.height == 0 || x.width != y.width || x.height != y.height {
|
||||
let Some((differs, typed, solid)) = differing_pixels(x, y, areas, skip) else {
|
||||
return (Difference { any: 1.0, art: 1.0, solid: 1.0, typed: 0.0 }, vec![]);
|
||||
};
|
||||
let area = (f64::from(x.width) * f64::from(x.height)).max(1.0);
|
||||
let any = differs.iter().filter(|d| **d).count() as f64 / area;
|
||||
let art = differs.iter().zip(&typed).filter(|(d, t)| **d && !**t).count() as f64 / area;
|
||||
let shares = Difference { any, art, solid: solid.iter().filter(|s| **s).count() as f64 / area, typed: any - art };
|
||||
(shares, solid)
|
||||
}
|
||||
|
||||
/// Which pixels (row by row) of two drawings of the same size differ ([`differ`], those in `skip`
|
||||
/// counting as alike), which of those lie in the type's `areas`, and which differ with all their
|
||||
/// neighbors away from type: [`compared`]'s pixels. `None` when the drawings can't be compared.
|
||||
fn differing_pixels(
|
||||
x: &vectorcraft_render::Rendered,
|
||||
y: &vectorcraft_render::Rendered,
|
||||
areas: &[Rect],
|
||||
skip: &[Rect],
|
||||
) -> Option<(Vec<bool>, Vec<bool>, Vec<bool>)> {
|
||||
if x.width == 0 || x.height == 0 || x.width != y.width || x.height != y.height {
|
||||
return None;
|
||||
}
|
||||
let (w, h) = (x.width as usize, x.height as usize);
|
||||
// Pixels in `skip` count as alike.
|
||||
@@ -691,10 +738,9 @@ fn compared_with_solid(x: &vectorcraft_render::Rendered, y: &vectorcraft_render:
|
||||
let differs: Vec<bool> =
|
||||
x.pixels.as_chunks::<4>().0.iter().zip(y.pixels.as_chunks::<4>().0).zip(&skipped).map(|((p, q), s)| !*s && differ(p, q)).collect();
|
||||
if !differs.contains(&true) {
|
||||
return (Difference { any: 0.0, art: 0.0, solid: 0.0, typed: 0.0 }, vec![false; w * h]);
|
||||
return Some((differs, vec![false; w * h], vec![false; w * h]));
|
||||
}
|
||||
let typed = in_areas(areas, w, h);
|
||||
let art = differs.iter().zip(&typed).filter(|(d, t)| **d && !**t).count();
|
||||
let typed: Vec<bool> = in_areas(areas, w, h).into_iter().zip(&differs).map(|(t, d)| t && *d).collect();
|
||||
// Which pixels differ with their left and right neighbors.
|
||||
let mut across = vec![false; w * h];
|
||||
for (k, a) in across.iter_mut().enumerate() {
|
||||
@@ -706,11 +752,7 @@ fn compared_with_solid(x: &vectorcraft_render::Rendered, y: &vectorcraft_render:
|
||||
for (k, s) in solid.iter_mut().enumerate().skip(w).take(w * h.saturating_sub(2)) {
|
||||
*s = !typed.get(k).copied().unwrap_or(true) && [k - w, k, k + w].iter().all(|n| across.get(*n).copied().unwrap_or(false));
|
||||
}
|
||||
let area = (w as f64 * h as f64).max(1.0);
|
||||
let any = differs.iter().filter(|d| **d).count() as f64 / area;
|
||||
let art = art as f64 / area;
|
||||
let shares = Difference { any, art, solid: solid.iter().filter(|s| **s).count() as f64 / area, typed: any - art };
|
||||
(shares, solid)
|
||||
Some((differs, typed, solid))
|
||||
}
|
||||
|
||||
/// Where the type that shows in `nodes` is, in pixels of `region` drawn at `scale` (with a margin
|
||||
@@ -879,7 +921,7 @@ const NOTABLE_DIFFERENCE: f64 = 0.002;
|
||||
pub(in crate::import) const MAX_SOLID_DIFFERENCE: f64 = 0.001;
|
||||
|
||||
/// How far (points) the art the layers print may reach past a page that holds it all.
|
||||
const PAST_THE_PAGE: f64 = 1.0;
|
||||
pub(super) const PAST_THE_PAGE: f64 = 1.0;
|
||||
|
||||
/// Do the layers' art and the page's own look alike (where the page holds all the art that prints,
|
||||
/// also round it), on every page the frame has ([`Frame::more`])? An error says they don't; `Ok` has
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::build::{PAGE_MOVE, Page, alone_scale, build, build_from, build_on_page, build_without_page, reid_page_art};
|
||||
use super::build::{PAGE_MOVE, Page, alone_scale, build, build_from, build_on_page, build_weighing, build_without_page, reid_page_art};
|
||||
use super::slots::LAYERS_UNREAD;
|
||||
use super::{Affine, Arc, Cow, Document, Imported, Node, NodeKind, PathData, Rect, Vec2, ai};
|
||||
|
||||
@@ -177,15 +177,27 @@ pub fn layered_ai_from(
|
||||
add_notes(&mut all, read.notes());
|
||||
(visible, all, false)
|
||||
};
|
||||
let built = structure
|
||||
.unwrap_or_else(|| ai::read_over_page(&data, false, names).map(Box::new))
|
||||
.and_then(|structure| build_from(*structure, &data, Some(&visible), Page::Artboard, outlined, trust, unit));
|
||||
// The envelopes of type the page doesn't hold against layers that don't draw as it, and how many
|
||||
// envelopes of type the layers had.
|
||||
let (mut free, mut count) = (None, 0);
|
||||
let built = structure.unwrap_or_else(|| ai::read_over_page(&data, false, names).map(Box::new)).and_then(|structure| {
|
||||
count = structure.warped_type.envelope_count();
|
||||
build_weighing(*structure, &data, Some(&visible), Page::Artboard, outlined, trust, unit, &mut free)
|
||||
});
|
||||
// Layers that waited on the page only for their envelopes of type open alone, as they would
|
||||
// have without them, each envelope keeping its drawn art, unless one opened live with the page.
|
||||
let live = matches!(&built, Ok((done, _)) if holds_type_envelope(&done.layers));
|
||||
// have without them, unless one opened live with the layers drawing as the page. Alone, each
|
||||
// envelope keeps its drawn art but those the page didn't hold against the layers, which open
|
||||
// live as they were weighed. They were weighed on the layers as compared with the page, before
|
||||
// any object of them came in as the page's art: those layers aren't used here, whatever came of
|
||||
// them.
|
||||
let live = free.is_none() && matches!(&built, Ok((done, _)) if holds_type_envelope(&done.layers));
|
||||
if !live
|
||||
&& let Some((mut done, notes)) = alone.and_then(|(pages, unit)| {
|
||||
let structure = ai::read_over_page(&data, false, names).ok()?;
|
||||
let mut structure = ai::read_over_page(&data, false, names).ok()?;
|
||||
// With none free, nothing needs settling again: every envelope keeps its drawn art.
|
||||
if let Some(free) = free.as_deref().filter(|free| !free.is_empty()) {
|
||||
structure.warped_type.open_alone(free, count);
|
||||
}
|
||||
build_without_page(structure, &data, &pages, unit).ok()?.ok()
|
||||
})
|
||||
{
|
||||
|
||||
@@ -5528,6 +5528,11 @@ fn type_envelope(keys: &str, drawn: &str) -> String {
|
||||
/// [`type_envelope`], with `after` (on plain lines, written on `%_` ones) after the text object in
|
||||
/// its contents, such as the text object's XML id.
|
||||
fn type_envelope_with(keys: &str, drawn: &str, after: &str) -> String {
|
||||
type_envelope_of(0, keys, drawn, after)
|
||||
}
|
||||
|
||||
/// [`type_envelope_with`] around the type of story `story`.
|
||||
fn type_envelope_of(story: u32, keys: &str, drawn: &str, after: &str) -> String {
|
||||
let corner = |x: f64, y: f64, nx: f64, ny: f64, px: f64, py: f64| format!("[0.8 1 {x} {y} {nx} {ny} 1 {px} {py} 1] /N X#\n");
|
||||
let mesh = format!(
|
||||
"/Mesh X!\n2 /Version X#\n/Cartesian /Type X#\n[1 1] /Size X#\n/Data X#\n/DeviceGray /CS X#\n[0 0] /P X#\n{}{}{}{}/E X#\n/End X!\n",
|
||||
@@ -5536,18 +5541,23 @@ fn type_envelope_with(keys: &str, drawn: &str, after: &str) -> String {
|
||||
corner(80.0, 70.0, 80.0, 60.0, 70.0, 70.0),
|
||||
corner(80.0, 40.0, 70.0, 40.0, 80.0, 50.0),
|
||||
);
|
||||
type_envelope_on(keys, &mesh, drawn, after)
|
||||
type_envelope_on_story(story, keys, &mesh, drawn, after)
|
||||
}
|
||||
|
||||
/// [`type_envelope_with`], with `shape` (on plain lines, written on `%_` ones) in the group before
|
||||
/// its dictionary, where the editing data saves the envelope's mesh.
|
||||
fn type_envelope_on(keys: &str, shape: &str, drawn: &str, after: &str) -> String {
|
||||
type_envelope_on_story(0, keys, shape, drawn, after)
|
||||
}
|
||||
|
||||
/// [`type_envelope_on`] around the type of story `story`.
|
||||
fn type_envelope_on_story(story: u32, keys: &str, shape: &str, drawn: &str, after: &str) -> String {
|
||||
let hidden = |s: &str| -> String { s.lines().map(|l| format!("%_{l}\n")).collect() };
|
||||
format!(
|
||||
"{}{}%_X=\n{}%_X+\n%_ (AIDeformArtKey) ,\n%_1 /Int (AIDeformStyle) ,\n%_;\n%_\n%_9 () XW\nu\n{drawn}U\n9 () XW\n(Adobe Deform Plugin) 1 0 0 XP\n",
|
||||
hidden(&format!("u\n{shape}U\n")),
|
||||
hidden(&format!("/ArtDictionary :\n{keys}")),
|
||||
hidden(&format!("u\n{}{after}U\n9 () XW\n", text_object(0))),
|
||||
hidden(&format!("u\n{}{after}U\n9 () XW\n", text_object(story))),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5594,7 +5604,12 @@ fn arc_envelope(text: &Node, bend: f64) -> Node {
|
||||
/// The type of the file with an envelope of type whose drawn art is `drawn`, read with its text
|
||||
/// document `doc`: the hidden text object it keeps, as the reader makes it.
|
||||
fn made_envelope_type(doc: &str) -> Node {
|
||||
let art = native_with_text(&layer("Art", true, &type_envelope(&arc_keys(0.3), &format!("{}\n", square(10, 10)))), doc);
|
||||
made_envelope_type_of(doc, 0)
|
||||
}
|
||||
|
||||
/// [`made_envelope_type`] of story `story`.
|
||||
fn made_envelope_type_of(doc: &str, story: u32) -> Node {
|
||||
let art = native_with_text(&layer("Art", true, &type_envelope_of(story, &arc_keys(0.3), &format!("{}\n", square(10, 10)), "")), doc);
|
||||
let r = import(&eps(&page_square(10, 10), &art)).unwrap();
|
||||
let mut text = None;
|
||||
r.document.walk(|n| {
|
||||
@@ -5685,9 +5700,9 @@ fn an_envelope_of_white_type_that_draws_apart_from_its_drawn_art_keeps_it() {
|
||||
|
||||
/// A `.ai` whose layers would open alone, without its PDF part, waits on the PDF part when an
|
||||
/// envelope of type is in them, as for the other live envelopes: one opens live only where the
|
||||
/// layers with it draw as the page. Where they don't, they open alone as before, the envelope
|
||||
/// keeping its drawn art and its type, hidden, with the note, even though it would draw as that
|
||||
/// drawn art; where they do, it opens live (#3179).
|
||||
/// layers with it draw as the page. Where they don't, the page drawing nothing where the envelope
|
||||
/// is, they open alone as before, the envelope keeping its drawn art and its type, hidden, with the
|
||||
/// note, even though it would draw as that drawn art; where they do, it opens live (#3179).
|
||||
#[test]
|
||||
fn an_envelope_of_type_waits_on_the_page_of_layers_that_would_open_alone() {
|
||||
let Some(family) = installed_family() else { return };
|
||||
@@ -5751,9 +5766,10 @@ fn an_envelope_of_type_that_draws_apart_from_the_page_does_not_open_live() {
|
||||
}
|
||||
|
||||
/// Layers used though they draw differently from the page (the editing data trusted) leave an
|
||||
/// envelope of type its drawn art and its type, hidden, with the note, even where it draws as that
|
||||
/// drawn art: one opens live only where the layers with it draw as the page. A `.ai` whose layers
|
||||
/// would open alone then opens alone, as it would without the envelope (#3179).
|
||||
/// envelope of type its drawn art and its type, hidden, with the note, where the page draws
|
||||
/// nothing where the envelope is, even where it draws as that drawn art: one opens live only where
|
||||
/// the layers with it draw as the page there. A `.ai` whose layers would open alone then opens
|
||||
/// alone, as it would without the envelope (#3179).
|
||||
#[test]
|
||||
fn trusted_layers_that_draw_apart_from_the_page_leave_an_envelope_of_type_its_drawn_art() {
|
||||
let Some(family) = installed_family() else { return };
|
||||
@@ -5789,19 +5805,27 @@ fn trusted_layers_that_draw_apart_from_the_page_leave_an_envelope_of_type_its_dr
|
||||
assert!(!notes.iter().any(|n| n.ends_with("they come from its editing data all the same")), "{notes:?}");
|
||||
}
|
||||
|
||||
/// Layers whose objects that draw differently from the page come in as its art leave an envelope
|
||||
/// of type its drawn art and its type, hidden, with the note, even where the envelope draws as the
|
||||
/// page: the layers with it don't (#3179).
|
||||
#[test]
|
||||
fn layers_with_objects_that_come_in_as_the_page_art_leave_an_envelope_of_type_its_drawn_art() {
|
||||
let Some(family) = installed_family() else { return };
|
||||
let doc = type_envelope_text(&family, None);
|
||||
let (drawn, page) = drawn_envelope(&arc_envelope(&made_envelope_type(&doc), 30.0));
|
||||
// A square black in the layers and cyan on the page, away from the envelope.
|
||||
/// The layers and the page of a file with an envelope of type that draws as its drawn art, and a
|
||||
/// square black in the layers and cyan on the page, away from the envelope; the page draws the
|
||||
/// envelope bent by `page_bend` (percent).
|
||||
fn envelope_beside_a_differing_square(family: &str, page_bend: f64) -> (String, String) {
|
||||
let doc = type_envelope_text(family, None);
|
||||
let text = made_envelope_type(&doc);
|
||||
let (drawn, _) = drawn_envelope(&arc_envelope(&text, 30.0));
|
||||
let (_, page) = drawn_envelope(&arc_envelope(&text, page_bend));
|
||||
let body = format!("{}\n{}\n{}", square(10, 10), square_16(76, BLACK), type_envelope(&arc_keys(0.3), &drawn));
|
||||
let art = native_with_text(&layer("Art", true, &body), &doc);
|
||||
let cyan = "1 0 0 0 setcmykcolor 76 10 moveto 92 10 lineto 92 26 lineto 76 26 lineto closepath fill";
|
||||
let r = import(&eps(&format!("{}\n{cyan}\n{page}", page_square(10, 10)), &art)).unwrap();
|
||||
(native_with_text(&layer("Art", true, &body), &doc), format!("{}\n{cyan}\n{page}", page_square(10, 10)))
|
||||
}
|
||||
|
||||
/// Layers whose objects that draw differently from the page come in as its art keep an envelope of
|
||||
/// type live where the envelope draws as the page: the art that differs isn't the envelope's, and
|
||||
/// the layers with it then draw as the page (#3179).
|
||||
#[test]
|
||||
fn layers_with_objects_that_come_in_as_the_page_art_keep_an_envelope_of_type_that_draws_as_the_page_live() {
|
||||
let Some(family) = installed_family() else { return };
|
||||
let (art, page) = envelope_beside_a_differing_square(&family, 30.0);
|
||||
let r = import(&eps(&page, &art)).unwrap();
|
||||
assert_eq!(names(&r.document), ["Art"], "{:?}", r.warnings);
|
||||
assert!(
|
||||
r.warnings.iter().any(|n| n.starts_with("objects that drew differently from the file's own page came in as its art")),
|
||||
@@ -5809,12 +5833,169 @@ fn layers_with_objects_that_come_in_as_the_page_art_leave_an_envelope_of_type_it
|
||||
r.warnings
|
||||
);
|
||||
let (envelopes, loose) = envelopes_and_type(&r.document);
|
||||
assert_eq!(envelopes, [vec!["FLUX".to_string()]], "{:?}", r.warnings);
|
||||
assert!(loose.is_empty(), "{loose:?}");
|
||||
assert!(!r.warnings.iter().any(|n| n == crate::import::ai::ENVELOPE_TYPE_DRAWN), "{:?}", r.warnings);
|
||||
}
|
||||
|
||||
/// Layers used though they draw differently from the page (the editing data trusted) leave an
|
||||
/// envelope of type its drawn art and its type, hidden, with the note, where the page draws it
|
||||
/// otherwise than they do (bent the other way), though other art differs too: the page holds the
|
||||
/// envelope against them where it is (#3179).
|
||||
#[test]
|
||||
fn trusted_layers_leave_an_envelope_of_type_the_page_draws_otherwise_its_drawn_art() {
|
||||
let Some(family) = installed_family() else { return };
|
||||
let (art, page) = envelope_beside_a_differing_square(&family, -30.0);
|
||||
let r = crate::import_trusting(&eps(&page, &art), true, true).unwrap();
|
||||
assert_eq!(names(&r.document), ["Art"], "{:?}", r.warnings);
|
||||
assert!(r.warnings.iter().any(|n| n.ends_with("they come from its editing data all the same")), "{:?}", r.warnings);
|
||||
let (envelopes, loose) = envelopes_and_type(&r.document);
|
||||
assert!(envelopes.is_empty(), "{envelopes:?}");
|
||||
assert_eq!(loose, [("FLUX".to_string(), false)], "{:?}", r.warnings);
|
||||
assert_eq!(loose_type_names(&r.document), [None]);
|
||||
assert!(r.warnings.iter().any(|n| n == crate::import::ai::ENVELOPE_TYPE_DRAWN), "{:?}", r.warnings);
|
||||
}
|
||||
|
||||
/// Layers used though they draw differently from the page (the editing data trusted) keep an
|
||||
/// envelope of type live where the page draws it as they do and the art that differs is elsewhere
|
||||
/// (#3179).
|
||||
#[test]
|
||||
fn trusted_layers_keep_an_envelope_of_type_live_where_other_art_differs_from_the_page() {
|
||||
let Some(family) = installed_family() else { return };
|
||||
let (art, page) = envelope_beside_a_differing_square(&family, 30.0);
|
||||
let r = crate::import_trusting(&eps(&page, &art), true, true).unwrap();
|
||||
assert_eq!(names(&r.document), ["Art"], "{:?}", r.warnings);
|
||||
assert!(r.warnings.iter().any(|n| n.ends_with("they come from its editing data all the same")), "{:?}", r.warnings);
|
||||
let (envelopes, loose) = envelopes_and_type(&r.document);
|
||||
assert_eq!(envelopes, [vec!["FLUX".to_string()]], "{:?}", r.warnings);
|
||||
assert!(loose.is_empty(), "{loose:?}");
|
||||
assert!(!r.warnings.iter().any(|n| n == crate::import::ai::ENVELOPE_TYPE_DRAWN), "{:?}", r.warnings);
|
||||
}
|
||||
|
||||
/// A `.ai` whose layers would open alone, and that waits on its PDF part for an envelope of type,
|
||||
/// opens alone with the envelope live where the page draws it as the layers do, though the page
|
||||
/// also draws art the layers don't have: the envelope's drawn art put back wouldn't make the layers
|
||||
/// draw as the page (#3179).
|
||||
#[test]
|
||||
fn layers_that_would_open_alone_keep_an_envelope_of_type_live_where_other_art_differs_from_the_page() {
|
||||
let Some(family) = installed_family() else { return };
|
||||
let stories = [("FLUX\r", 0, (8170.0, 8180.0), (0.0, 0.0), 0.0), ("Note\r", 0, (8170.0, 8220.0), (0.0, 0.0), 0.0)];
|
||||
let mut doc = text_document(&stories).replace("(Helvetica)", &format!("({family})"));
|
||||
// [`native_with_text`] writes 60 characters a line: the end of the data, `~>`, isn't split.
|
||||
while ascii85(doc.as_bytes()).len() % 60 == 1 {
|
||||
doc.push('\n');
|
||||
}
|
||||
let (drawn, _) = drawn_envelope(&arc_envelope(&made_envelope_type(&doc), 30.0));
|
||||
let art = format!("u\n{}U\n{}", text_object(1), type_envelope(&arc_keys(0.3), &drawn));
|
||||
let file = private(&native_with_text(&layer("Art", true, &art), &doc));
|
||||
let names = crate::NameEncoding::default();
|
||||
let open = |page: Document| {
|
||||
let Err(layers) = crate::ai_layers_alone(&file, &[(100.0, 100.0)], 1.0, names) else { panic!("opened without the page") };
|
||||
crate::layered_ai_from(layers, page, vec![], false, false, 1.0, names)
|
||||
};
|
||||
// The layers alone, with the envelope's drawn art: what the page draws.
|
||||
let (mut page, notes, _) = open(pdf_part(80.0, 80.0));
|
||||
assert!(envelopes_and_type(&page).0.is_empty(), "{notes:?}");
|
||||
// And a black square the layers don't have, away from the type.
|
||||
add_black_square(&mut page, 80.0, 4.0);
|
||||
let (d, notes, read) = open(page);
|
||||
assert!(read, "{notes:?}");
|
||||
let (envelopes, loose) = envelopes_and_type(&d);
|
||||
assert_eq!(envelopes, [vec!["FLUX".to_string()]], "{notes:?}");
|
||||
assert_eq!(loose, [("Note".to_string(), true)], "{notes:?}");
|
||||
assert!(!notes.iter().any(|n| n == crate::import::ai::ENVELOPE_TYPE_DRAWN), "{notes:?}");
|
||||
}
|
||||
|
||||
/// A black square of 16 points at (`x`, `y`) put on top of `page`'s first layer.
|
||||
fn add_black_square(page: &mut Document, x: f64, y: f64) {
|
||||
let paint = vectorcraft_color::Paint::solid(vectorcraft_color::Color::BLACK);
|
||||
let item = vectorcraft_doc::AppearanceItem::Fill(vectorcraft_doc::FillLayer::new(paint));
|
||||
let path = vectorcraft_geom::shapes::rectangle(vectorcraft_geom::Rect::new(x, y, x + 16.0, y + 16.0));
|
||||
let square = Node::path(page.alloc_id(), path, vectorcraft_doc::Appearance { items: vec![item], ..Default::default() });
|
||||
let layer = page.layers[0].id;
|
||||
page.insert(Some(layer), 0, square).unwrap();
|
||||
}
|
||||
|
||||
/// A `.ai` whose layers would open alone, with the editing data trusted, opens alone with an
|
||||
/// envelope of type live where the page draws it as the layers do, though the page also draws art
|
||||
/// they don't have: not from the layers compared with the page, which would say they come from the
|
||||
/// editing data all the same (#3179).
|
||||
#[test]
|
||||
fn trusted_layers_that_would_open_alone_open_alone_with_an_envelope_of_type_the_page_doesnt_hold_live() {
|
||||
let Some(family) = installed_family() else { return };
|
||||
let stories = [("FLUX\r", 0, (8170.0, 8180.0), (0.0, 0.0), 0.0), ("Note\r", 0, (8170.0, 8220.0), (0.0, 0.0), 0.0)];
|
||||
let mut doc = text_document(&stories).replace("(Helvetica)", &format!("({family})"));
|
||||
while ascii85(doc.as_bytes()).len() % 60 == 1 {
|
||||
doc.push('\n');
|
||||
}
|
||||
let (drawn, _) = drawn_envelope(&arc_envelope(&made_envelope_type(&doc), 30.0));
|
||||
let art = format!("u\n{}U\n{}", text_object(1), type_envelope(&arc_keys(0.3), &drawn));
|
||||
let file = private(&native_with_text(&layer("Art", true, &art), &doc));
|
||||
let names = crate::NameEncoding::default();
|
||||
let open = |page: Document, trust: bool| {
|
||||
let Err(layers) = crate::ai_layers_alone(&file, &[(100.0, 100.0)], 1.0, names) else { panic!("opened without the page") };
|
||||
crate::layered_ai_from(layers, page, vec![], false, trust, 1.0, names)
|
||||
};
|
||||
let (mut page, _, _) = open(pdf_part(80.0, 80.0), false);
|
||||
add_black_square(&mut page, 80.0, 4.0);
|
||||
let (d, notes, read) = open(page, true);
|
||||
assert!(read, "{notes:?}");
|
||||
let (envelopes, loose) = envelopes_and_type(&d);
|
||||
assert_eq!(envelopes, [vec!["FLUX".to_string()]], "{notes:?}");
|
||||
assert_eq!(loose, [("Note".to_string(), true)], "{notes:?}");
|
||||
assert!(notes.is_empty(), "{notes:?}");
|
||||
}
|
||||
|
||||
/// Of two envelopes of type in a `.ai` whose layers would open alone, the one the page holds
|
||||
/// against the layers (it draws nothing where the envelope is) keeps its drawn art and its type,
|
||||
/// hidden, with the note, and the other opens live: each by its place among the envelopes of type
|
||||
/// as the layers are read, both times they are (#3179).
|
||||
#[test]
|
||||
fn layers_that_would_open_alone_keep_live_only_the_envelopes_of_type_the_page_doesnt_hold() {
|
||||
let Some(family) = installed_family() else { return };
|
||||
let stories = [
|
||||
("FLUX\r", 0, (8170.0, 8180.0), (0.0, 0.0), 0.0),
|
||||
("HILL\r", 0, (8170.0, 8200.0), (0.0, 0.0), 0.0),
|
||||
("Note\r", 0, (8170.0, 8220.0), (0.0, 0.0), 0.0),
|
||||
];
|
||||
let mut doc = text_document(&stories).replace("(Helvetica)", &format!("({family})"));
|
||||
while ascii85(doc.as_bytes()).len() % 60 == 1 {
|
||||
doc.push('\n');
|
||||
}
|
||||
let drawn = |story| drawn_envelope(&arc_envelope(&made_envelope_type_of(&doc, story), 30.0)).0;
|
||||
let art = format!(
|
||||
"u\n{}U\n{}{}",
|
||||
text_object(2),
|
||||
type_envelope_of(0, &arc_keys(0.3), &drawn(0), ""),
|
||||
type_envelope_of(1, &arc_keys(0.3), &drawn(1), "")
|
||||
);
|
||||
let file = private(&native_with_text(&layer("Art", true, &art), &doc));
|
||||
let names = crate::NameEncoding::default();
|
||||
let open = |page: Document| {
|
||||
let Err(layers) = crate::ai_layers_alone(&file, &[(100.0, 100.0)], 1.0, names) else { panic!("opened without the page") };
|
||||
crate::layered_ai_from(layers, page, vec![], false, false, 1.0, names)
|
||||
};
|
||||
// The layers alone, with the drawn art of both envelopes, but the first's: the page draws
|
||||
// nothing where the first is.
|
||||
let (mut page, notes, _) = open(pdf_part(80.0, 80.0));
|
||||
let top = page.layers[0].clone();
|
||||
let first = top
|
||||
.children()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|n| matches!(n.kind, NodeKind::Group { .. }))
|
||||
.min_by(|a, b| a.visual_bounds().map_or(f64::MAX, |r| r.y0).total_cmp(&b.visual_bounds().map_or(f64::MAX, |r| r.y0)))
|
||||
.map(|n| n.id)
|
||||
.unwrap_or_else(|| panic!("no drawn art: {notes:?}"));
|
||||
page.remove(first).unwrap();
|
||||
let (d, notes, read) = open(page);
|
||||
assert!(read, "{notes:?}");
|
||||
let (envelopes, mut loose) = envelopes_and_type(&d);
|
||||
assert_eq!(envelopes, [vec!["HILL".to_string()]], "{notes:?}");
|
||||
loose.sort();
|
||||
assert_eq!(loose, [("FLUX".to_string(), false), ("Note".to_string(), true)], "{notes:?}");
|
||||
assert!(notes.iter().any(|n| n == crate::import::ai::ENVELOPE_TYPE_DRAWN), "{notes:?}");
|
||||
}
|
||||
|
||||
/// An envelope of type whose type a visibility variable binds keeps its drawn art, as one whose type
|
||||
/// a text variable binds does: the binding stays the hidden type's, which the live envelope would
|
||||
/// take the place of (#3179).
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user