using RioJoy.Core.Overlay; using Xunit; namespace RioJoy.Core.Tests.Overlay; public class OverlayLayoutEngineTests { // LinearTextMeasurer(0.6, 1.2): width = len*size*0.6, height = size*1.2. private static readonly OverlayLayoutEngine Engine = new(new LinearTextMeasurer()); private static OverlayRegion Region( string name = "b-00", double x = 0, double y = 0, double w = 200, double h = 40, double size = 10) => new() { Name = name, X = x, Y = y, Width = w, Height = h, FontSizePx = size, FontFamily = "Sans" }; [Fact] public void Place_TextFits_KeepsBaseSize_AndCenters() { // "FIRE" at size 10 -> 24 x 12, well inside 200 x 40. PlacedLabel p = Engine.Place(Region(x: 10, y: 20), "FIRE"); Assert.Equal(10, p.FontSizePx, 6); Assert.Equal(24, p.Width, 6); Assert.Equal(12, p.Height, 6); Assert.Equal(10 + (200 - 24) / 2, p.X, 6); // 98 Assert.Equal(20 + (40 - 12) / 2, p.Y, 6); // 34 } [Fact] public void Place_Overflow_Fit_ShrinksFont() { // Narrow cell: "FIRE" at base 10 is 24 wide > 20 -> auto-fit shrinks it. PlacedLabel p = Engine.Place(Region(w: 20, h: 40), "FIRE"); Assert.True(p.FontSizePx < 10); Assert.True(p.Width <= 20, $"fitted width {p.Width} must be <= 20"); } [Fact] public void Place_Overflow_Crop_KeepsBaseSize() { var opts = new OverlayLayoutOptions { SizeMode = OverlaySizeMode.Crop }; PlacedLabel p = Engine.Place(Region(w: 20, h: 40), "FIRE", opts); Assert.Equal(10, p.FontSizePx, 6); // not shrunk Assert.Equal(24, p.Width, 6); // overflows the 20-wide cell } [Theory] [InlineData(OverlayHJust.Left, 0)] [InlineData(OverlayHJust.Center, 44)] // (100-12)/2 [InlineData(OverlayHJust.Right, 88)] // 100-12 public void Place_HorizontalJustification(OverlayHJust hjust, double expectedX) { // "AB" at size 10 -> 12 x 12 inside a 100 x 20 cell at origin. var opts = new OverlayLayoutOptions { HJust = hjust }; PlacedLabel p = Engine.Place(Region(w: 100, h: 20), "AB", opts); Assert.Equal(expectedX, p.X, 6); } [Theory] [InlineData(OverlayVJust.Top, 0)] [InlineData(OverlayVJust.Center, 4)] // (20-12)/2 [InlineData(OverlayVJust.Bottom, 8)] // 20-12 public void Place_VerticalJustification(OverlayVJust vjust, double expectedY) { var opts = new OverlayLayoutOptions { VJust = vjust }; PlacedLabel p = Engine.Place(Region(w: 100, h: 20), "AB", opts); Assert.Equal(expectedY, p.Y, 6); } [Fact] public void Place_RegionOverride_BeatsEngineDefault() { OverlayRegion region = Region(w: 100, h: 20); region.HJust = OverlayHJust.Right; region.VJust = OverlayVJust.Top; var opts = new OverlayLayoutOptions { HJust = OverlayHJust.Left, VJust = OverlayVJust.Bottom }; PlacedLabel p = Engine.Place(region, "AB", opts); Assert.Equal(88, p.X, 6); // Right wins over Left Assert.Equal(0, p.Y, 6); // Top wins over Bottom } [Fact] public void Place_Rotated_CentersInCell_AndCarriesAngle() { // "HELLO" at size 10 -> 30 x 12; fits the swapped 200 x 50 envelope, so size holds. OverlayRegion region = Region(w: 50, h: 200, size: 10); region.RotationDegrees = 90; PlacedLabel p = Engine.Place(region, "HELLO"); Assert.Equal(90, p.RotationDegrees, 6); Assert.Equal(10, p.FontSizePx, 6); Assert.Equal((50 - 30) / 2.0, p.X, 6); // centered horizontally -> 10 Assert.Equal((200 - 12) / 2.0, p.Y, 6); // centered vertically -> 94 } [Fact] public void Place_Rotated_FitsAgainstSwappedDimensions() { // A wide, short cell: upright the text fits height-bound; rotated it must fit // the (swapped) narrow height, forcing a smaller font. OverlayRegion upright = Region(w: 200, h: 20, size: 30); OverlayRegion rotated = Region(w: 200, h: 20, size: 30); rotated.RotationDegrees = 90; double uprightSize = Engine.Place(upright, "HELLO").FontSizePx; double rotatedSize = Engine.Place(rotated, "HELLO").FontSizePx; Assert.True(rotatedSize < uprightSize, $"rotated {rotatedSize} should be smaller than upright {uprightSize} (fit uses swapped axes)"); } [Fact] public void Layout_SkipsEmptyAndMissingLabels_PreservesRegionOrder() { var template = new OverlayTemplate { Regions = { Region("b-00"), Region("b-01"), Region("b-02"), }, }; var labels = new Dictionary { ["b-00"] = "FIRE", ["b-01"] = "", // empty -> skipped ["b-02"] = "GEAR", ["b-99"] = "ORPHAN", // no such region -> ignored }; IList placed = Engine.Layout(template, labels); Assert.Equal(2, placed.Count); Assert.Equal("b-00", placed[0].RegionName); Assert.Equal("b-02", placed[1].RegionName); Assert.Equal("GEAR", placed[1].Text); } }