mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Legacy PostScript .ai files (Illustrator 3-8, and their EPS) open through the EPS interpreter, which only recorded what was drawn: every group came in flattened. In a file in Illustrator's format (its %AI comments, or Illustrator named as the creator) the `u` ... `U` it writes at the top level now open and close groups, whatever its prolog defines them as, so groups come in nested as they were, between and inside clipping groups. clipnest's Clip carries an optional region: without one it is a plain group, nested in the same chain as the clips (ids given in opening order, so a chain of clips and groups sorts by id). Clips nested without end used to nest clipping groups past the stack (a stack overflow in EPS and EMF/WMF import): clips and groups now nest at most 128 deep with a warning, and each object's chain counts against the EPS reader's drawing budget. PDF-compatible .ai (Illustrator 9+) can't get the same: its PDF content draws each object on its own and doesn't mark plain groups (only layers, clipping groups and groups with opacity, blending or a mask, which already come back); the groups live only in the private data, which is out of scope. Documented in ROADMAP and docs/mcp.md. Addresses Discord feedback. Co-Authored-By: Claude Opus 5.5 <[email protected]>
83 lines
3.2 KiB
Rust
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() }))
|
|
}
|