using System; using System.Linq; using Glass.Typesetting; using UnitTestSharp; using static Glass.Typesetting.LineBreakClass; namespace Glass.UnitTests.Typesetting { public class LineBreakPairEntryExtensionsTests : TestFixture { public static readonly LineBreakPairEntry AllFlags = LineBreakPairEntry.Absorbed | LineBreakPairEntry.ActsAsLetter | LineBreakPairEntry.StartsTrailing | LineBreakPairEntry.EndingMask | LineBreakPairEntry.EndsTrailing; public static readonly LineBreakPairEntry Everything = LineBreakPairEntry.BoundaryMask | AllFlags | LineBreakPairEntry.NextClassMask; public class BoundaryTests : TestFixture { public void IgnoresTheRest() { foreach (LineBreakBoundary boundary in Enum.GetValues(typeof(LineBreakBoundary))) { CheckEqual(boundary, ((LineBreakPairEntry)boundary | AllFlags | LineBreakPairEntry.NextClassMask).Boundary()); } } } public class NextClassTests : TestFixture { public void ReadsTheHighByte_AndIgnoresTheRest() { var entry = (LineBreakPairEntry)((int)ZW << 8) | LineBreakPairEntry.BoundaryMask | AllFlags; CheckEqual(ZW, entry.NextClass()); } } public class IsAbsorbedTests : TestFixture { public void ReadsOnlyItsOwnFlag() { CheckTrue(LineBreakPairEntry.Absorbed.IsAbsorbed()); CheckFalse((Everything & ~LineBreakPairEntry.Absorbed).IsAbsorbed()); } } public class ActsAsLetterTests : TestFixture { public void ReadsOnlyItsOwnFlag() { CheckTrue(LineBreakPairEntry.ActsAsLetter.ActsAsLetter()); CheckFalse((Everything & ~LineBreakPairEntry.ActsAsLetter).ActsAsLetter()); } } public class StartsTrailingTests : TestFixture { public void ReadsOnlyItsOwnFlag() { CheckTrue(LineBreakPairEntry.StartsTrailing.StartsTrailing()); CheckFalse((Everything & ~LineBreakPairEntry.StartsTrailing).StartsTrailing()); } } public class EndingTests : TestFixture { public void ReadsItsBits_AndIgnoresTheRest() { foreach (LineBreakEnding ending in Enum.GetValues(typeof(LineBreakEnding))) { var entry = (LineBreakPairEntry)((int)ending << 5) | (Everything & ~LineBreakPairEntry.EndingMask); CheckEqual(ending, entry.Ending()); } } } public class EndsTrailingTests : TestFixture { public void ReadsOnlyItsOwnFlag() { CheckTrue(LineBreakPairEntry.EndsTrailing.EndsTrailing()); CheckFalse((Everything & ~LineBreakPairEntry.EndsTrailing).EndsTrailing()); } } } public class LineBreakPairTableTests : TestFixture { public static LineBreakClass[] AllClasses => Enum.GetValues(typeof(LineBreakClass)).Cast().ToArray(); public static LineBreakClass[] AllRows => AllClasses.Append(LineBreakPairTable.PlainSpace).Append(LineBreakPairTable.LineFeedAfterCarriageReturn) .Append(LineBreakClassSetTable.StartOfText).ToArray(); public static readonly LineBreakPairTable Table = LineBreakPairTable.Build(settleByClasses: true); public class IndexerTests : TestFixture { public void TakesThePreviousClassFirst() { CheckEqual(LineBreakBoundary.Mandatory, Table[BK, AL].Boundary()); CheckEqual(LineBreakBoundary.Prohibited, Table[AL, BK].Boundary()); } } public class DependsOnContextTests : TestFixture { public void AfterSpaces_DependsUnlessTheyArePlain() { CheckTrue(LineBreakPairTable.DependsOnContext(SP, AL, plainSpaces: false)); CheckFalse(LineBreakPairTable.DependsOnContext(SP, AL, plainSpaces: true)); } public void AfterPlainSpaces_DependsBeforeWhatLooksAhead() { CheckTrue(LineBreakPairTable.DependsOnContext(SP, QU_Pf, plainSpaces: true)); CheckTrue(LineBreakPairTable.DependsOnContext(SP, IS, plainSpaces: true)); } public void QuotationOnEitherSide_Depends() { CheckTrue(LineBreakPairTable.DependsOnContext(QU, ID, plainSpaces: false)); CheckTrue(LineBreakPairTable.DependsOnContext(ID, QU, plainSpaces: false)); } public void AfterAHyphen_Depends() { CheckTrue(LineBreakPairTable.DependsOnContext(HY, ID, plainSpaces: false)); } public void NumberAfterASlash_Depends() { CheckTrue(LineBreakPairTable.DependsOnContext(SY, NU, plainSpaces: false)); } public void NumberAfterAnythingElse_DoesNot() { var previousClasses = new[] { NU, IS, PR, PO, OP, ID }; CheckTrue(previousClasses.All( previous => !LineBreakPairTable.DependsOnContext(previous, NU, plainSpaces: false))); } public void AffixAfterASeparatorOrBracket_Depends() { var previousClasses = new[] { SY, IS, CL, CP }; CheckTrue(previousClasses.All( previous => LineBreakPairTable.DependsOnContext(previous, PO, plainSpaces: false))); CheckTrue(LineBreakPairTable.DependsOnContext(IS, PR, plainSpaces: false)); } public void AffixAfterANumberOrAnythingElse_DoesNot() { CheckFalse(LineBreakPairTable.DependsOnContext(NU, PO, plainSpaces: false)); CheckFalse(LineBreakPairTable.DependsOnContext(ID, PR, plainSpaces: false)); } public void OpeningBracketAfterAnAffix_Depends() { CheckTrue(LineBreakPairTable.DependsOnContext(PR, OP, plainSpaces: false)); } public void AksaraPairs_Depend() { CheckTrue(LineBreakPairTable.DependsOnContext(AP, AK, plainSpaces: false)); CheckTrue(LineBreakPairTable.DependsOnContext(AK, VF, plainSpaces: false)); CheckTrue(LineBreakPairTable.DependsOnContext(VI, AK, plainSpaces: false)); CheckTrue(LineBreakPairTable.DependsOnContext(AK, AS, plainSpaces: false)); } public void TwoLetters_DoNot() { CheckFalse(LineBreakPairTable.DependsOnContext(AL, AL, plainSpaces: false)); } public void DottedCircleBeforeAnAksara_Depends() { CheckTrue(LineBreakPairTable.DependsOnContext(DottedCircle, AK, plainSpaces: false)); } public void LetterBeforeAnAksara_DoesNot() { CheckFalse(LineBreakPairTable.DependsOnContext(AL, AK, plainSpaces: false)); } public void RegionalIndicatorPairs_Depend() { CheckTrue(LineBreakPairTable.DependsOnContext(RI, RI, plainSpaces: false)); } public void BeforeAnEmojiModifier_Depends() { CheckTrue(LineBreakPairTable.DependsOnContext(ID, EM, plainSpaces: false)); } public void IdeographPair_DoesNot() { CheckFalse(LineBreakPairTable.DependsOnContext(ID, ID, plainSpaces: false)); } } public class BuildTests : TestFixture { public static readonly LineBreakClass[] Specials = { SP, ZWJ, CM }; public void OrdinaryCodePoint_ComesNextAsItsOwnClass() { CheckTrue(AllRows.All(row => AllClasses.Where(current => !Specials.Contains(current)) .All(current => Table[row, current].NextClass() == current || (row == CR && current == LF)))); } public void LineFeedAfterACarriageReturn_ComesNextAsItsOwnRow() { CheckEqual(LineBreakPairTable.LineFeedAfterCarriageReturn, Table[CR, LF].NextClass()); } public void LineFeedAfterACarriageReturnRow_SettlesLikeALineFeed() { const LineBreakPairEntry allButEnding = ~LineBreakPairEntry.EndingMask; CheckTrue(AllClasses.All(current => (Table[LineBreakPairTable.LineFeedAfterCarriageReturn, current] & allButEnding) == (Table[LF, current] & allButEnding))); } public void EveryRow_CarriesTheEndingOfAWordThatEndsInIt() { (LineBreakClass Row, LineBreakEnding Ending)[] expected = { (AL, LineBreakEnding.Plain), (SP, LineBreakEnding.Plain), (LineBreakPairTable.PlainSpace, LineBreakEnding.Plain), (ZWJ, LineBreakEnding.Plain), (SoftHyphen, LineBreakEnding.SoftHyphen), (BK, LineBreakEnding.HardBreak), (CR, LineBreakEnding.HardBreak), (LF, LineBreakEnding.HardBreak), (NL, LineBreakEnding.HardBreak), (LineBreakPairTable.LineFeedAfterCarriageReturn, LineBreakEnding.CarriageReturnLineFeed), }; CheckTrue(expected.All(pair => AllClasses.All(current => Table[pair.Row, current].Ending() == pair.Ending))); } public void SpaceOrHardBreakRow_EndsTrailing() { var rows = new[] { SP, LineBreakPairTable.PlainSpace, BK, CR, LF, NL, LineBreakPairTable.LineFeedAfterCarriageReturn, }; CheckTrue(rows.All(row => AllClasses.All(current => Table[row, current].EndsTrailing()))); } public void AnyOtherRow_DoesNotEndTrailing() { var rows = new[] { AL, ID, ZW, ZWJ, SoftHyphen, LineBreakClassSetTable.StartOfText }; CheckTrue(rows.All(row => AllClasses.All(current => !Table[row, current].EndsTrailing()))); } public void SpaceAfterWhatNothingLooksPast_ComesNextAsAPlainSpace() { var rows = new[] { AL, ID, NU, LineBreakPairTable.PlainSpace, LineBreakClassSetTable.StartOfText }; CheckTrue(rows.All(row => Table[row, SP].NextClass() == LineBreakPairTable.PlainSpace)); } public void SpaceAfterWhatARuleLooksPast_ComesNextAsASpace() { var rows = new[] { ZW, OP, CL, CP, B2, SP }; CheckTrue(rows.All(row => Table[row, SP].NextClass() == SP)); } public void SpaceWhoseRunMightNotBePlain_ComesNextAsASpace() { CheckEqual(SP, Table[QU_Pi, SP].NextClass()); CheckEqual(SP, Table[ZWJ, SP].NextClass()); } public void Joiner_ComesNextAsAJoiner() { CheckTrue(AllRows.All(row => Table[row, ZWJ].NextClass() == ZWJ)); } public void CombiningMarkThatActsAsALetter_ComesNextAsALetter() { CheckEqual(AL, Table[SP, CM].NextClass()); CheckEqual(AL, Table[LineBreakClassSetTable.StartOfText, CM].NextClass()); } public void PairTheClassesSettle_HasTheRulesAnswer() { CheckEqual(LineBreakBoundary.Prohibited, Table[CL, NS].Boundary()); CheckEqual(LineBreakBoundary.Allowed, Table[AL, ID].Boundary()); } public void DottedCircle_HasARowAndAColumn() { CheckEqual(LineBreakBoundary.Prohibited, Table[DottedCircle, AL].Boundary()); CheckEqual(LineBreakBoundary.Prohibited, Table[AL, DottedCircle].Boundary()); } public void PairThatReadsContext_DependsOnContext() { CheckEqual(LineBreakBoundary.DependsOnContext, Table[QU_Pi, AL].Boundary()); } public void StartOfTextRow_ProhibitsEverythingButMarks() { CheckTrue(AllClasses.Where(current => current != CM && current != ZWJ).All(current => Table[LineBreakClassSetTable.StartOfText, current].Boundary() == LineBreakBoundary.Prohibited)); } public void SpaceRow_DependsOnContext() { CheckTrue(AllClasses.All(current => Table[SP, current].Boundary() == LineBreakBoundary.DependsOnContext)); } public void PlainSpaceRow_SettlesWhatFollowsTheSpaces() { CheckEqual(LineBreakBoundary.Allowed, Table[LineBreakPairTable.PlainSpace, AL].Boundary()); CheckEqual(LineBreakBoundary.DependsOnContext, Table[LineBreakPairTable.PlainSpace, IS].Boundary()); } public void ZwjRow_DependsOnContext() { CheckTrue(AllClasses.All(current => Table[ZWJ, current].Boundary() == LineBreakBoundary.DependsOnContext)); } public void CombiningMarkColumns_DependOnContext() { CheckTrue(AllRows.All(previous => Table[previous, CM].Boundary() == LineBreakBoundary.DependsOnContext && Table[previous, ZWJ].Boundary() == LineBreakBoundary.DependsOnContext)); } public void EveryEntryThatActsAsALetter_DependsOnContext() { CheckTrue(AllRows.All(row => AllClasses.All(current => !Table[row, current].ActsAsLetter() || Table[row, current].Boundary() == LineBreakBoundary.DependsOnContext))); } public void MarkAfterAnItemThatTakesMarks_IsAbsorbed() { CheckTrue(Table[AL, CM].IsAbsorbed()); CheckTrue(Table[ID, ZWJ].IsAbsorbed()); CheckFalse(Table[AL, CM].ActsAsLetter()); } public void MarkAfterAZwj_IsAbsorbed() { CheckTrue(Table[ZWJ, CM].IsAbsorbed()); } public void MarkWithNothingToJoin_ActsAsALetter() { var rows = new[] { SP, LineBreakPairTable.PlainSpace, BK, CR, LF, NL, ZW, LineBreakClassSetTable.StartOfText, }; CheckTrue(rows.All(row => Table[row, CM].ActsAsLetter() && !Table[row, CM].IsAbsorbed())); } public void NonMark_IsNeitherAbsorbedNorALetter() { CheckTrue(AllRows.All(row => AllClasses.Where(current => current != CM && current != ZWJ) .All(current => !Table[row, current].IsAbsorbed() && !Table[row, current].ActsAsLetter()))); } public void SpaceOrHardBreakAfterAnythingElse_StartsTrailing() { var rows = new[] { AL, ID, CM, ZWJ, ZW, LineBreakClassSetTable.StartOfText }; CheckTrue(rows.All(row => new[] { SP, BK, CR, LF, NL }.All(current => Table[row, current].StartsTrailing()))); } public void SpaceOrHardBreakAfterASpaceOrHardBreak_DoesNotStartTrailing() { var trailing = new[] { SP, LineBreakPairTable.PlainSpace, BK, CR, LF, NL, LineBreakPairTable.LineFeedAfterCarriageReturn, }; CheckTrue(trailing.All(row => new[] { SP, BK, CR, LF, NL }.All(current => !Table[row, current].StartsTrailing()))); } public void AnythingElse_DoesNotStartTrailing() { CheckTrue(AllRows.All(row => AllClasses.Where(current => !current.Is(LineBreakClassSets.Trailing)) .All(current => !Table[row, current].StartsTrailing()))); } public void WithoutSettling_StillStartsTrailing() { LineBreakPairTable table = LineBreakPairTable.Build(settleByClasses: false); CheckTrue(table[AL, SP].StartsTrailing()); CheckFalse(table[SP, SP].StartsTrailing()); } public void WithoutSettling_LeavesEveryBoundaryToTheRules() { LineBreakPairTable table = LineBreakPairTable.Build(settleByClasses: false); CheckTrue(AllRows.All(row => AllClasses.All(current => table[row, current].Boundary() == LineBreakBoundary.DependsOnContext))); } public void WithoutSettling_StillJoinsMarks() { LineBreakPairTable table = LineBreakPairTable.Build(settleByClasses: false); CheckTrue(table[AL, CM].IsAbsorbed()); CheckTrue(table[SP, CM].ActsAsLetter()); } } } }