242 lines
8.0 KiB
Rust
242 lines
8.0 KiB
Rust
|
|
use hcie_protocol::tools::{TextAlignment, TextOrientation, TextEffect};
|
||
|
|
use hcie_text::TextRenderer;
|
||
|
|
|
||
|
|
/// Integration tests for hcie-text text rendering.
|
||
|
|
///
|
||
|
|
/// Covers: font loading, font management, rasterization baseline,
|
||
|
|
/// error handling for missing fonts, fallback behavior,
|
||
|
|
/// create_text_layer and refresh_text_layer.
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Helper: load a built-in font for testing
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
fn setup_renderer() -> (TextRenderer, bool) {
|
||
|
|
let mut renderer = TextRenderer::new();
|
||
|
|
let font_data = include_bytes!("../tests/fixtures/Inter-Regular.ttf");
|
||
|
|
let ok = renderer.load_font("Inter", font_data).is_ok();
|
||
|
|
(renderer, ok)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Font management
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn new_renderer_has_no_fonts() {
|
||
|
|
let renderer = TextRenderer::new();
|
||
|
|
assert!(renderer.get_fonts().is_empty(), "new renderer should have no fonts");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn load_font_succeeds_with_valid_data() {
|
||
|
|
let mut renderer = TextRenderer::new();
|
||
|
|
// embed a small TTF for testing — must exist at this path
|
||
|
|
let font_data = include_bytes!("../tests/fixtures/Inter-Regular.ttf");
|
||
|
|
let result = renderer.load_font("Inter", font_data);
|
||
|
|
assert!(result.is_ok(), "loading a valid TTF should succeed: {:?}", result.err());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn load_font_fails_with_invalid_data() {
|
||
|
|
let mut renderer = TextRenderer::new();
|
||
|
|
let result = renderer.load_font("Invalid", &[0u8, 1, 2, 3]);
|
||
|
|
assert!(result.is_err(), "loading garbage data as a font should fail");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn has_font_returns_true_after_loading() {
|
||
|
|
let mut renderer = TextRenderer::new();
|
||
|
|
let font_data = include_bytes!("../tests/fixtures/Inter-Regular.ttf");
|
||
|
|
if renderer.load_font("Inter", font_data).is_ok() {
|
||
|
|
assert!(renderer.has_font("Inter"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn has_font_returns_false_for_unloaded() {
|
||
|
|
let renderer = TextRenderer::new();
|
||
|
|
assert!(!renderer.has_font("NonexistentFont"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn get_fonts_returns_loaded_names() {
|
||
|
|
let mut renderer = TextRenderer::new();
|
||
|
|
let font_data = include_bytes!("../tests/fixtures/Inter-Regular.ttf");
|
||
|
|
if renderer.load_font("MyFont", font_data).is_ok() {
|
||
|
|
let fonts = renderer.get_fonts();
|
||
|
|
assert!(fonts.contains(&"MyFont".to_string()));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Text rasterization (baseline — if font is available)
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn rasterize_text_returns_valid_output() {
|
||
|
|
let (mut renderer, font_ok) = setup_renderer();
|
||
|
|
if !font_ok {
|
||
|
|
eprintln!("Skipping: test font not available");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let result = renderer.rasterize_text(
|
||
|
|
"Hello",
|
||
|
|
"Inter",
|
||
|
|
24.0,
|
||
|
|
[0, 0, 0, 255], // black
|
||
|
|
0.0, 0.0, // x, y
|
||
|
|
0.0, // angle
|
||
|
|
TextAlignment::Left,
|
||
|
|
TextOrientation::Horizontal,
|
||
|
|
&[], // no effects
|
||
|
|
true, // anti-alias
|
||
|
|
);
|
||
|
|
|
||
|
|
assert!(result.is_ok(), "rasterize should succeed: {:?}", result.err());
|
||
|
|
|
||
|
|
let (pixels, w, h, _ox, _oy, _uw, _uh) = result.unwrap();
|
||
|
|
assert!(w > 0, "rasterized width should be > 0");
|
||
|
|
assert!(h > 0, "rasterized height should be > 0");
|
||
|
|
assert_eq!(pixels.len(), (w * h * 4) as usize);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn rasterize_empty_string_produces_no_pixels_or_minimal() {
|
||
|
|
let (mut renderer, font_ok) = setup_renderer();
|
||
|
|
if !font_ok { return; }
|
||
|
|
|
||
|
|
let result = renderer.rasterize_text(
|
||
|
|
"", "Inter", 24.0, [0, 0, 0, 255],
|
||
|
|
0.0, 0.0, 0.0, TextAlignment::Left,
|
||
|
|
TextOrientation::Horizontal, &[], true,
|
||
|
|
);
|
||
|
|
|
||
|
|
// Empty string may return an error or a 0-size buffer; either is acceptable
|
||
|
|
match result {
|
||
|
|
Ok((pixels, w, h, _, _, _, _)) => {
|
||
|
|
assert!(w == 0 || h == 0 || pixels.is_empty(),
|
||
|
|
"empty string should produce trivial output: {}x{}", w, h);
|
||
|
|
}
|
||
|
|
Err(_) => { /* empty string may legitimately fail */ }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn rasterize_returns_different_output_for_different_sizes() {
|
||
|
|
let (mut renderer, font_ok) = setup_renderer();
|
||
|
|
if !font_ok { return; }
|
||
|
|
|
||
|
|
let small = renderer.rasterize_text(
|
||
|
|
"A", "Inter", 12.0, [0, 0, 0, 255],
|
||
|
|
0.0, 0.0, 0.0, TextAlignment::Left,
|
||
|
|
TextOrientation::Horizontal, &[], true,
|
||
|
|
).unwrap();
|
||
|
|
let large = renderer.rasterize_text(
|
||
|
|
"A", "Inter", 48.0, [0, 0, 0, 255],
|
||
|
|
0.0, 0.0, 0.0, TextAlignment::Left,
|
||
|
|
TextOrientation::Horizontal, &[], true,
|
||
|
|
).unwrap();
|
||
|
|
|
||
|
|
// Larger font size should produce taller output
|
||
|
|
assert!(large.2 >= small.2, "larger font should produce taller output");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Color handling
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn rasterize_text_applies_color() {
|
||
|
|
let (mut renderer, font_ok) = setup_renderer();
|
||
|
|
if !font_ok { return; }
|
||
|
|
|
||
|
|
let result = renderer.rasterize_text(
|
||
|
|
"X", "Inter", 24.0, [255, 0, 0, 255], // red
|
||
|
|
0.0, 0.0, 0.0, TextAlignment::Left,
|
||
|
|
TextOrientation::Horizontal, &[], true,
|
||
|
|
);
|
||
|
|
|
||
|
|
if let Ok((pixels, w, h, _, _, _, _)) = result {
|
||
|
|
if w > 0 && h > 0 {
|
||
|
|
// At least some pixels should have non-zero red channel
|
||
|
|
let has_red = pixels.iter().step_by(4).any(|&r| r > 0);
|
||
|
|
assert!(has_red, "text rasterized in red should have non-zero R channel");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// create_text_layer
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn create_text_layer_produces_valid_layer() {
|
||
|
|
let (mut renderer, font_ok) = setup_renderer();
|
||
|
|
if !font_ok { return; }
|
||
|
|
|
||
|
|
let result = renderer.create_text_layer(
|
||
|
|
"Hello", "Inter", 24.0, [0, 0, 0, 255],
|
||
|
|
10.0, 20.0, 0.0, TextAlignment::Left,
|
||
|
|
TextOrientation::Horizontal, &[],
|
||
|
|
);
|
||
|
|
|
||
|
|
assert!(result.is_ok(), "create_text_layer should succeed: {:?}", result.err());
|
||
|
|
let layer = result.unwrap();
|
||
|
|
assert!(layer.width > 0);
|
||
|
|
assert!(layer.height > 0);
|
||
|
|
assert!(layer.pixels.len() >= 4);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn create_text_layer_fails_for_missing_font() {
|
||
|
|
let mut renderer = TextRenderer::new();
|
||
|
|
let result = renderer.create_text_layer(
|
||
|
|
"Hello", "NonExistentFont", 24.0, [0, 0, 0, 255],
|
||
|
|
0.0, 0.0, 0.0, TextAlignment::Left,
|
||
|
|
TextOrientation::Horizontal, &[],
|
||
|
|
);
|
||
|
|
assert!(result.is_err(), "should fail when font is not loaded");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Error handling
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn rasterize_fails_for_missing_font() {
|
||
|
|
let mut renderer = TextRenderer::new();
|
||
|
|
let result = renderer.rasterize_text(
|
||
|
|
"Hi", "AbsentFont", 12.0, [0; 4],
|
||
|
|
0.0, 0.0, 0.0, TextAlignment::Left,
|
||
|
|
TextOrientation::Horizontal, &[], true,
|
||
|
|
);
|
||
|
|
assert!(result.is_err(), "rasterize with missing font should fail");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// refresh_text_layer
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn refresh_text_layer_does_not_panic() {
|
||
|
|
let (mut renderer, font_ok) = setup_renderer();
|
||
|
|
if !font_ok { return; }
|
||
|
|
|
||
|
|
let mut layer = match renderer.create_text_layer(
|
||
|
|
"Test", "Inter", 16.0, [0, 0, 0, 255],
|
||
|
|
0.0, 0.0, 0.0, TextAlignment::Left,
|
||
|
|
TextOrientation::Horizontal, &[],
|
||
|
|
) {
|
||
|
|
Ok(l) => l,
|
||
|
|
Err(e) => {
|
||
|
|
eprintln!("Skipping refresh test: cannot create layer: {}", e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
let result = renderer.refresh_text_layer(&mut layer);
|
||
|
|
assert!(result.is_ok() || result.is_err());
|
||
|
|
// Either outcome is valid depending on the layer's state
|
||
|
|
}
|