use cosmic_text::{SwashCache, SwashContent};
use crabshell_ui::{Content, Font, Row, Style, Text};
use super::{Buffer, Scene};
fn mul(channel: u8, alpha: u8) -> u8 {
((u16::from(channel) * u16::from(alpha) + 127) / 255) as u8
}
fn draw_color_glyph(family: &'static str, text: &str) -> (Buffer, Vec<u8>, i32, i32, u32, u32) {
let mut scene = Scene::new();
let panel = scene.panel().child(
Row::new()
.padding(20, 40)
.child(Text::new(text).style(Style::new().font(Font::new(family, 48.0)))),
);
let window = scene.window(panel, 160.0, 100.0);
let buffer = scene.full(&window, 1.0);
let view = window.view();
let Content::Text(text) = super::node_at(view.root(), &[0, 0]).content() else {
panic!("a text node has text content");
};
let origin = text.origin();
let mut cache = SwashCache::new();
for run in text.runs() {
let baseline = (origin.y + run.line_y).round();
for glyph in run.glyphs {
let physical = glyph.physical((origin.x, baseline), 1.0);
let Some(image) = cache.get_image_uncached(scene.text.font_system(), physical.cache_key)
else {
continue;
};
if image.content == SwashContent::Color {
let left = physical.x + image.placement.left;
let top = physical.y - image.placement.top;
return (
buffer,
image.data,
left,
top,
image.placement.width,
image.placement.height,
);
}
}
}
panic!("no color glyph in {family}; is the font installed?");
}
fn check(family: &'static str, text: &str, expected: impl Fn([u8; 4]) -> [u8; 4]) {
let (buffer, data, left, top, width, height) = draw_color_glyph(family, text);
let mut checked = 0;
for y in 0..height {
for x in 0..width {
let index = (y * width + x) as usize * 4;
let mut pixel = [0; 4];
pixel.copy_from_slice(&data[index..index + 4]);
if pixel[3] == 0 || pixel[3] == 255 {
continue;
}
let (bx, by) = (left + x as i32, top + y as i32);
if bx < 0 || by < 0 || bx as u32 >= buffer.width || by as u32 >= buffer.height {
continue;
}
let drawn = buffer.pixel(bx as u32, by as u32);
let want = expected(pixel);
assert!(
drawn.iter().zip(want).all(|(a, b)| a.abs_diff(b) <= 1),
"{family}: glyph pixel {pixel:?} drawn as {drawn:?}, expected {want:?}"
);
checked += 1;
}
}
assert!(checked > 10, "{family}: only {checked} edge pixels checked");
}
#[test]
fn colr_glyphs_are_already_premultiplied() {
check("Noto Znamenny Musical Notation", "\u{1CF01}", |pixel| pixel);
}
#[test]
fn bitmap_emoji_are_premultiplied_once() {
check("Noto Color Emoji", "\u{1F600}", |[r, g, b, a]| {
[mul(r, a), mul(g, a), mul(b, a), a]
});
}
#[test]
fn the_glyph_cache_is_cleared_once_it_grows_large() {
use crabshell_ui::Column;
let mut scene = Scene::new();
let text: String = (0x21_u32..=0x7E)
.chain(0xC0..=0x11F)
.filter_map(char::from_u32)
.collect();
let column = (8..21).fold(Column::new(), |column, size| {
column
.child(Text::new(text.clone()).style(Style::new().font(Font::new("sans-serif", size as f32))))
});
let window = scene.window(scene.panel().child(column), 4000.0, 400.0);
scene.full(&window, 1.0);
assert!(
scene.renderer.cached_glyphs() <= 1024,
"{}",
scene.renderer.cached_glyphs()
);
let small = scene.window(scene.panel().child(Text::new("abc")), 60.0, 20.0);
scene.full(&small, 1.0);
assert!((1..=2048).contains(&scene.renderer.cached_glyphs()));
}