mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 10:17:02 -06:00
Threaded text: Flatten Transparency on a frame no longer duplicates its text
Object › Flatten Transparency replaced a frame of threaded text by its outlines with Convert All Text to Outlines, and by flat regions or an image where the frame was transparent. The thread's re-flow then added the frame's text to the story, as it does for a frame that was deleted, so the other frames held that text a second time while the outlines, regions or image drew it. A frame that Flatten Transparency outlines or flattens now leaves its thread with the text it shows, and the other frames keep the text they hold, as with Create Outlines and Expand. When the thread's last frame is flattened, the text it has no room for goes after the text of the last frame that stays. A thread left with one frame is no longer a thread, and undo restores the thread. Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -47,7 +47,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Flatten Transparency…",
|
||||
["Object"],
|
||||
None,
|
||||
"{preset?: \"high\"|\"medium\"|\"low\" or a saved preset's name (see flattener.presets.list; default medium), balance?: 0..100 (raster/vector balance; 0 rasterizes everything), lineArtPpi?: 1..2400, gradientPpi?: 1..2400 (areas only gradients and meshes reach), textToOutlines?, strokesToOutlines?, clipComplexRegions? (clip images to the region outlines, else rectangles), antiAlias?, preserveAlpha? (composite over nothing instead of white), preserveOverprints? (areas showing one paint keep its colour and overprint; false clears overprints), options?: {the same keys}, ids?|id?} overlapping transparent objects become one group of flat-colour regions (CMYK colours composited ink by ink in CMYK documents), plus an image where gradients, patterns, images, masks or raster effects reach; objects without transparency stay → {ids, rasterized: images made, vector: regions made, options, warning? (an image in the art couldn't be decoded, as object.rasterize says)}",
|
||||
"{preset?: \"high\"|\"medium\"|\"low\" or a saved preset's name (see flattener.presets.list; default medium), balance?: 0..100 (raster/vector balance; 0 rasterizes everything), lineArtPpi?: 1..2400, gradientPpi?: 1..2400 (areas only gradients and meshes reach), textToOutlines?, strokesToOutlines?, clipComplexRegions? (clip images to the region outlines, else rectangles), antiAlias?, preserveAlpha? (composite over nothing instead of white), preserveOverprints? (areas showing one paint keep its colour and overprint; false clears overprints), options?: {the same keys}, ids?|id?} overlapping transparent objects become one group of flat-colour regions (CMYK colours composited ink by ink in CMYK documents), plus an image where gradients, patterns, images, masks or raster effects reach; objects without transparency stay; a frame of threaded text that is outlined or flattened leaves its thread with the text it shows → {ids, rasterized: images made, vector: regions made, options, warning? (an image in the art couldn't be decoded, as object.rasterize says)}",
|
||||
has_doc,
|
||||
flatten
|
||||
),
|
||||
@@ -268,7 +268,14 @@ fn flatten(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if plan.flat.is_empty() && !plan.kept.iter().any(|id| src.node(*id).is_some_and(|n| kept_work(n, &o))) {
|
||||
return Ok(json!({ "ids": roots.iter().map(|i| i.0).collect::<Vec<_>>(), "rasterized": 0, "vector": 0, "options": options }));
|
||||
}
|
||||
let (ids, vector, rasterized) = s.edit("Flatten Transparency", |d, sel| apply(d, sel, plan, &o))?;
|
||||
let (ids, vector, rasterized) = s.edit("Flatten Transparency", |d, sel| {
|
||||
let threaded = super::threads::frames(d);
|
||||
let out = apply(d, sel, plan, &o)?;
|
||||
// Frames of threaded text that were outlined or flattened leave their threads with the
|
||||
// text they show.
|
||||
super::threads::leave_threads(d, &threaded);
|
||||
Ok(out)
|
||||
})?;
|
||||
let mut out = json!({ "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>(), "rasterized": rasterized, "vector": vector, "options": options });
|
||||
if let Some(w) = warning {
|
||||
out["warning"] = json!(w);
|
||||
|
||||
@@ -1066,6 +1066,55 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Object › Flatten Transparency outlines frames of threaded text with Convert All Text to
|
||||
/// Outlines, and turns transparent frames into flat regions or an image: each such frame leaves
|
||||
/// its thread with the text it shows, and the other frames keep the text they hold. Undo threads
|
||||
/// the frames again. When the last frame is flattened, the frame before it takes the text it has
|
||||
/// no room for.
|
||||
#[test]
|
||||
fn flattened_frames_leave_their_thread_with_their_text() {
|
||||
let cases = [(json!({"textToOutlines": true}), false), (json!({}), true), (json!({"balance": 0}), true)];
|
||||
for (options, transparent) in cases {
|
||||
for out in [&[0][..], &[1], &[2], &[0, 2]] {
|
||||
let case = format!("{options} {out:?}");
|
||||
let (mut s, frames) = three_frames(&TEXT.repeat(4), 200.0);
|
||||
let ids: Vec<u64> = out.iter().map(|k| frames[*k]).collect();
|
||||
if transparent {
|
||||
s.execute("transparency.set", &json!({"ids": ids, "opacity": 50})).unwrap();
|
||||
}
|
||||
let shown = frames.map(|id| text(&s, id));
|
||||
assert_eq!(shown.concat(), TEXT.repeat(4));
|
||||
let mut p = options.clone();
|
||||
p["ids"] = json!(ids);
|
||||
s.execute("object.flattenTransparency", &p).unwrap();
|
||||
let rest: Vec<u64> = frames.iter().copied().filter(|id| !ids.contains(id)).collect();
|
||||
for id in &ids {
|
||||
assert!(!is_frame(&s, *id), "{case}: frame {id} is flattened");
|
||||
}
|
||||
for (id, before) in frames.iter().zip(&shown).filter(|(id, _)| rest.contains(id)) {
|
||||
assert_eq!(text(&s, *id), *before, "{case}: frame {id} keeps its text");
|
||||
}
|
||||
assert_eq!(threads(&s), if rest.len() >= 2 { vec![rest] } else { vec![] }, "{case}");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(threads(&s), [frames], "{case}: undo");
|
||||
assert_eq!(frames.map(|id| text(&s, id)), shown, "{case}: undo");
|
||||
}
|
||||
}
|
||||
let (mut s, [a, b, c]) = three_frames(&TEXT.repeat(12), 100.0);
|
||||
let shown = [a, b, c].map(|id| text(&s, id));
|
||||
let laid = {
|
||||
let Some(t) = text_of(&s.doc().unwrap().doc, NodeId(c)) else { panic!("not area type") };
|
||||
vectorcraft_text::layout(vectorcraft_text::FontDb::global(), t)
|
||||
};
|
||||
assert!(laid.overflow, "the last frame has more text than it shows");
|
||||
let end = laid.lines.last().map_or(0, |l| l.end);
|
||||
let hidden = shown[2][end..].trim_start_matches(' ');
|
||||
s.execute("object.flattenTransparency", &json!({"ids": [c], "textToOutlines": true})).unwrap();
|
||||
assert_eq!(text(&s, a), shown[0]);
|
||||
assert_eq!(text(&s, b), format!("{}{hidden}", shown[1]), "the frame before it keeps its text and takes the rest");
|
||||
assert_eq!(threads(&s), [[a, b]]);
|
||||
}
|
||||
|
||||
/// Expand Appearance keeps type whose only effect is a shadow or Crop Marks as type under a new
|
||||
/// id, in a group under the frame's id: the type takes the frame's place in its thread, and
|
||||
/// each frame keeps its text, the last frame the text it has no room for too. Type in a group
|
||||
|
||||
Reference in New Issue
Block a user