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 }