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:?}"); +}