Merge remote-tracking branch 'origin/main' into feat/width-smooth-profile

# Conflicts:
#	docs/target-app-parity.md
This commit is contained in:
adambelis
2026-11-11 22:13:44 +01:00
592 changed files with 54042 additions and 2799 deletions
+72
View File
@@ -138,6 +138,78 @@ fn composite_font_parts_are_not_highlighted_as_substituted_glyphs() {
assert!(g.substituted_glyphs.elements().is_empty() && g.substituted_fonts.elements().is_empty());
}
/// Type in one of a document's composite fonts draws in the document's definition when nothing has
/// registered it: in a render, an ink render, a thumbnail and a placed document's art, as type set
/// in the part's font draws.
/// The font database is left without it afterwards.
#[test]
fn a_documents_own_composite_font_draws_without_being_registered() {
use vectorcraft_doc::{CompositeFont, CompositePart, CompositeParts};
let _adding = adding_fonts();
const NAME: &str = "CF Unregistered";
let db = vectorcraft_text::FontDb::global();
let typed = |family: &str| {
let mut d = Document::new(300.0, 150.0);
let style = CharStyle { size: 60.0, font_family: family.into(), ..Default::default() };
let n = Node::new(d.alloc_id(), NodeKind::Text(Box::new(TextObject::point(Point::new(10.0, 100.0), "Hamburg", style))));
let l = d.layers[0].id;
let id = d.insert(Some(l), 0, n).unwrap();
(d, id)
};
let (mut composite, id) = typed(NAME);
let mut parts = CompositeParts::all(CompositePart::new("Source Sans 3", "Regular"));
parts.roman = CompositePart::new("JetBrains Mono", "Regular");
composite.composite_fonts.push(CompositeFont { name: NAME.into(), parts });
let (plain, plain_id) = typed("JetBrains Mono");
assert!(db.composite_font(NAME).is_none());
let opts = RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() };
let view = Affine::IDENTITY;
assert_eq!(
Renderer::new().render(&composite, 300, 150, view, &opts).pixels,
Renderer::new().render(&plain, 300, 150, view, &opts).pixels,
"rendered"
);
let region = Rect::new(0.0, 0.0, 300.0, 150.0);
assert_eq!(
Renderer::new().render_region_inks(&composite, region, 1.0, &opts),
Renderer::new().render_region_inks(&plain, region, 1.0, &opts),
"rendered as ink amounts"
);
let thumbnail = |d: &Document, id| Renderer::new().render_thumbnail(d, id, 64).map(|r| r.pixels);
assert_eq!(thumbnail(&composite, id), thumbnail(&plain, plain_id), "thumbnail");
let placed = |d: &Document| crate::placed_document::render_inside(&mut Renderer::new(), d, &d.layers[0], 300, 150, view).map(|r| r.pixels);
assert_eq!(placed(&composite), placed(&plain), "a placed document's art");
// A page that places the document, drawn exactly (no bitmap of the placed art).
let page = |d: &Document, id: NodeId, key: &str| {
use vectorcraft_doc::{ImageBlob, LinkInfo, PlacedDocument};
let blob = ImageBlob::new("application/json", key.as_bytes().to_vec());
let p = PlacedDocument {
link: LinkInfo::new("/art/type.vectorcraft"),
key: key.into(),
bounding: false,
page_box: Default::default(),
width: 300.0,
height: 150.0,
xf: Affine::IDENTITY,
placement: Default::default(),
};
let art = d.node(id).unwrap().clone();
vectorcraft_doc::placed_document::prime(&p, blob.bytes.clone(), (d.clone(), art, Rect::new(0.0, 0.0, 300.0, 150.0)));
let mut page = Document::new(300.0, 150.0);
page.images.insert(key.into(), blob);
let n = Node::new(page.alloc_id(), NodeKind::PlacedDocument(Box::new(p)));
let l = page.layers[0].id;
page.insert(Some(l), 0, n).unwrap();
Renderer::new().render(&page, 300, 150, view, &opts).pixels
};
assert_eq!(
page(&composite, id, "fontchange-composite-placed"),
page(&plain, plain_id, "fontchange-plain-placed"),
"a placed document's art drawn exactly"
);
assert!(db.composite_font(NAME).is_none(), "given back after each render");
}
/// Type at 80% opacity, in Multiply and with a drop shadow redraws in a font that becomes
/// available: the glyph geometry kept for it is laid out again.
#[test]
+38
View File
@@ -1824,6 +1824,44 @@ fn pattern_and_symbol_art_with_plugin_effects_follow_the_plugins_installed() {
plugin_change_draws_as_new(with_art(d, vec![group]), ID, Some(triangle(82.0, 32.0, 10.0)), Some(triangle(82.0, 32.0, 30.0)));
}
/// A document draws plug-in effects ([`draws_plugin_effects`]) when an object in its layers, in a
/// symbol's or a pattern's art, or in a brush's art has a visible plug-in effect, and not when its
/// objects have no effect, another effect or a hidden plug-in effect.
#[test]
fn a_document_draws_plugin_effects_where_any_of_its_art_has_one() {
use vectorcraft_brush::{ArtBrush, Brush, BrushKind};
use vectorcraft_geom::shapes::rectangle;
let plugin = || effect("plugin.test.render.draws", serde_json::json!({}));
let square = |d: &mut Document, effects: Vec<vectorcraft_doc::Effect>| {
filled(d, rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)), Paint::solid(Color::BLACK), effects)
};
let mut cases: Vec<(&str, Document, bool)> = vec![];
for (name, effects, draws) in [
("no effect", vec![], false),
("another effect", vec![drop_shadow()], false),
("hidden plug-in effect", vec![vectorcraft_doc::Effect { visible: false, ..plugin() }], false),
("plug-in effect", vec![plugin()], true),
] {
let mut d = Document::new(200.0, 100.0);
let n = square(&mut d, effects);
cases.push((name, with_art(d, vec![n]), draws));
}
let mut d = Document::new(200.0, 100.0);
let art = square(&mut d, vec![plugin()]);
d.symbols.push(vectorcraft_doc::Symbol { dynamic: false, name: "Shaped".into(), art: Arc::new(art) });
cases.push(("symbol art", d, true));
let mut d = Document::new(200.0, 100.0);
let art = square(&mut d, vec![plugin()]);
d.patterns.push(vectorcraft_doc::pattern::PatternDef::new("Shaped", vec![Arc::new(art)]));
cases.push(("pattern art", d, true));
let mut d = Document::new(200.0, 100.0);
let art = square(&mut d, vec![plugin()]);
vectorcraft_brush::store(&mut d, &[Brush { name: "Shaped".into(), kind: BrushKind::Art(ArtBrush { art, ..Default::default() }) }]);
cases.push(("brush art", d, true));
let wrong: Vec<&str> = cases.iter().filter(|(_, d, draws)| draws_plugin_effects(d) != *draws).map(|(name, ..)| *name).collect();
assert!(wrong.is_empty(), "{wrong:?}");
}
fn effect(id: &str, params: serde_json::Value) -> vectorcraft_doc::Effect {
vectorcraft_doc::Effect { id: id.into(), params, visible: true }
}