Files
vectorcraft/crates/doc/src/clipnest.rs
T
bflatasticandClaude Opus 5.5 0be7640088 Part of #637: the editing data's structure read whole, colours, gradients, transparency, names, images and artboards included
The layers reader of the editing copy (operators translated into
PostScript for the interpreter, any file with RGB colours, gradients,
transparency or images falling back to its page) is replaced by a reader
of the whole structure (import/ai): layers and sublayers with their
options, groups, compound paths, clipping groups and layer clipping,
names, hidden and locked objects, RGB, CMYK, grey, named and spot
colours, gradients, transparency, fill rule, overprint, embedded images
with their alpha channel, guides and every artboard, and objects drawn
as their look when they have several fills or strokes, effects or a
brush. Containers: Zstandard, zlib or plain data in EPS and .ai.

The text placement, the comparison with the page and the overwrite guard
stay around it. The page's type takes its text object's place without a
group; the comparison is by lightness, on nothing, without layers that
don't print. Symbols, pattern fills, placed files and unknown operators
make the file come in as its page where they show, and are left out of
layers that don't.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:02:36 +07:00

83 lines
3.2 KiB
Rust

//! Clipped and grouped art read from page descriptions (metafiles, PostScript) into clipping
//! groups and groups.
//!
//! Importers record each object they draw with the chain of clips (and groups) in force at the
//! time, outermost first ([`Drawn`]); [`nest`] then puts runs of objects under the same clip into
//! one clipping group (under the same group into one group), nesting groups for nested clips,
//! keeping the drawing order.
use std::sync::Arc;
use vectorcraft_geom::{BezPath, FillRule, PathData};
use crate::appearance::Appearance;
use crate::{Document, Node, NodeKind};
/// A clip: a region in document space and its rule, or a plain group (one that holds its art
/// without clipping it, such as an Illustrator file's `u` … `U`). Each has an id unique within an
/// import, so what one clip clips (or one group holds) can be grouped.
#[derive(Debug)]
pub struct Clip {
pub id: u32,
/// The region and its rule; `None` for a plain group.
pub region: Option<(BezPath, FillRule)>,
}
/// Deepest nesting of clips, and of groups, an importer records ([`nest`] goes one level deeper
/// for each): deeper ones are left out.
pub const MAX_NEST: usize = 128;
/// The warning for clips left out for nesting deeper than [`MAX_NEST`].
pub fn deep_clips() -> String {
format!("clips nested more than {MAX_NEST} deep were left out: what they clip is clipped by the clips around them")
}
/// Objects in drawing order, each with its clips (outermost first).
pub type Drawn = Vec<(Vec<Arc<Clip>>, Node)>;
/// The nodes of `items`, those under clips grouped into clipping groups and those in groups into
/// groups (ids from `doc`), in order.
pub fn nest(doc: &mut Document, items: Drawn) -> Vec<Arc<Node>> {
nest_at(doc, items, 0)
}
/// [`nest`] for the clips at `depth` and deeper.
fn nest_at(doc: &mut Document, items: Drawn, depth: usize) -> Vec<Arc<Node>> {
let mut out: Vec<Arc<Node>> = vec![];
let mut group: Drawn = vec![];
let mut clip: Option<Arc<Clip>> = None;
for (chain, node) in items {
let here = chain.get(depth).cloned();
if here.as_ref().map(|c| c.id) != clip.as_ref().map(|c| c.id) {
if let Some(c) = clip.take() {
out.push(clip_group(doc, &c, std::mem::take(&mut group), depth));
}
clip = here.clone();
}
match here {
Some(_) => group.push((chain, node)),
None => out.push(Arc::new(node)),
}
}
if let Some(c) = clip {
out.push(clip_group(doc, &c, group, depth));
}
out
}
/// A clipping group of `items`, clipped by `c` (their clip at `depth`), or their group when `c` is
/// a plain group.
fn clip_group(doc: &mut Document, c: &Clip, items: Drawn, depth: usize) -> Arc<Node> {
let mut children = vec![];
if let Some((bp, r)) = &c.region {
let mut path = Node::path(doc.alloc_id(), PathData::from_bezpath(bp), Appearance::default());
if let NodeKind::Path { clipping, rule, .. } = &mut path.kind {
*clipping = true;
*rule = *r;
}
children.push(Arc::new(path));
}
children.extend(nest_at(doc, items, depth + 1));
Arc::new(Node::new(doc.alloc_id(), NodeKind::Group { children, clip: c.region.is_some() }))
}