use super::*;
const FONT_BYTES: &[u8] = include_bytes!("../../../demo/assets/CascadiaCode.ttf");
const FA_BYTES: &[u8] = include_bytes!("../../../demo/assets/fa.ttf");
fn test_font() -> Arc<Font> {
Arc::new(Font::from_slice(FONT_BYTES).expect("font ok"))
}
const FA_LAPTOP: &str = "\u{F109}";
#[test]
fn test_shape_text_renders_fa_icon_via_fallback() {
let fa = Font::from_slice(FA_BYTES).expect("parse fa.ttf");
let font = Arc::new(
Font::from_slice(FONT_BYTES)
.expect("cc")
.with_fallback(Arc::new(fa)),
);
let shaped = shape_glyphs(&font, FA_LAPTOP, 16.0);
assert_eq!(shaped.len(), 1);
assert!(
shaped[0].fallback_font.is_some(),
"FA codepoint must resolve via fallback font"
);
let (paths, _adv) = shape_text(&font, FA_LAPTOP, 16.0, 0.0, 0.0);
assert_eq!(
paths.len(),
1,
"fallback outline must yield exactly one PathStorage for FA_LAPTOP"
);
}
#[test]
fn test_shape_text_fa_outline_matches_fallback_font() {
use agg_rust::basics::{is_stop, VertexSource};
use agg_rust::conv_curve::ConvCurve;
let fa_arc = Arc::new(Font::from_slice(FA_BYTES).expect("fa"));
let font = Arc::new(
Font::from_slice(FONT_BYTES)
.expect("cc")
.with_fallback(Arc::clone(&fa_arc)),
);
let (mut paths, _) = shape_text(&font, FA_LAPTOP, 48.0, 0.0, 0.0);
assert_eq!(paths.len(), 1);
let mut curves = ConvCurve::new(&mut paths[0]);
curves.rewind(0);
let (mut xmin, mut xmax) = (f64::INFINITY, f64::NEG_INFINITY);
loop {
let (mut cx, mut cy) = (0.0, 0.0);
let cmd = curves.vertex(&mut cx, &mut cy);
if is_stop(cmd) {
break;
}
if cx < xmin {
xmin = cx;
}
if cx > xmax {
xmax = cx;
}
let _ = cy;
}
let width = xmax - xmin;
assert!(
width > 32.0,
"FA glyph outline width at 48 px was {width:.1} — too narrow, \
likely still rendering CascadiaCode .notdef instead of FA fallback"
);
}
#[test]
fn test_flatten_point_count_is_sane() {
let font = test_font();
let sizes: &[f64] = &[10.0, 13.0, 14.0, 24.0, 34.0];
let texts: &[&str] = &[
"Hello",
"The quick brown fox",
"Caption — 10px The quick brown fox",
"agg-gui",
"Aa",
];
for &size in sizes {
for &text in texts {
let contours = shape_and_flatten_text(&font, text, size, 0.0, 0.0, 0.5);
let total_pts: usize = contours.iter().map(|c| c.len()).sum();
let char_count = text.chars().count().max(1);
let pts_per_char = total_pts / char_count;
assert!(
pts_per_char <= 500,
"size={size} text={text:?}: {pts_per_char} pts/char \
(total {total_pts}) — too many, subdivision loop likely"
);
assert!(
total_pts > 0 || text.trim().is_empty(),
"size={size} text={text:?}: zero points produced"
);
}
}
}
#[test]
fn test_dump_single_char_coords() {
use crate::gl_renderer::tessellate_fill;
let font = test_font();
for ch in ['W', 'i', 'd', 'g', 'e', 't', 's'] {
let s = ch.to_string();
let contours = shape_and_flatten_text(&font, &s, 13.0, 10.0, 50.0, 0.5);
let total: usize = contours.iter().map(|c| c.len()).sum();
eprintln!("{:?}: {} contours, {} pts", ch, contours.len(), total);
for (ci, c) in contours.iter().enumerate() {
if c.is_empty() {
continue;
}
let xs: Vec<f32> = c.iter().map(|p| p[0]).collect();
let ys: Vec<f32> = c.iter().map(|p| p[1]).collect();
let xmin = xs.iter().cloned().fold(f32::INFINITY, f32::min);
let xmax = xs.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let ymin = ys.iter().cloned().fold(f32::INFINITY, f32::min);
let ymax = ys.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
eprintln!(
" contour {ci}: {}/{} pts x:[{xmin:.1},{xmax:.1}] y:[{ymin:.1},{ymax:.1}]",
c.len(),
c.len()
);
}
let result = tessellate_fill(&contours);
eprintln!(
" tess: {:?}",
result.as_ref().map(|(v, i)| (v.len() / 2, i.len() / 3))
);
}
}
#[test]
fn test_first_frame_text_pipeline_is_fast() {
use crate::gl_renderer::tessellate_fill;
use std::time::Instant;
let font = test_font();
let t0 = Instant::now();
let calls: &[(&str, f64)] = &[
("Basics", 13.0),
("Widgets", 13.0),
("Text", 13.0),
("Layout", 13.0),
("Tree", 13.0),
("3D Demo", 16.0),
("WebGL2 — rotating cube", 11.0),
("Primary Action", 14.0),
("Secondary", 14.0),
("Destructive", 14.0),
("Type something\u{2026}", 14.0),
("Another field", 14.0),
];
let mut total_pts = 0usize;
let mut total_tris = 0usize;
for &(text, size) in calls {
let contours = shape_and_flatten_text(&font, text, size, 10.0, 50.0, 0.5);
total_pts += contours.iter().map(|c| c.len()).sum::<usize>();
if let Some((verts, idx)) = tessellate_fill(&contours) {
total_tris += idx.len() / 3;
let _ = verts;
}
}
let elapsed = t0.elapsed();
assert!(total_pts > 0, "no contour points produced");
assert!(total_tris > 0, "no triangles tessellated");
assert!(
elapsed.as_millis() < 200,
"first-frame text pipeline took {}ms (pts={total_pts} tris={total_tris}) — \
too slow, would hang browser (WASM is ~5× slower)",
elapsed.as_millis()
);
eprintln!(
"first-frame text: {total_pts} pts, {total_tris} tris in {}ms",
elapsed.as_millis()
);
}
#[test]
fn test_shape_glyphs_basic() {
let font = test_font();
let glyphs = shape_glyphs(&font, "Hi", 14.0);
assert_eq!(glyphs.len(), 2, "two glyphs for 'Hi'");
assert!(glyphs[0].x_advance > 0.0, "H has positive advance");
assert!(glyphs[1].x_advance > 0.0, "i has positive advance");
}
#[test]
fn test_flatten_glyph_at_origin_local_coords() {
let font = test_font();
let size = 16.0_f64;
let glyphs = shape_glyphs(&font, "H", size);
assert!(!glyphs.is_empty());
let gid = glyphs[0].glyph_id;
let contours = flatten_glyph_at_origin(&font, gid, size).expect("'H' must have an outline");
assert!(!contours.is_empty(), "should produce at least one contour");
for contour in &contours {
for &[x, y] in contour {
assert!(
x >= -2.0 && x <= size as f32 + 4.0,
"x={x} should be in glyph-local pixels for size={size}"
);
assert!(
y >= -size as f32 * 0.3 && y <= size as f32 * 1.2,
"y={y} should be in glyph-local pixels for size={size}"
);
}
}
}
#[test]
fn test_flatten_glyph_at_origin_space_returns_none() {
let font = test_font();
let glyphs = shape_glyphs(&font, " ", 14.0);
assert_eq!(glyphs.len(), 1);
let result = flatten_glyph_at_origin(&font, glyphs[0].glyph_id, 14.0);
assert!(
result.is_none(),
"space glyph should have no outline, got {:?}",
result.as_ref().map(|c| c.len())
);
}
#[test]
fn test_flatten_output_is_in_screen_space() {
let font = test_font();
let contours = shape_and_flatten_text(&font, "Hello", 16.0, 100.0, 200.0, 0.5);
assert!(!contours.is_empty(), "should produce contours for 'Hello'");
for (ci, contour) in contours.iter().enumerate() {
for &[x, y] in contour {
assert!(
x > 50.0 && x < 300.0,
"contour {ci}: x={x} looks like font units, not screen px"
);
assert!(
y > 150.0 && y < 280.0,
"contour {ci}: y={y} looks like font units, not screen px"
);
}
}
}