Type: emoji go with type that effects reshape and Expand Appearance

Squashed from #3879 onto main.
This commit is contained in:
bflatastic
2026-10-24 04:49:06 +07:00
parent 94e1fa3ecc
commit c2ad9f8bbb
91 changed files with 519 additions and 168 deletions
+9 -2
View File
@@ -421,8 +421,15 @@ fn import_image(s: &mut Session, p: &Value) -> Result<Value> {
let img = fileio::raster_image(&data_param(p, C)?).map_err(|e| bad(C, e.to_string()))?;
let (sx, sy) = pt_per_px(img.ppi);
let (w, h) = (img.width, img.height);
let image =
ImageObject { key: img.key.clone(), width: w, height: h, xf: Affine::scale_non_uniform(sx, sy), link: None, placement: Default::default() };
let image = ImageObject {
key: img.key.clone(),
width: w,
height: h,
xf: Affine::scale_non_uniform(sx, sy),
link: None,
placement: Default::default(),
pixels: None,
};
let mut clip = Clipboard { nodes: vec![Node::new(NodeId(1), NodeKind::Image(Box::new(image)))], ..Default::default() };
clip.images.insert(img.key, img.blob);
let count = s.load_clipboard(clip, p)?;
+6 -4
View File
@@ -558,10 +558,12 @@ pub(crate) fn expand_node(d: &mut Document, id: NodeId, brushes: &[vectorcraft_b
let mut listed = vec![];
let m = expanded_node(d, n, brushes, &mut listed);
out.extend(listed.into_iter().map(|(id, _)| id));
if let Some(m) = m
&& let Some(slot) = d.node_mut(id)
{
*slot = m;
if let Some(mut m) = m {
// Art made from type takes its emoji's pixels into the document.
d.adopt_pixels(&mut m);
if let Some(slot) = d.node_mut(id) {
*slot = m;
}
}
}
+9 -1
View File
@@ -1274,7 +1274,15 @@ impl Builder {
let sy = img.height as f64 / f64::from(r.height.max(1));
let xf = to_doc * affine(img.transform) * Affine::scale_non_uniform(sx, sy);
self.doc.images.entry(r.key.clone()).or_insert(r.blob);
Some(NodeKind::Image(Box::new(ImageObject { key: r.key, width: r.width, height: r.height, xf, link: None, placement: Default::default() })))
Some(NodeKind::Image(Box::new(ImageObject {
key: r.key,
width: r.width,
height: r.height,
xf,
link: None,
placement: Default::default(),
pixels: None,
})))
}
}
+2 -1
View File
@@ -215,7 +215,8 @@ fn flatten_images(d: &mut Document, images: &[(usize, NodeId)]) -> Result<()> {
let png = flat.png;
let key = super::super::menucmds::unique_key(d, "flattened");
d.images.insert(key.clone(), ImageBlob::new("image/png", png));
let image = ImageObject { key, width: flat.width, height: flat.height, xf: flat.xf, link: None, placement: Default::default() };
let image =
ImageObject { key, width: flat.width, height: flat.height, xf: flat.xf, link: None, placement: Default::default(), pixels: None };
let image = Node::new(d.alloc_id(), NodeKind::Image(Box::new(image)));
let mut group = super::super::rasterfx::clip_group(d, flat.clip, FillRule::NonZero, image);
group.name = src.node(id).and_then(|n| n.name.clone());
+4 -2
View File
@@ -658,8 +658,10 @@ pub(super) fn raster_doc(name: &str, bytes: &[u8]) -> Result<Document> {
let layer = d.layers[0].id;
let id = d.alloc_id();
let xf = Affine::scale_non_uniform(sx, sy);
let mut n =
Node::new(id, NodeKind::Image(Box::new(ImageObject { key: key.clone(), width, height, xf, link: None, placement: Default::default() })));
let mut n = Node::new(
id,
NodeKind::Image(Box::new(ImageObject { key: key.clone(), width, height, xf, link: None, placement: Default::default(), pixels: None })),
);
n.name = Some(name.to_string());
d.images.insert(key, blob);
d.insert(Some(layer), 0, n).map_err(err)?;
@@ -248,6 +248,7 @@ fn a_raster_export_says_nothing_of_images_another_thread_could_not_decode() {
xf: vectorcraft_geom::Affine::scale(20.0),
link: None,
placement: Default::default(),
pixels: None,
};
let (id, layer) = (broken.alloc_id(), broken.layers[0].id);
broken.insert(Some(layer), 0, Node::new(id, NodeKind::Image(Box::new(im)))).unwrap();
+9 -1
View File
@@ -1136,7 +1136,15 @@ fn raster_node(d: &mut Document, r: Raster) -> Node {
d.images.insert(key.clone(), r.blob);
let image = Node::new(
d.alloc_id(),
NodeKind::Image(Box::new(ImageObject { key, width: r.width, height: r.height, xf: r.xf, link: None, placement: Default::default() })),
NodeKind::Image(Box::new(ImageObject {
key,
width: r.width,
height: r.height,
xf: r.xf,
link: None,
placement: Default::default(),
pixels: None,
})),
);
let Some(path) = r.clip else { return image };
super::rasterfx::clip_group(d, path, FillRule::NonZero, image)
+2
View File
@@ -1334,6 +1334,8 @@ fn expand_envelope(d: &mut Document, id: NodeId) -> Result<()> {
let n = d.node(id).cloned().ok_or(EngineError::NoNode(id))?;
let mut g = vectorcraft_render::effects::expanded_live_group(Some(d), &n);
fix_ids(d, &mut g);
// Outlined type's emoji take their pixels into the document.
d.adopt_pixels(&mut g);
// Outlined type repeats its object's id on its pieces.
vectorcraft_render::effects::fresh_ids(d, &mut g, &mut Default::default());
*d.node_mut(id).ok_or(EngineError::NoNode(id))? = g;
+3 -1
View File
@@ -469,7 +469,7 @@ impl Rasterized {
let key = unique_key(d, "raster");
d.images.insert(key.clone(), vectorcraft_doc::ImageBlob::new("image/png", self.png));
let id = d.alloc_id();
let image = ImageObject { key, width: self.width, height: self.height, xf: self.xf, link: None, placement: Default::default() };
let image = ImageObject { key, width: self.width, height: self.height, xf: self.xf, link: None, placement: Default::default(), pixels: None };
let node = Node::new(id, NodeKind::Image(Box::new(image)));
match self.clip {
Some((path, rule)) => super::rasterfx::clip_group(d, path, rule, node),
@@ -721,6 +721,7 @@ fn crop_image(s: &mut Session, p: &Value) -> Result<Value> {
xf: Affine::translate(region.origin().to_vec2()) * Affine::scale(1.0 / scale),
link: None,
placement: Default::default(),
pixels: None,
}));
}
Ok(())
@@ -788,6 +789,7 @@ fn trim_image(s: &mut Session, id: NodeId, im: ImageObject) -> Result<Value> {
xf: im.xf * Affine::translate((f64::from(x0), f64::from(y0))),
link: None,
placement: Default::default(),
pixels: None,
}));
}
Ok(())
+1
View File
@@ -385,6 +385,7 @@ fn build(d: &mut Document, l: Loaded, link: Option<LinkInfo>) -> Node {
xf: Affine::scale_non_uniform(sx, sy),
link,
placement: Default::default(),
pixels: None,
};
Node::new(d.alloc_id(), NodeKind::Image(Box::new(im)))
}
+1 -1
View File
@@ -96,7 +96,7 @@ fn bitmap_document(cmd: &str, doc: &Document, set: &PrintSettings, rich_black: b
let key = unique_key(&out, "print-bitmap");
out.images.insert(key.clone(), ImageBlob::new("image/png", img.to_png().map_err(EngineError::Other)?));
let xf = Affine::translate(region.origin().to_vec2()) * Affine::scale(1.0 / scale);
let image = ImageObject { key, width: img.width, height: img.height, xf, link: None, placement: Default::default() };
let image = ImageObject { key, width: img.width, height: img.height, xf, link: None, placement: Default::default(), pixels: None };
images.push(Arc::new(Node::new(out.alloc_id(), NodeKind::Image(Box::new(image)))));
}
let mut layer = Node::layer(out.alloc_id(), "Bitmap", LayerColor::Preset(0));
+9 -1
View File
@@ -225,7 +225,15 @@ pub(crate) fn effect_image(out: &mut Document, whole: &Node, knockout: Option<&N
let xf = Affine::translate(region.origin().to_vec2()) * Affine::scale(1.0 / scale);
let mut image = Node::new(
id,
NodeKind::Image(Box::new(ImageObject { key, width: img.width, height: img.height, xf, link: None, placement: Default::default() })),
NodeKind::Image(Box::new(ImageObject {
key,
width: img.width,
height: img.height,
xf,
link: None,
placement: Default::default(),
pixels: None,
})),
);
image.name = Some("Raster effect".into());
Some(image)
+1
View File
@@ -92,6 +92,7 @@ mod tests {
xf: Affine::scale(72.0 / ppi),
link: None,
placement: Default::default(),
pixels: None,
})),
)
}
+5 -13
View File
@@ -4,7 +4,7 @@
use std::sync::Arc;
use serde_json::{Value, json};
use vectorcraft_doc::{AreaFit, Document, ImageObject, Justify, Node, NodeId, NodeKind, RubyAlign, RubyPosition, RubySpan, TextObject};
use vectorcraft_doc::{AreaFit, Document, Justify, Node, NodeId, NodeKind, RubyAlign, RubyPosition, RubySpan, TextObject};
use vectorcraft_geom::{Affine, PathData, Vec2};
use super::edit::selected_roots;
@@ -559,18 +559,10 @@ fn create_outlines(s: &mut Session, _: &Value) -> Result<Value> {
children.push(Arc::new(Node::new(id, NodeKind::SymbolInstance { symbol: art.symbol.clone(), xf })));
continue;
}
// Colour bitmap glyphs (emoji) become embedded images, in place; glyphs that show
// the same image share its pixels, under a key of their own if another image has
// taken theirs.
if let Some((image, to_text)) = g.placed_image() {
let mut key = format!("glyph-{}", image.blob.content_key());
if d.images.get(&key).is_some_and(|b| b.bytes != image.blob.bytes) {
key = super::menucmds::unique_key(d, "glyph");
}
d.images.entry(key.clone()).or_insert_with(|| image.blob.clone());
let im =
ImageObject { key, width: image.width, height: image.height, xf: t.xf * to_text, link: None, placement: Default::default() };
children.push(Arc::new(Node::new(d.alloc_id(), NodeKind::Image(Box::new(im)))));
// Colour bitmap glyphs (emoji) become embedded images, in place, their pixels taken
// into the document: glyphs that show the same image share them.
if let Some(image) = g.placed_image_node(t.xf) {
children.push(Arc::new(d.reid(&image)));
continue;
}
// COLR glyphs become groups of their layers, in place, with ids of the document's.