using System; using Amphora; using Glass.Text; using Glass.Typesetting; using Glass.UnitTests.Typesetting; using UnitTestSharp; namespace Glass.UnitTests.Text { public class FaceResolverTests : TestFixture { private const string Page = "family.png"; /// /// A family of four faces on one page, each a different width so a test can tell them apart. /// private class Typeface { public readonly SpriteFontDefinition Regular = FacedMeasuringTests.Face(10, page: Page); public readonly SpriteFontDefinition Bold = FacedMeasuringTests.Face(11, page: Page); public readonly SpriteFontDefinition Italic = FacedMeasuringTests.Face(12, page: Page); public readonly SpriteFontDefinition BoldItalic = FacedMeasuringTests.Face(13, page: Page); public Typeface() { new FontFamily(Regular, Bold, Italic, BoldItalic); } } private static DynamicArray Spans(params StyleSpan[] spans) { var returnMe = new DynamicArray(capacity: spans.Length); foreach (StyleSpan span in spans) { returnMe.Add(span); } return returnMe; } private static StyleSpan Span(int start, int end, TextStyle style) => new StyleSpan(start, end, style); public class ResolveTests : TestFixture { public void NoSpans_LeaveEverythingInTheBaseFont() { var typeface = new Typeface(); CheckTrue(FaceResolver.Resolve(typeface.Regular, null, reuse: null).IsUniform); CheckTrue(FaceResolver.Resolve(typeface.Regular, Spans(), reuse: null).IsUniform); } public void SpansThatAskForNoFace_LeaveEverythingInTheBaseFont() { var typeface = new Typeface(); DynamicArray spans = Spans(Span(0, 3, new TextStyle { TintColor = Prism.Colors.Red })); CheckTrue(FaceResolver.Resolve(typeface.Regular, spans, reuse: null).IsUniform); } public void BoldSpan_IsSetInTheBoldFace() { var typeface = new Typeface(); DynamicArray spans = Spans(Span(2, 4, new TextStyle { Bold = true })); FaceRuns faces = FaceResolver.Resolve(typeface.Regular, spans, reuse: null); CheckTrue(ReferenceEquals(typeface.Regular, faces.FontAt(1))); CheckTrue(ReferenceEquals(typeface.Bold, faces.FontAt(2))); CheckTrue(ReferenceEquals(typeface.Bold, faces.FontAt(3))); CheckTrue(ReferenceEquals(typeface.Regular, faces.FontAt(4))); } public void ItalicSpan_IsSetInTheItalicFace() { var typeface = new Typeface(); DynamicArray spans = Spans(Span(0, 2, new TextStyle { Italic = true })); FaceRuns faces = FaceResolver.Resolve(typeface.Regular, spans, reuse: null); CheckTrue(ReferenceEquals(typeface.Italic, faces.FontAt(0))); } public void BoldItalicSpan_IsSetInTheBoldItalicFace() { var typeface = new Typeface(); DynamicArray spans = Spans(Span(0, 2, new TextStyle { Bold = true, Italic = true })); FaceRuns faces = FaceResolver.Resolve(typeface.Regular, spans, reuse: null); CheckTrue(ReferenceEquals(typeface.BoldItalic, faces.FontAt(1))); } public void BoldOffInARegularFont_ChangesNothing() { var typeface = new Typeface(); DynamicArray spans = Spans(Span(0, 2, new TextStyle { Bold = false, Italic = false })); CheckTrue(FaceResolver.Resolve(typeface.Regular, spans, reuse: null).IsUniform); } public void ItalicOverABoldBaseFont_IsBoldItalic() { var typeface = new Typeface(); DynamicArray spans = Spans(Span(0, 2, new TextStyle { Italic = true })); FaceRuns faces = FaceResolver.Resolve(typeface.Bold, spans, reuse: null); CheckTrue(ReferenceEquals(typeface.BoldItalic, faces.FontAt(0))); } public void BoldOffOverABoldBaseFont_IsRegular() { var typeface = new Typeface(); DynamicArray spans = Spans(Span(1, 2, new TextStyle { Bold = false })); FaceRuns faces = FaceResolver.Resolve(typeface.Bold, spans, reuse: null); CheckTrue(ReferenceEquals(typeface.Bold, faces.FontAt(0))); CheckTrue(ReferenceEquals(typeface.Regular, faces.FontAt(1))); } public void BoldOverABoldBaseFont_ChangesNothing() { var typeface = new Typeface(); DynamicArray spans = Spans(Span(0, 2, new TextStyle { Bold = true })); CheckTrue(FaceResolver.Resolve(typeface.Bold, spans, reuse: null).IsUniform); } public void ExplicitFont_IsSetInIt() { var typeface = new Typeface(); SpriteFontDefinition other = FacedMeasuringTests.Face(20, page: Page); DynamicArray spans = Spans(Span(1, 3, new TextStyle { Font = other })); FaceRuns faces = FaceResolver.Resolve(typeface.Regular, spans, reuse: null); CheckTrue(ReferenceEquals(other, faces.FontAt(2))); } public void BoldOverAnExplicitFont_IsItsFamilysBoldFace() { var typeface = new Typeface(); var other = new Typeface(); DynamicArray spans = Spans(Span(0, 2, new TextStyle { Font = other.Regular, Bold = true })); FaceRuns faces = FaceResolver.Resolve(typeface.Regular, spans, reuse: null); CheckTrue(ReferenceEquals(other.Bold, faces.FontAt(0))); } public void SeparateSpans_MakeSeparateRuns() { var typeface = new Typeface(); DynamicArray spans = Spans( Span(0, 2, new TextStyle { Bold = true }), Span(3, 5, new TextStyle { Italic = true })); FaceRuns faces = FaceResolver.Resolve(typeface.Regular, spans, reuse: null); CheckTrue(ReferenceEquals(typeface.Bold, faces.FontAt(1))); CheckTrue(ReferenceEquals(typeface.Regular, faces.FontAt(2))); CheckTrue(ReferenceEquals(typeface.Italic, faces.FontAt(4))); } public void OverlappingSpans_LetTheFirstToHoldACharacterDecideItsFont() { var typeface = new Typeface(); DynamicArray spans = Spans( Span(0, 4, new TextStyle { Bold = true }), Span(2, 6, new TextStyle { Italic = true })); FaceRuns faces = FaceResolver.Resolve(typeface.Regular, spans, reuse: null); CheckTrue(ReferenceEquals(typeface.Bold, faces.FontAt(3))); CheckTrue(ReferenceEquals(typeface.Italic, faces.FontAt(4))); CheckTrue(ReferenceEquals(typeface.Italic, faces.FontAt(5))); CheckTrue(ReferenceEquals(typeface.Regular, faces.FontAt(6))); } public void ASpanOfNoFaceCoveringAnother_KeepsTheBaseFontThere() { var typeface = new Typeface(); DynamicArray spans = Spans(Span(0, 6, new TextStyle { TintColor = Prism.Colors.Red }), Span(2, 4, new TextStyle { Bold = true })); CheckTrue(FaceResolver.Resolve(typeface.Regular, spans, reuse: null).IsUniform); } public void ASpanInsideAnother_AddsNothing() { var typeface = new Typeface(); DynamicArray spans = Spans( Span(0, 6, new TextStyle { Bold = true }), Span(2, 4, new TextStyle { Italic = true })); FaceRuns faces = FaceResolver.Resolve(typeface.Regular, spans, reuse: null); CheckTrue(ReferenceEquals(typeface.Bold, faces.FontAt(3))); CheckEqual(2, faces.Count); } public void SpansThatMeetInTheSameFace_AreOneRun() { var typeface = new Typeface(); DynamicArray spans = Spans( Span(0, 2, new TextStyle { Bold = true }), Span(2, 4, new TextStyle { Bold = true })); CheckEqual(2, FaceResolver.Resolve(typeface.Regular, spans, reuse: null).Count); } public void BoldInAFontOfNoFamily_Throws() { SpriteFontDefinition lone = FacedMeasuringTests.Face(10, page: Page); DynamicArray spans = Spans(Span(0, 2, new TextStyle { Bold = true })); CheckThrow(typeof(InvalidOperationException)); FaceResolver.Resolve(lone, spans, reuse: null); } public void ItalicInAFamilyWithNoItalicFace_Throws() { SpriteFontDefinition regular = FacedMeasuringTests.Face(10, page: Page); new FontFamily(regular, bold: FacedMeasuringTests.Face(11, page: Page)); DynamicArray spans = Spans(Span(0, 2, new TextStyle { Italic = true })); CheckThrow(typeof(InvalidOperationException)); FaceResolver.Resolve(regular, spans, reuse: null); } public void AnExplicitFontOnAnotherPage_Throws() { var typeface = new Typeface(); SpriteFontDefinition elsewhere = FacedMeasuringTests.Face(10, page: "other.png"); DynamicArray spans = Spans(Span(0, 2, new TextStyle { Font = elsewhere })); CheckThrow(typeof(InvalidOperationException)); FaceResolver.Resolve(typeface.Regular, spans, reuse: null); } public void NullBaseFont_Throws() { CheckThrow(typeof(ArgumentNullException)); FaceResolver.Resolve(null, Spans(), reuse: null); } public void Reuse_IsFilledInPlace() { var typeface = new Typeface(); var reuse = new FaceRuns(typeface.Regular); reuse.Add(0, 9, typeface.Italic); DynamicArray spans = Spans(Span(1, 2, new TextStyle { Bold = true })); FaceRuns faces = FaceResolver.Resolve(typeface.Regular, spans, reuse); CheckTrue(ReferenceEquals(reuse, faces)); CheckTrue(ReferenceEquals(typeface.Regular, faces.FontAt(0))); CheckTrue(ReferenceEquals(typeface.Bold, faces.FontAt(1))); CheckTrue(ReferenceEquals(typeface.Regular, faces.FontAt(5))); } public void ReuseBuiltOnAnotherFont_IsLeftAloneAndNewRunsMade() { var typeface = new Typeface(); var reuse = new FaceRuns(typeface.Bold); FaceRuns faces = FaceResolver.Resolve(typeface.Regular, Spans(), reuse); CheckFalse(ReferenceEquals(reuse, faces)); CheckTrue(ReferenceEquals(typeface.Regular, faces.BaseFont)); } private static readonly Typeface AllocationTypeface = new Typeface(); private static readonly FaceRuns AllocationRuns = new FaceRuns(AllocationTypeface.Regular); private static readonly DynamicArray AllocationSpans = Spans( Span(0, 2, new TextStyle { Bold = true }), Span(3, 5, new TextStyle { Italic = true })); [AllocationTest] public void Resolving_AllocatesNothingOnceWarm() { FaceRuns faces = FaceResolver.Resolve(AllocationTypeface.Regular, AllocationSpans, AllocationRuns); CheckNoAllocations(); CheckEqual(4, faces.Count); } } } }