Character: type with color glyphs is drawn as Sharp

Color glyphs (bitmap glyphs in sbix or CBDT, and COLR layers) paint
their own pixels and layers. The renderer draws them unchanged in the
black drawing that measures coverage, so their own opacity counted as
the characters' coverage: Strong made a half-transparent pixel inside a
glyph opaque, and Crisp could erase faint detail. draw_type_edges now
returns false for type with color glyphs, as it does for type with
inline graphics, and docs/mcp.md lists them.
This commit is contained in:
dotCooCoo
2026-10-23 04:17:01 -07:00
parent 4d6873d1f7
commit 347c99c7b3
3 changed files with 46 additions and 7 deletions
+10 -5
View File
@@ -1958,11 +1958,12 @@ impl Renderer {
/// the type is drawn offscreen again.
///
/// Returns false, having drawn nothing, for type that is drawn as Sharp draws it: type with
/// inline graphics, type whose own fills or strokes include a brush stroke or a blend mode
/// other than Normal, type that overprints while Overprint Preview is on, and type that shows a
/// substitution highlight on screen. Drawn offscreen, those would blend with nothing or take
/// the method on art that is not the characters'. It also returns false for type whose
/// offscreen drawing would be more than [`MAX_RASTER_SIDE`] pixels a side.
/// inline graphics, type with color glyphs (bitmap or COLR), type whose own fills or strokes
/// include a brush stroke or a blend mode other than Normal, type that overprints while
/// Overprint Preview is on, and type that shows a substitution highlight on screen. Drawn
/// offscreen, those would blend with nothing or take the method on art that is not the
/// characters'. It also returns false for type whose offscreen drawing would be more than
/// [`MAX_RASTER_SIDE`] pixels a side.
fn draw_type_edges(&mut self, ctx: &mut RenderContext, f: &Frame, n: &Node, t: &TextObject, g: &TextGeom) -> bool {
if proof::overprints(f.opts) && t.runs.iter().any(|r| r.style.overprint_fill || r.style.overprint_stroke) {
return false;
@@ -1972,6 +1973,10 @@ impl Renderer {
if t.runs.iter().any(|r| r.inline.is_some()) || n.appearance.items.iter().any(|i| i.visible() && (i.blend() != normal || brush(i))) {
return false;
}
// Color glyphs paint their own pixels and layers, whose opacity is not the characters' coverage.
if !g.images.is_empty() || !g.colours.is_empty() {
return false;
}
if f.opts.highlight_substitutions {
let setup = &f.doc.setup;
let shows = |on: bool, path: &BezPath| on && !path.elements().is_empty();
+33
View File
@@ -201,6 +201,39 @@ fn type_with_an_inline_graphic_is_drawn_as_sharp() {
}
}
/// Type with a color glyph (a bitmap in `sbix` or `CBDT`, or COLR v0 or v1 layers) is drawn as
/// Sharp draws it, so the glyph's own opacity is kept.
#[test]
fn type_with_a_color_glyph_is_drawn_as_sharp() {
use vectorcraft_text::test_fonts::{COLOUR_CHAR, colour_bitmap_family, colour_bitmap_font, colr_family, colr_font};
let _adding = crate::tests_fontchange::adding_fonts();
let fonts = [
(colour_bitmap_family(true), colour_bitmap_font(true)),
(colour_bitmap_family(false), colour_bitmap_font(false)),
(colr_family(false), colr_font(false)),
(colr_family(true), colr_font(true)),
];
for (family, font) in fonts {
let Some(font) = font else { panic!("{family}: the test font is built") };
vectorcraft_text::FontDb::global().add_font(font);
let draw = |m: TypeAntiAlias| {
let mut d = Document::new(160.0, 160.0);
let style = CharStyle { font_family: family.into(), size: 100.0, ..Default::default() };
let mut t = TextObject::point(Point::new(20.3, 120.4), &COLOUR_CHAR.to_string(), style);
t.anti_alias = m;
let n = Node::new(d.alloc_id(), NodeKind::Text(Box::new(t)));
let l = d.layers[0].id;
d.insert(Some(l), 0, n).unwrap();
Renderer::new().render(&d, 160, 160, Affine::IDENTITY, &RenderOptions::default()).pixels
};
let sharp = draw(TypeAntiAlias::Sharp);
assert!(sharp.chunks(4).any(|p| p[3] > 0), "{family}: the glyph is drawn");
for m in [TypeAntiAlias::None, TypeAntiAlias::Crisp, TypeAntiAlias::Strong] {
assert!(draw(m) == sharp, "{family}, {m:?}");
}
}
}
/// In a supersampled export, None type in a group drawn offscreen (it isolates art that blends)
/// still has hard edges on the exported pixels, wherever the group's layer starts.
#[test]
+3 -2
View File
@@ -4251,8 +4251,9 @@ method does, on the pixels of the picture being drawn (the canvas at its zoom, a
- `strong` draws a pixel the type covers by a share c at `2c`, at most 1, so the type looks heavier.
The opacity of the type's paints stays as it is inside the characters. Type is drawn as Sharp draws it when it holds
inline graphics, when its own fills or strokes in the Appearance panel include a brush stroke or a blend mode other than
Normal, when it overprints while Overprint Preview is on, and on the canvas while it shows a substitution highlight.
inline graphics or color glyphs (bitmap or COLR), when its own fills or strokes in the Appearance panel include a brush
stroke or a blend mode other than Normal, when it overprints while Overprint Preview is on, and on the canvas while it
shows a substitution highlight.
With Preferences › General › Anti-aliased Artwork off, the canvas draws all its art with hard edges, type included, and
so does an export whose own Anti-aliasing option is None. SVG, PDF and EPS export are unchanged and carry no setting.
Reset Panel sets Sharp. Saved as `antiAlias` only when not `sharp`. One undo step.