Merge pull request #3873 from lehni/chore/colour-glyph-tests

Type: tests pin colour glyphs in variable fonts, on paths and on the canvas
This commit is contained in:
BFlat
2026-10-24 04:11:47 +07:00
committed by GitHub
3 changed files with 82 additions and 3 deletions
+17
View File
@@ -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##"<svg xmlns="http://www.w3.org/2000/svg"><g id="glyph{g}"><path fill="#f00" d="M0 0h250v-700h-250z"/><path fill="currentColor" d="M250 0h250v-700h-250z"/></g></svg>"##
);
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");
}
+30 -3
View File
@@ -658,10 +658,37 @@ pub fn colr_font(v1: bool) -> Option<Vec<u8>> {
/// 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<Vec<u8>> {
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<Vec<u8>> {
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<Vec<u8>> {
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<u8>, colr: &write_fonts::tables::colr::Colr) -> Option<Vec<u8>> {
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();
+35
View File
@@ -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:?}");
}