mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 09:33:54 +00:00
Save As .ai: write inline graphics in type as their art in the editing data
Type is written as its outlines, and the outlines leave out inline graphics, so the editing data lost them while the PDF page drew them. Each inline graphic is now written as its symbol's art where the layout places it, over the characters and under the object's own fills and strokes above the Characters row, in the order the PDF page draws them.
This commit is contained in:
@@ -36,7 +36,7 @@
|
||||
//! the first artboard's top-left corner. On the canvas ([`CANVAS_SIZE`] points square, y down) the
|
||||
//! artboards together are centered; each artboard's `RulerOrigin` is its top-left corner there, and
|
||||
//! `%AI3_TemplateBox` holds the canvas's center in art space. What the form above can't hold is written as plain filled and stroked paths, each such
|
||||
//! loss a warning: type as its outlines, gradients as their average color, pattern fills as their
|
||||
//! loss a warning: type as its outlines and its inline graphics' art, gradients as their average color, pattern fills as their
|
||||
//! tiles cut to the shape, clipping groups and clipping layers as their art cut to the clipping
|
||||
//! path (the clipping path's fill cut too, its stroke not), images as their outlines filled with
|
||||
//! their average color, symbols and live objects as their art, strokes a plain line can't draw as
|
||||
|
||||
@@ -617,6 +617,40 @@ fn type_is_written_as_create_outlines_makes_it() {
|
||||
assert_eq!(rounded(group), rounded(&outlined));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_graphics_in_type_are_written_as_their_art() {
|
||||
let mut doc = Document::new(200.0, 100.0);
|
||||
let layer = doc.layers[0].id;
|
||||
let mark = Node::path(
|
||||
doc.alloc_id(),
|
||||
shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)),
|
||||
Appearance::basic(solid(Color::rgb(0.0, 0.0, 1.0)), Paint::None, 0.0),
|
||||
);
|
||||
doc.symbols.push(Symbol { name: "Mark".into(), art: Arc::new(mark) });
|
||||
let style = vectorcraft_doc::CharStyle { size: 18.0, fill: solid(Color::BLACK), ..vectorcraft_doc::CharStyle::default() };
|
||||
let mut t = vectorcraft_doc::TextObject::point(vectorcraft_geom::Point::new(10.0, 70.0), "A", style.clone());
|
||||
t.runs.push(vectorcraft_doc::TextRun::inline(vectorcraft_doc::InlineArt::new("Mark"), style));
|
||||
let mut text = Node::new(doc.alloc_id(), NodeKind::Text(Box::new(t)));
|
||||
// A stroke of the object's own above the Characters row: drawn over the inline graphic.
|
||||
let red = solid(Color::rgb(1.0, 0.0, 0.0));
|
||||
text.appearance =
|
||||
Appearance { items: vec![AppearanceItem::Stroke(StrokeLayer::new(red.clone(), 3.0))], contents_index: Some(0), ..Appearance::default() }
|
||||
.into();
|
||||
doc.insert(Some(layer), usize::MAX, text).unwrap();
|
||||
let back = read_back(&doc);
|
||||
let mut all = vec![];
|
||||
paths(back.layers[0].children().unwrap(), &mut all);
|
||||
let has = |p: &Node, c: &Paint| p.appearance.items.iter().any(|i| paint(i.paint()) == paint(c));
|
||||
let blue = solid(Color::rgb(0.0, 0.0, 1.0));
|
||||
let art: Vec<&Node> = all.iter().filter(|p| has(p, &blue)).collect();
|
||||
assert_eq!(art.len(), 1, "the inline graphic is written");
|
||||
// To the right of the letter, 18 pt high as the run's size makes it.
|
||||
let b = art[0].visual_bounds().unwrap();
|
||||
assert!(b.x0 > 10.0 && (b.height() - 18.0).abs() < 0.01, "{b:?}");
|
||||
let at = |c: &Paint| all.iter().position(|p| has(p, c)).unwrap();
|
||||
assert!(at(&blue) < at(&red), "the stroke above the characters is over the inline graphic");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stained_symbol_instance_is_written_in_its_stained_colors() {
|
||||
let mut doc = Document::new(200.0, 100.0);
|
||||
|
||||
@@ -507,19 +507,55 @@ impl<'a> Writer<'a> {
|
||||
|
||||
/// Type `n` as a group of what Type › Create Outlines makes of it
|
||||
/// ([`vectorcraft_effects::outline_text`]): each run's glyphs with their underline and
|
||||
/// strikethrough, its color glyphs, and the object's own fills and strokes.
|
||||
/// strikethrough, its color glyphs, and the object's own fills and strokes; then its inline
|
||||
/// graphics as their symbols' art where the layout places them, as the PDF page draws them.
|
||||
fn text(&mut self, n: &Node) {
|
||||
self.warn(TYPE);
|
||||
self.line("u");
|
||||
if let Some(outlined) = vectorcraft_effects::outline_text(n) {
|
||||
let state = self.state;
|
||||
for c in outlined.children().map(Vec::as_slice).unwrap_or_default() {
|
||||
self.node(c);
|
||||
}
|
||||
if let Some((locked, hidden)) = state {
|
||||
self.state(locked, hidden);
|
||||
let doc = self.doc;
|
||||
let resolved = match &n.kind {
|
||||
NodeKind::Text(t) => Some(doc.inline_resolved(t).into_owned()),
|
||||
_ => None,
|
||||
};
|
||||
let node = match &resolved {
|
||||
Some(t) => Node { kind: NodeKind::Text(Box::new(t.clone())), ..n.clone() },
|
||||
None => n.clone(),
|
||||
};
|
||||
let state = self.state;
|
||||
let outlined = vectorcraft_effects::outline_text(&node);
|
||||
let pieces = outlined.as_ref().and_then(|o| o.children()).map(Vec::as_slice).unwrap_or_default();
|
||||
// The object's fills and strokes above the Characters row, when they paint, are the last
|
||||
// piece: the inline graphics go under it, as the PDF page draws them.
|
||||
let above = !node.appearance.split_contents().1.is_empty() && {
|
||||
let bare = Node { appearance: Default::default(), ..node.clone() };
|
||||
let glyphs = vectorcraft_effects::outline_text(&bare).and_then(|o| o.children().map(Vec::len)).unwrap_or_default();
|
||||
pieces.len() > glyphs
|
||||
};
|
||||
let (under, over) = pieces.split_at(pieces.len() - usize::from(above));
|
||||
for c in under {
|
||||
self.node(c);
|
||||
}
|
||||
if let Some(t) = resolved.as_ref().filter(|_| self.nest < MAX_NEST) {
|
||||
let layout = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), t);
|
||||
for ig in &layout.inlines {
|
||||
let Some(art) = t.runs.get(ig.run).and_then(|r| r.inline.as_ref()) else { continue };
|
||||
let Some(sym) = doc.symbols.iter().find(|x| x.name == art.symbol) else { continue };
|
||||
self.warn(LIVE);
|
||||
let mut art_node = (*sym.art).clone();
|
||||
// Strokes scale with the art, as on the canvas.
|
||||
art_node.transform(t.xf * ig.xf * doc.symbol_natural_xf(&art.symbol), true);
|
||||
(art_node.visible, art_node.locked, art_node.name) = (true, false, None);
|
||||
self.nest += 1;
|
||||
self.node(&art_node);
|
||||
self.nest -= 1;
|
||||
}
|
||||
}
|
||||
for c in over {
|
||||
self.node(c);
|
||||
}
|
||||
if let Some((locked, hidden)) = state {
|
||||
self.state(locked, hidden);
|
||||
}
|
||||
self.line("U");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user