Type: a COLR glyph whose radial gradient draws no circle draws nothing

A radial gradient whose circles both have the same negative radius (as a
variable font's instance can give) draws no circle. A glyph painted with
only that gradient was read as no colour glyph, so layout drew its
outline instead. It is now colour art that draws nothing.
This commit is contained in:
dotCooCoo
2026-11-14 05:45:32 -08:00
parent 443ca3cb4a
commit 648aedbe37
3 changed files with 48 additions and 2 deletions
+10 -2
View File
@@ -148,6 +148,9 @@ struct Tree<'a> {
painted: Option<Rect>,
/// It paints with the foreground colour (palette index 0xFFFF).
foreground_used: bool,
/// A fill draws nothing (a radial gradient none of whose circles has a positive radius), so a
/// glyph with nothing else is colour art that draws nothing, not its outline.
drew_nothing: bool,
}
impl<'a> Tree<'a> {
@@ -166,6 +169,7 @@ impl<'a> Tree<'a> {
too_large: false,
painted: None,
foreground_used: false,
drew_nothing: false,
}
}
@@ -254,7 +258,7 @@ impl<'a> Tree<'a> {
self.close();
}
let root = self.groups.pop()?;
if root.children.is_empty() {
if root.children.is_empty() && !self.drew_nothing {
return None;
}
Some(Arc::new(Node::group(NO_ID, root.children)))
@@ -314,7 +318,11 @@ impl<'a> Tree<'a> {
self.unsupported.get_or_insert("a repeating or reflecting gradient with a stop missing from the palette");
return None;
}
let (c0, r0, c1, r1, mut stops) = non_negative_radii(point(*c0), f64::from(*r0), point(*c1), f64::from(*r1), line, *extend)?;
let Some((c0, r0, c1, r1, mut stops)) = non_negative_radii(point(*c0), f64::from(*r0), point(*c1), f64::from(*r1), line, *extend)
else {
self.drew_nothing = true;
return None;
};
let concentric = (c0 - c1).hypot() < 1e-6;
if (r0 > 1e-6 && !concentric) || r1 <= r0 || (c0 - c1).hypot() >= r1 {
self.unsupported.get_or_insert("a radial gradient from a circle");
+6
View File
@@ -1939,6 +1939,12 @@ pub fn colr_variable_font() -> Option<Vec<u8>> {
with_colr(&variable_font()?, family_names(COLR_VARIABLE_FAMILY, &axis_names)?, &colr)
}
/// [`variable_font`] (one weight axis, 400 to 700) as `family` with `colr` and [`colr_font`]'s
/// palette. `None` when it can't be built.
pub fn colr_variable_font_with(family: &str, colr: &write_fonts::tables::colr::Colr) -> Option<Vec<u8>> {
with_colr(&variable_font()?, family_names(family, &[])?, 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(' ', "");
+32
View File
@@ -467,6 +467,38 @@ fn an_image_of_a_radial_gradient_with_a_stop_past_its_circle_of_radius_0_draws_f
}
}
/// A radial gradient whose circles both have the same negative radius at a variable font's
/// instance draws no circle, so a glyph painted with only that gradient is colour art that draws
/// nothing, not its outline. The radii are 10 at the default weight and -10 at 700.
#[test]
fn a_radial_gradient_with_no_circle_drawn_leaves_the_glyph_empty() {
use write_fonts::tables::colr::{BaseGlyphList, BaseGlyphPaint, Colr, Extend, VarColorLine, VarColorStop};
use write_fonts::tables::variations::{ItemVariationData, ItemVariationStore, RegionAxisCoordinates, VariationRegion, VariationRegionList};
use write_fonts::types::{F2Dot14, FWord, GlyphId16, UfWord};
let bytes = colr_font(false).unwrap();
let read = skrifa::FontRef::new(&bytes).unwrap();
let gid = |ch| GlyphId16::new(u16::try_from(read.charmap().map(ch).unwrap().to_u32()).unwrap());
let (one, fixed) = (F2Dot14::from_f32(1.0), 0xFFFF_FFFF);
let line =
VarColorLine::new(Extend::Pad, 2, vec![VarColorStop::new(F2Dot14::from_f32(0.0), 0, one, fixed), VarColorStop::new(one, 1, one, fixed)]);
let w = FWord::new;
let radial = ColrPaint::var_radial_gradient(line, w(290), w(330), UfWord::new(10), w(290), w(330), UfWord::new(10), 0);
let mut colr = Colr::new(0, None, None, 0);
colr.base_glyph_list = Some(BaseGlyphList::new(1, vec![BaseGlyphPaint::new(gid(COLOUR_CHAR), ColrPaint::glyph(radial, gid('O')))])).into();
// One 16-bit delta for each of x0, y0, r0, x1, y1 and r1, at full weight.
let deltas: Vec<u8> = [0i16, 0, -20, 0, 0, -20].iter().flat_map(|d| d.to_be_bytes()).collect();
let region = VariationRegion::new(vec![RegionAxisCoordinates::new(F2Dot14::from_f32(0.0), one, one)]);
let data = ItemVariationData::new(6, 1, vec![0], deltas);
colr.item_variation_store = Some(ItemVariationStore::new(VariationRegionList::new(1, vec![region]), vec![Some(data)])).into();
let font = crate::test_fonts::colr_variable_font_with("Colrtest No Circle", &colr).unwrap();
let font = skrifa::FontRef::new(&font).unwrap();
let bold = font.axes().location([("wght", 700.0)]);
let Some(ColourContent::Art(root)) = colour_glyph(&font, (&bold).into(), 1000.0, u32::from(gid(COLOUR_CHAR).to_u16()), FOREGROUND).0 else {
panic!("colour art")
};
assert!(children(&root).is_empty(), "{:?}", children(&root).iter().map(|c| c.kind_label()).collect::<Vec<_>>());
}
/// A COLRv1 color line through `stops` (offset, palette index) that extends by `extend`.
fn color_line(extend: write_fonts::tables::colr::Extend, stops: &[(f32, u16)]) -> write_fonts::tables::colr::ColorLine {
use write_fonts::tables::colr::{ColorLine, ColorStop};