From 347c99c7b3c4764aa350b4ec5d1e439578d4ea2e Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 23 Oct 2026 04:17:01 -0700 Subject: [PATCH] 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. --- crates/render/src/lib.rs | 15 ++++++++----- crates/render/src/tests_typeedges.rs | 33 ++++++++++++++++++++++++++++ docs/mcp.md | 5 +++-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 5ee64398e..dea7cf764 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -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(); diff --git a/crates/render/src/tests_typeedges.rs b/crates/render/src/tests_typeedges.rs index 855f20d46..7d5219539 100644 --- a/crates/render/src/tests_typeedges.rs +++ b/crates/render/src/tests_typeedges.rs @@ -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] diff --git a/docs/mcp.md b/docs/mcp.md index 3a7f1b313..3bdebb39d 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -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.