using System; using System.Collections.Generic; using Glass.Typesetting; using UnitTestSharp; using GlyphDefinition = Glass.SpriteFontDefinition.GlyphDefinition; namespace Glass.UnitTests.Typesetting { public class TextWidthTests : TestFixture { public class IsSurrogateTests : TestFixture { public void ActualSurrogates_AreAllTrue() { for (int character = 0xD800; character <= 0xDFFF; ++character) { CheckTrue(TextWidth.IsSurrogate(character)); } } public void FarFromTheSurrogateBits_IsFalse() { CheckFalse(TextWidth.IsSurrogate(0x0041)); } public void CjkIdeograph_IsNotASurrogate() { CheckFalse(TextWidth.IsSurrogate(0x4E00)); } public void JustAboveTheSurrogateRange_IsFalse() { // 0xF800-0xFFFF sets every bit a narrower 4-bit mask (15, 14, 12, 11) would have tested, which is // exactly why that narrower mask would false-positive here; this 5-bit mask also pins bit 13, // which 0xF800 has set and a real surrogate never does, so it correctly reads as false. CheckFalse(TextWidth.IsSurrogate(0xF800)); } } public class CalculateTests : TestFixture { public void NullFont_Throws() { CheckThrow(typeof(ArgumentNullException)); TextWidth.Calculate(null, "ab"); } public void EmptyText_IsZero() { CheckEqual(0, TextWidth.Calculate(WordMeasurerTests.TestFont(), "")); } public void OneWord_KernsItsInterior() { CheckEqual(19, TextWidth.Calculate(WordMeasurerTests.TestFont(), "ab")); } public void KerningFlowsThroughSpaces_UnlikeAWordBoundary() { CheckEqual(28, TextWidth.Calculate(WordMeasurerTests.TestFont(), "ab c")); } public void MatchesTheOldEngineOnPlainText() { SpriteFontDefinition font = WordMeasurerTests.TestFont(); string text = "ab c ab"; CheckEqual(Convert.ToInt32(TextLayout.CalculateTextWidth(font, text)), TextWidth.Calculate(font, text)); } public void SoftHyphen_TakesNoWidthAndKerningCarriesAcross() { CheckEqual(15, TextWidth.Calculate(WordMeasurerTests.TestFont(), $"a{LineBreakIterator.SoftHyphen}c")); } public void AstralCharacter_MeasuresAsOneInvalidGlyph() { CheckEqual(WordMeasurerTests.InvalidGlyphWidth, TextWidth.Calculate(WordMeasurerTests.TestFont(), WordMeasurerTests.Emoji)); } public void AstralDefaultIgnorable_TakesNoWidthAndKerningCarriesAcross() { CheckEqual(15, TextWidth.Calculate(WordMeasurerTests.TestFont(), $"a{WordMeasurerTests.LanguageTag}c")); } public void LoneSurrogate_MeasuresAsTheInvalidGlyph() { CheckEqual(WordMeasurerTests.InvalidGlyphWidth, TextWidth.Calculate(WordMeasurerTests.TestFont(), WordMeasurer.LoneSurrogate.ToString())); } public void NonAsciiBmpCharacter_MeasuresItsOwnGlyph() { var glyphs = new Dictionary { ['é'] = new GlyphDefinition { Width = 12 } }; SpriteFontDefinition font = WordMeasurerTests.TestFont(glyphs); CheckEqual(12, TextWidth.Calculate(font, "é")); } public void LongAsciiRun_BatchesInPairsWithoutMiscounting() { CheckEqual(100, TextWidth.Calculate(WordMeasurerTests.TestFont(), new string('a', 10))); } public void OddLengthAsciiRun_HandlesTheLeftoverCharacter() { CheckEqual(90, TextWidth.Calculate(WordMeasurerTests.TestFont(), new string('a', 9))); } public void View_PositionsAreRelativeToItsStart() { CheckEqual(19, TextWidth.Calculate(WordMeasurerTests.TestFont(), ((StringView)"xxabxx")[2, 4])); } public void TwoAstralCharacters_EachPairIsWithinItsOwnWindow() { string text = WordMeasurerTests.Emoji + WordMeasurerTests.Emoji; CheckEqual(2 * WordMeasurerTests.InvalidGlyphWidth, TextWidth.Calculate(WordMeasurerTests.TestFont(), text)); } public void AstralCharacterAfterAnOddNumberOfCharacters_StraddlesTwoWindowsButStillCombines() { CheckEqual(10 + WordMeasurerTests.InvalidGlyphWidth + 10, TextWidth.Calculate(WordMeasurerTests.TestFont(), $"a{WordMeasurerTests.Emoji}a")); } public void TwoNonAsciiBmpCharacters_TakeTheGeneralPairPath() { var glyphs = new Dictionary { ['é'] = new GlyphDefinition { Width = 12 } }; SpriteFontDefinition font = WordMeasurerTests.TestFont(glyphs); CheckEqual(24, TextWidth.Calculate(font, "éé")); } // U+3000 ORed with a genuine high surrogate reads as 0xF800 rather than 0xD800: a mask-based check // would miss the surrogate here and later throw in ConvertToUtf32 for want of a high half. U+3000 is // default-ignorable in this font (width 0), so only the astral pair's own width should show. public void BmpCharacterThatHidesAHighSurrogateWhenOred_StillCombinesTheAstralPair() { string text = $" {WordMeasurerTests.Emoji} "; CheckEqual(WordMeasurerTests.InvalidGlyphWidth, TextWidth.Calculate(WordMeasurerTests.TestFont(), text)); } } } }