Merge main into feat/character-anti-aliasing-method-r

This commit is contained in:
dotCooCoo
2026-11-08 13:24:43 -08:00
217 changed files with 11105 additions and 1117 deletions
+10 -2
View File
@@ -563,17 +563,25 @@ mod tests {
fn the_ink_table_matches_the_profile() {
let cms = cms::active();
let lut = InkLut::build(&cms);
let mut worst = 0.0f32;
let (mut worst, mut worst_dark) = (0.0f32, 0.0f32);
// Between the grid points (where interpolation errs most) and on them.
for i in 0..=20 {
for j in 0..=20 {
let inks = [i as f32 / 20.0, j as f32 / 20.0, ((i * 7 + j * 3) % 21) as f32 / 20.0, ((i + j * 5) % 21) as f32 / 20.0];
let (a, b) = (lut.apply(inks), cms.cmyk_to_srgb(inks, false));
worst = worst.max((0..3).map(|c| (a[c] - b[c]).abs()).fold(0.0, f32::max));
let err = (0..3).map(|c| (a[c] - b[c]).abs()).fold(0.0, f32::max);
// Builds darker than the press's black point clip at 0 in one or more XYZ channels,
// so the grid interpolates across that clip in the darkest levels.
if b.iter().all(|v| *v < 0.05) {
worst_dark = worst_dark.max(err);
} else {
worst = worst.max(err);
}
}
}
// Within two 8-bit levels: far below a visible difference.
assert!(worst * 255.0 < 2.5, "worst error {} levels", worst * 255.0);
assert!(worst_dark * 255.0 < 5.0, "worst error near black {} levels", worst_dark * 255.0);
}
#[test]
+19
View File
@@ -119,6 +119,25 @@ fn variable_font_settings_are_not_highlighted_as_substituted_glyphs() {
assert!(g.alternate_glyphs(&t).elements().is_empty());
}
/// Type in a composite font draws each class of character in its part's face, which is not a
/// substitute: Document Setup's highlight of substituted glyphs leaves it alone.
#[test]
fn composite_font_parts_are_not_highlighted_as_substituted_glyphs() {
use vectorcraft_doc::{CompositeFont, CompositePart, CompositeParts};
let _adding = adding_fonts();
let db = vectorcraft_text::FontDb::global();
let mut parts = CompositeParts::all(CompositePart::new("Source Sans 3", "Regular"));
parts.roman = CompositePart::new("JetBrains Mono", "Regular");
parts.numerals = CompositePart::new("Inter", "Regular");
db.set_composite_font(&CompositeFont { name: "CF Highlight".into(), parts });
let style = CharStyle { size: 40.0, font_family: "CF Highlight".into(), ..Default::default() };
let t = TextObject::point(Point::new(10.0, 60.0), "Ab12", style);
let g = text_geom(&t);
db.remove_composite_font("CF Highlight");
assert!(!g.all.elements().is_empty());
assert!(g.substituted_glyphs.elements().is_empty() && g.substituted_fonts.elements().is_empty());
}
/// Type at 80% opacity, in Multiply and with a drop shadow redraws in a font that becomes
/// available: the glyph geometry kept for it is laid out again.
#[test]
+8 -1
View File
@@ -191,7 +191,14 @@ pub(crate) fn text_geom_snapped(t: &TextObject, snap: Option<Affine>) -> TextGeo
if let Some(art) = g.placed_colour(Affine::IDENTITY) {
colours.push((g.run, Arc::new(art)));
}
let Some(&(missing, face)) = faces.get(g.run) else { continue };
let Some(&(mut missing, mut face)) = faces.get(g.run) else { continue };
// Type in a composite font: each character's face is its class's part's.
if let Some(r) = t.runs.get(g.run)
&& let Some(c) = plain.get(g.byte..).and_then(|s| s.chars().next())
&& let Some((m, f)) = db.composite_face(&r.style.font_family, c)
{
(missing, face) = (m, Some(f));
}
let Some(line) = layout.lines.get(g.line) else { continue };
let mut cell = Rect::new(g.origin.x, g.origin.y - line.ascent, g.origin.x + g.advance, g.origin.y + line.descent).to_path(0.1);
cell.apply_affine(Affine::rotate_about(g.angle, g.origin));