diff --git a/crates/engine/src/tests_colourfonts.rs b/crates/engine/src/tests_colourfonts.rs
index ce49ebe45..205d5c658 100644
--- a/crates/engine/src/tests_colourfonts.rs
+++ b/crates/engine/src/tests_colourfonts.rs
@@ -151,3 +151,20 @@ fn expand_appearance_keeps_the_colour_glyphs_of_type_with_effects() {
assert!(gradients > 0, "the colour glyph's gradient layers");
vectorcraft_testkit::invariants::check_document(&s.doc().unwrap().doc).unwrap();
}
+
+/// The canvas draws an SVG glyph as its art: its own colours, and the type's fill where it uses
+/// `currentColor`.
+#[test]
+fn the_canvas_draws_svg_glyphs_as_their_art() {
+ let g = source_sans_gid(SVG_CHAR).unwrap();
+ // The left half of a 500 by 700 box red, the right half in the text's colour.
+ let doc = format!(
+ r##""##
+ );
+ let s = with_svg_type("Svgtest Canvas", &doc);
+ let opts = vectorcraft_render::RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() };
+ let img = vectorcraft_render::Renderer::new().render(&s.doc().unwrap().doc, 100, 100, vectorcraft_geom::Affine::IDENTITY, &opts);
+ // At 32 pt from x 10 on the baseline at y 50: 16 pt wide, 22.4 pt tall.
+ assert_eq!(img.pixel(14, 40), [255, 0, 0, 255], "its own red");
+ assert_eq!(img.pixel(22, 40), [0, 0, 0, 255], "the type's black fill");
+}
diff --git a/crates/text/src/test_fonts.rs b/crates/text/src/test_fonts.rs
index 30d2acb26..e57fb92d9 100644
--- a/crates/text/src/test_fonts.rs
+++ b/crates/text/src/test_fonts.rs
@@ -658,10 +658,37 @@ pub fn colr_font(v1: bool) -> Option> {
/// The bundled Source Sans 3 Regular as `family` with `colr` and [`colr_font`]'s palette.
pub fn colr_font_with(family: &str, colr: &write_fonts::tables::colr::Colr) -> Option> {
- use write_fonts::tables::cpal::{ColorRecord, Cpal};
- let font = skrifa::FontRef::new(SOURCE_SANS).ok()?;
+ with_colr(SOURCE_SANS, family_names(family, &[])?, colr)
+}
+
+/// The family of [`colr_variable_font`].
+pub const COLR_VARIABLE_FAMILY: &str = "Colrtest Variable";
+
+/// [`variable_font`] as family [`COLR_VARIABLE_FAMILY`], with a `COLR` (v0) table in which
+/// [`COLOUR_CHAR`] is the outline of `l` (one of [`VARIABLE_CHARS`]) in red, so its layer widens
+/// with the weight. `None` when it can't be built.
+pub fn colr_variable_font() -> Option> {
+ use write_fonts::tables::colr::{BaseGlyph, Colr, Layer};
+ use write_fonts::types::GlyphId16;
+ let [base, l] = source_sans_gids([COLOUR_CHAR, 'l'])?;
+ let colr = Colr::new(1, Some(vec![BaseGlyph::new(GlyphId16::new(base), 0, 1)]), Some(vec![Layer::new(GlyphId16::new(l), 0)]), 1);
+ let axis_names = [(256, "Weight"), (257, "Normal"), (258, "SemiBold"), (259, "Bold")];
+ with_colr(&variable_font()?, family_names(COLR_VARIABLE_FAMILY, &axis_names)?, &colr)
+}
+
+/// A `name` table for Regular of `family`, with `more` records.
+fn family_names(family: &str, more: &[(u16, &str)]) -> Option> {
let ps = family.replace(' ', "");
- let name = name_table(&[(1, family), (2, "Regular"), (4, &format!("{family} Regular")), (6, &format!("{ps}-Regular"))])?;
+ let (full, postscript) = (format!("{family} Regular"), format!("{ps}-Regular"));
+ let mut records = vec![(1, family), (2, "Regular"), (4, full.as_str()), (6, postscript.as_str())];
+ records.extend_from_slice(more);
+ name_table(&records)
+}
+
+/// Font `base` with the `name` table `name`, `colr` and [`colr_font`]'s palette.
+fn with_colr(base: &[u8], name: Vec, colr: &write_fonts::tables::colr::Colr) -> Option> {
+ use write_fonts::tables::cpal::{ColorRecord, Cpal};
+ let font = skrifa::FontRef::new(base).ok()?;
let records = [COLR_RED, COLR_BLUE, COLR_GREEN].map(|[r, g, b, a]| ColorRecord::new(b, g, r, a)).to_vec();
let cpal = Cpal::new(3, 1, 3, Some(records), vec![0]);
let mut builder = FontBuilder::new();
diff --git a/crates/text/src/tests_colr.rs b/crates/text/src/tests_colr.rs
index 116eeff3c..9f51e0d9e 100644
--- a/crates/text/src/tests_colr.rs
+++ b/crates/text/src/tests_colr.rs
@@ -376,3 +376,38 @@ fn a_glyph_without_the_foreground_colour_is_read_once_for_every_fill() {
assert!(tree(true, Color::rgb8(0, 0, 200)), "v1's glyph uses only its palette");
assert!(!tree(false, Color::rgb8(0, 0, 200)), "v0's foreground layer follows the fill");
}
+
+/// A variable font's COLR glyph draws its layers at the face's instance: at Bold, the layer's `l`
+/// is as much wider as the font widens it.
+#[test]
+fn a_variable_fonts_colr_layers_follow_its_instance() {
+ use crate::test_fonts::{COLR_VARIABLE_FAMILY, VARIABLE_WIDEN, colr_variable_font};
+ let db = FontDb::with_font_dirs(vec![]);
+ assert_eq!(db.add_font(colr_variable_font().expect("the test font builds")), 3, "Regular, SemiBold and Bold");
+ let width = |style: &str| {
+ let st = CharStyle { font_family: COLR_VARIABLE_FAMILY.into(), font_style: style.into(), size: 100.0, ..CharStyle::default() };
+ let l = layout(&db, &TextObject::point(KPoint::ZERO, &COLOUR_CHAR.to_string(), st));
+ l.glyphs[0].placed_colour(kurbo::Affine::IDENTITY).unwrap().geometric_bounds().unwrap().width()
+ };
+ let (regular, bold) = (width("Regular"), width("Bold"));
+ assert!((bold / regular - 1.0 - f64::from(VARIABLE_WIDEN)).abs() < 0.02, "{regular} → {bold}");
+}
+
+/// On a path, a colour glyph's layers turn with it: along a path going down, the tall `l` layer
+/// lies on its side, across the path.
+#[test]
+fn a_colour_glyph_on_a_path_turns_with_it() {
+ use crate::test_fonts::{COLR_VARIABLE_FAMILY, colr_variable_font};
+ use vectorcraft_doc::text::TextKind;
+ let db = FontDb::with_font_dirs(vec![]);
+ db.add_font(colr_variable_font().expect("the test font builds"));
+ let st = CharStyle { font_family: COLR_VARIABLE_FAMILY.into(), size: 100.0, ..CharStyle::default() };
+ let mut t = TextObject::point(KPoint::ZERO, &COLOUR_CHAR.to_string(), st);
+ let upright = layout(&db, &t).glyphs[0].placed_colour(kurbo::Affine::IDENTITY).unwrap().geometric_bounds().unwrap();
+ let down = kurbo::Shape::to_path(&kurbo::Line::new((0.0, 0.0), (0.0, 400.0)), 0.1);
+ t.kind = TextKind::OnPath { path: vectorcraft_geom::PathData::from_bezpath(&down), start: 0.0, end: None };
+ let b = layout(&db, &t).glyphs[0].placed_colour(kurbo::Affine::IDENTITY).unwrap().geometric_bounds().unwrap();
+ assert!(upright.height() > 2.0 * upright.width(), "upright, the `l` is tall: {upright:?}");
+ assert!((b.width() - upright.height()).abs() < 1.0 && (b.height() - upright.width()).abs() < 1.0, "on its side: {b:?}");
+ assert!(b.y0 >= -1.0 && b.y1 <= 400.0, "along the path: {b:?}");
+}