M .golden/paragraphLayout/spannedLoremIpsum20em.golden => .golden/paragraphLayout/spannedLoremIpsum20em.golden +24 -2
@@ 71,9 71,20 @@ ParagraphLayout
SpanLayout
[ Fragment
{ fragmentUserData = ()
+ , fragmentLine = 1
+ , fragmentAncestorBoxes =
+ [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}
+ ]
+ , fragmentRect = Rect {x_origin = 18310, y_origin = 0, x_size = 0, y_size = -1121}
+ , fragmentPen = (0, -932)
+ , fragmentGlyphs =
+ []
+ }
+ , Fragment
+ { fragmentUserData = ()
, fragmentLine = 2
, fragmentAncestorBoxes =
- [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = SpacedEdge 0}
+ [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}
]
, fragmentRect = Rect {x_origin = 0, y_origin = -1121, x_size = 8553, y_size = -1121}
, fragmentPen = (0, -932)
@@ 137,9 148,20 @@ ParagraphLayout
SpanLayout
[ Fragment
{ fragmentUserData = ()
+ , fragmentLine = 2
+ , fragmentAncestorBoxes =
+ [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}
+ ]
+ , fragmentRect = Rect {x_origin = 17443, y_origin = -1121, x_size = 0, y_size = -1121}
+ , fragmentPen = (0, -932)
+ , fragmentGlyphs =
+ []
+ }
+ , Fragment
+ { fragmentUserData = ()
, fragmentLine = 3
, fragmentAncestorBoxes =
- [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = SpacedEdge 0}
+ [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}
]
, fragmentRect = Rect {x_origin = 0, y_origin = -2242, x_size = 9114, y_size = -1121}
, fragmentPen = (0, -932)
M .golden/richParagraphLayout/mixedLineHeight.golden => .golden/richParagraphLayout/mixedLineHeight.golden +52 -2
@@ 1,5 1,5 @@
ParagraphLayout
- { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 16708, y_size = -8900}
+ { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 16708, y_size = -9400}
, paragraphFragments =
[ Fragment
{ fragmentUserData = "mediumText"
@@ 86,6 86,16 @@ ParagraphLayout
]
}
, Fragment
+ { fragmentUserData = "lineBreak"
+ , fragmentLine = 1
+ , fragmentAncestorBoxes =
+ []
+ , fragmentRect = Rect {x_origin = 16708, y_origin = 0, x_size = 0, y_size = -1300}
+ , fragmentPen = (0, -1022)
+ , fragmentGlyphs =
+ []
+ }
+ , Fragment
{ fragmentUserData = "smallText"
, fragmentLine = 2
, fragmentAncestorBoxes =
@@ 136,6 146,16 @@ ParagraphLayout
]
}
, Fragment
+ { fragmentUserData = "lineBreak"
+ , fragmentLine = 2
+ , fragmentAncestorBoxes =
+ []
+ , fragmentRect = Rect {x_origin = 8852, y_origin = -1700, x_size = 0, y_size = -1300}
+ , fragmentPen = (0, -1022)
+ , fragmentGlyphs =
+ []
+ }
+ , Fragment
{ fragmentUserData = "mediumText"
, fragmentLine = 3
, fragmentAncestorBoxes =
@@ 169,6 189,16 @@ ParagraphLayout
]
}
, Fragment
+ { fragmentUserData = "lineBreak"
+ , fragmentLine = 3
+ , fragmentAncestorBoxes =
+ []
+ , fragmentRect = Rect {x_origin = 6303, y_origin = -3400, x_size = 0, y_size = -1300}
+ , fragmentPen = (0, -1022)
+ , fragmentGlyphs =
+ []
+ }
+ , Fragment
{ fragmentUserData = "largeText"
, fragmentLine = 4
, fragmentAncestorBoxes =
@@ 202,6 232,16 @@ ParagraphLayout
]
}
, Fragment
+ { fragmentUserData = "lineBreak"
+ , fragmentLine = 4
+ , fragmentAncestorBoxes =
+ []
+ , fragmentRect = Rect {x_origin = 6246, y_origin = -4700, x_size = 0, y_size = -1300}
+ , fragmentPen = (0, -1022)
+ , fragmentGlyphs =
+ []
+ }
+ , Fragment
{ fragmentUserData = "smallText"
, fragmentLine = 5
, fragmentAncestorBoxes =
@@ 218,12 258,22 @@ ParagraphLayout
]
}
, Fragment
+ { fragmentUserData = "lineBreak"
+ , fragmentLine = 5
+ , fragmentAncestorBoxes =
+ []
+ , fragmentRect = Rect {x_origin = 2375, y_origin = -6400, x_size = 0, y_size = -1300}
+ , fragmentPen = (0, -1022)
+ , fragmentGlyphs =
+ []
+ }
+ , Fragment
{ fragmentUserData = "largeText"
, fragmentLine = 6
, fragmentAncestorBoxes =
[ AncestorBox {boxUserData = "largeBox", boxLeftEdge = SpacedEdge 0, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = SpacedEdge 0}
]
- , fragmentRect = Rect {x_origin = 0, y_origin = -7200, x_size = 2318, y_size = -1700}
+ , fragmentRect = Rect {x_origin = 0, y_origin = -7700, x_size = 2318, y_size = -1700}
, fragmentPen = (0, -1222)
, fragmentGlyphs =
[ (GlyphInfo {codepoint = 79, cluster = 95, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 273, y_advance = 0, x_offset = 0, y_offset = 0})
M CHANGELOG.md => CHANGELOG.md +4 -0
@@ 14,6 14,10 @@
* Added function `paragraphSafeWidth` to help calculating max-content width
for CSS.
+* Trimmed white space at the beginning and end of lines may now generate
+ fragments with zero glyphs, to more closely match CSS behaviour.
+ These fragments may affect line height and/or the start edge of boxes.
+
* Text will now overflow the end edge of the paragraph according to the
text direction of the root box, instead of always overflowing the right edge.
M src/Data/Text/ParagraphLayout/Internal/Layout.hs => src/Data/Text/ParagraphLayout/Internal/Layout.hs +26 -31
@@ 10,7 10,7 @@ import Data.Int (Int32)
import Data.List (mapAccumL)
import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty, (<|))
import qualified Data.List.NonEmpty as NonEmpty
-import Data.Maybe (catMaybes, fromMaybe)
+import Data.Maybe (fromMaybe)
import Data.Text.Foreign (lengthWord8)
import Data.Text.Glyphize
( Buffer (..)
@@ 64,30 64,26 @@ layoutAndAlignLines
-> [FragmentWithSpan d]
layoutAndAlignLines dir align maxWidth runs = frags
where
- frags = concatMap NonEmpty.toList fragsInLines
+ frags = concatMap toList fragsInLines
(_, fragsInLines) = mapAccumL positionLine originY numberedLines
positionLine = positionLineH dir align maxWidth
numberedLines = zip [1 ..] canonicalLines
canonicalLines = fmap reorderProtoFragments visibleLines
visibleLines = filter PL.visible logicalLines
- logicalLines = nonEmptyItems $ layoutLines maxWidth [] runs
+ logicalLines = toList $ layoutLines maxWidth [] runs
originY = paragraphOriginY
reorderProtoFragments :: PL.ProtoLine NonEmpty d -> PL.ProtoLine NonEmpty d
reorderProtoFragments pl@(PL.ProtoLine { PL.protoFragments = pfs }) =
pl { PL.protoFragments = reorder pfs }
-nonEmptyItems :: Foldable t =>
- t (PL.ProtoLine [] d) -> [PL.ProtoLine NonEmpty d]
-nonEmptyItems = catMaybes . map PL.nonEmpty . toList
-
-- | Create a multi-line layout from the given runs, splitting them as
-- necessary to fit within the requested line width.
--
-- The output is a two-dimensional list of fragments positioned along the
-- horizontal axis.
layoutLines :: Int32 -> [RB.ResolvedBox d] -> NonEmpty (WithSpan d Run) ->
- NonEmpty (PL.ProtoLine [] d)
+ NonEmpty (PL.ProtoLine NonEmpty d)
layoutLines maxWidth openBoxes runs = case nonEmpty rest of
-- Everything fits. We are done.
Nothing -> fitting :| []
@@ 95,10 91,9 @@ layoutLines maxWidth openBoxes runs = case nonEmpty rest of
Just runs' -> fitting <| layoutLines maxWidth openBoxes' runs'
where
(fitting, rest) = layoutAndWrapRunsH maxWidth openBoxes runs
- -- Update the list of open boxes using the logically last run on this
- -- line, if there is one.
- openBoxes' =
- openBoxes `fromMaybe` lastSpanBoxes (PL.protoFragments fitting)
+ -- Update the list of open boxes using the logically last run
+ -- on this line.
+ openBoxes' = lastSpanBoxes $ PL.protoFragments fitting
-- | Position all the given horizontal fragments on the same line,
-- using @originY@ as its top edge, and return the bottom edge for continuation.
@@ 231,7 226,7 @@ layoutAndWrapRunsH
:: Int32
-> [RB.ResolvedBox d]
-> NonEmpty (WithSpan d Run)
- -> (PL.ProtoLine [] d, [WithSpan d Run])
+ -> (PL.ProtoLine NonEmpty d, [WithSpan d Run])
layoutAndWrapRunsH maxWidth prevOpenBoxes runs = NonEmpty.head $ validProtoLines
where
validProtoLines = dropWhile1 tooLong layouts
@@ 250,9 245,9 @@ layoutAndWrapRunsH maxWidth prevOpenBoxes runs = NonEmpty.head $ validProtoLines
-- to determine `PL.nextOpenBoxes`.
protoLine
:: [RB.ResolvedBox d]
- -> [ProtoFragmentWithSpan d]
+ -> NonEmpty (ProtoFragmentWithSpan d)
-> [WithSpan d Run]
- -> PL.ProtoLine [] d
+ -> PL.ProtoLine NonEmpty d
protoLine prev pfs rest = PL.ProtoLine pfs prev next
where
next = [] `fromMaybe` firstSpanBoxes rest
@@ 262,10 257,9 @@ firstSpanBoxes xs = case xs of
[] -> Nothing
(WithSpan rs _) : _ -> Just $ RS.spanBoxes rs
-lastSpanBoxes :: [WithSpan d a] -> Maybe [RB.ResolvedBox d]
-lastSpanBoxes xs = case reverse xs of
- [] -> Nothing
- (WithSpan rs _) : _ -> Just $ RS.spanBoxes rs
+lastSpanBoxes :: NonEmpty (WithSpan d a) -> [RB.ResolvedBox d]
+lastSpanBoxes xs = case NonEmpty.last xs of
+ WithSpan rs _ -> RS.spanBoxes rs
-- | Treat a list of runs as a contiguous sequence, and split them into two
-- lists so that the first list contains as many non-whitespace characters as
@@ 281,21 275,20 @@ lastSpanBoxes xs = case reverse xs of
--
-- If there is no hard line break in the input, the first output list will
-- contain the whole input, and the second output list will be empty.
-hardSplit :: NonEmpty (WithSpan d Run) -> ([WithSpan d Run], [WithSpan d Run])
+hardSplit :: NonEmpty (WithSpan d Run) ->
+ (NonEmpty (WithSpan d Run), [WithSpan d Run])
hardSplit runs = case reverse hSplits of
[] -> noSplit
(splitRuns : _) -> forcedSplit splitRuns
where
- noSplit =
- (NonEmpty.toList (trim runs), [])
- forcedSplit (runs1, runs2) =
- (NonEmpty.toList $ markHard $ trim runs1, runs2)
+ noSplit = (trim runs, [])
+ forcedSplit (runs1, runs2) = (markHard $ trim runs1, runs2)
markHard = mapLast markHard'
markHard' (WithSpan rs x) = WithSpan rs x { runHardBreak = True }
trim
- = trimTextsStartPreserve isStartSpace
- . trimTextsEndPreserve isEndSpace
- . trimTextsEndPreserve isNewline
+ = dropWhileStartCascade isStartSpace
+ . dropWhileEndCascade isEndSpace
+ . dropWhileEndCascade isNewline
-- TODO: Consider optimising.
-- We do not need to look for any line breaks further than the
-- shortest hard break.
@@ 323,11 316,13 @@ mapLast f xs = case NonEmpty.uncons xs of
-- closer to the start of the list are preferred for line breaking, but without
-- considering overflows.
softSplits :: NonEmpty (WithSpan d Run) ->
- [([WithSpan d Run], [WithSpan d Run])]
+ [(NonEmpty (WithSpan d Run), [WithSpan d Run])]
softSplits runs = map (allowSndEmpty . trimFst) splits
where
trimFst (runs1, runs2) = (trim runs1, runs2)
- trim = trimTextsStart isStartSpace . trimTextsEnd isEndSpace
+ trim
+ = dropWhileStartCascade isStartSpace
+ . dropWhileEndCascade isEndSpace
splits = lSplits ++ cSplits
lSplits = nonEmptyPairs $
splitTextsBy (map fst . runLineBreaks) runs
@@ 349,8 344,8 @@ dropWhile1 p list = case NonEmpty.uncons list of
-- | Calculate layout for multiple horizontal runs on the same line, without
-- any breaking.
-layoutRunsH :: [WithSpan d Run] -> [ProtoFragmentWithSpan d]
-layoutRunsH runs = map layoutRunH runs
+layoutRunsH :: Functor f => f (WithSpan d Run) -> f (ProtoFragmentWithSpan d)
+layoutRunsH runs = fmap layoutRunH runs
-- | Calculate layout for the given horizontal run and attach extra information.
layoutRunH :: WithSpan d Run -> ProtoFragmentWithSpan d
M src/Data/Text/ParagraphLayout/Internal/TextContainer.hs => src/Data/Text/ParagraphLayout/Internal/TextContainer.hs +38 -59
@@ 2,20 2,18 @@ module Data.Text.ParagraphLayout.Internal.TextContainer
( SeparableTextContainer
, TextContainer
, dropWhileEnd
+ , dropWhileEndCascade
, dropWhileStart
+ , dropWhileStartCascade
, getText
, splitTextAt8
, splitTextsBy
- , trimTextsEnd
- , trimTextsEndPreserve
- , trimTextsStart
- , trimTextsStartPreserve
)
where
import Data.Foldable (toList)
-import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty)
-import qualified Data.List.NonEmpty as NonEmpty
+import Data.List (mapAccumL, mapAccumR)
+import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Text.Foreign (dropWord8, takeWord8)
@@ 88,61 86,42 @@ collapse (tc :| tcs)
-- | Treat a list of text containers as a contiguous sequence,
-- and remove a prefix of characters that match the given predicate.
--
--- Empty text containers are removed from the output, so the result may
--- potentially be an empty list.
-trimTextsStart :: (SeparableTextContainer a, Foldable f) =>
- (Char -> Bool) -> f a -> [a]
-trimTextsStart p tcs = trimTextsStart' p $ toList tcs
-
--- | Treat a list of text containers as a contiguous sequence,
--- and remove a prefix of characters that match the given predicate.
---
--- Empty text containers are removed from the output except the first one,
--- which is instead truncated to zero length.
-trimTextsStartPreserve :: SeparableTextContainer a =>
- (Char -> Bool) -> NonEmpty a -> NonEmpty a
-trimTextsStartPreserve p tcs =
- case nonEmpty $ trimTextsStart p $ NonEmpty.toList tcs of
- Nothing -> truncateText (NonEmpty.head tcs) :| []
- Just out -> out
+-- All text containers are preserved but their contents may end up having
+-- zero length.
+dropWhileStartCascade :: (SeparableTextContainer a, Traversable t) =>
+ (Char -> Bool) -> t a -> t a
+dropWhileStartCascade p tcs = trimTextsStartCascade (dropWhileStart p) tcs
-- | Treat a list of text containers as a contiguous sequence,
-- and remove a suffix of characters that match the given predicate.
--
--- Empty text containers are removed from the output, so the result may
--- potentially be an empty list.
-trimTextsEnd :: (SeparableTextContainer a, Foldable f) =>
- (Char -> Bool) -> f a -> [a]
-trimTextsEnd p tcs = trimTextsEnd' p $ reverse $ toList tcs
-
--- | Treat a list of text containers as a contiguous sequence,
--- and remove a suffix of characters that match the given predicate.
---
--- Empty text containers are removed from the output except the first one,
--- which is instead truncated to zero length.
-trimTextsEndPreserve :: SeparableTextContainer a =>
- (Char -> Bool) -> NonEmpty a -> NonEmpty a
-trimTextsEndPreserve p tcs =
- case nonEmpty $ trimTextsEnd p $ NonEmpty.toList tcs of
- Nothing -> truncateText (NonEmpty.head tcs) :| []
- Just out -> out
-
-trimTextsStart' :: SeparableTextContainer a => (Char -> Bool) -> [a] -> [a]
-trimTextsStart' _ [] = []
-trimTextsStart' p (tc : tcs)
- | Text.null (getText trimmed) = trimTextsStart' p tcs
- | otherwise = trimmed : tcs
+-- All text containers are preserved but their contents may end up having
+-- zero length.
+dropWhileEndCascade :: (SeparableTextContainer a, Traversable t) =>
+ (Char -> Bool) -> t a -> t a
+dropWhileEndCascade p tcs = trimTextsEndCascade (dropWhileEnd p) tcs
+
+-- | Traverse the given structure from start to end, applying the given
+-- text trimming function to each text container until a non-empty container
+-- is produced.
+trimTextsStartCascade :: (SeparableTextContainer a, Traversable t) =>
+ (a -> a) -> t a -> t a
+trimTextsStartCascade trimFunc tcs =
+ snd $ mapAccumL (cascadingTrim trimFunc) True tcs
+
+-- | Traverse the given structure from end to start, applying the given
+-- text trimming function to each text container until a non-empty container
+-- is produced.
+trimTextsEndCascade :: (SeparableTextContainer a, Traversable t) =>
+ (a -> a) -> t a -> t a
+trimTextsEndCascade trimFunc tcs =
+ snd $ mapAccumR (cascadingTrim trimFunc) True tcs
+
+-- | Wraps a text trimming function in a controlled cascade.
+-- When the trim produces an empty text, the cascade continues.
+cascadingTrim :: SeparableTextContainer a => (a -> a) -> Bool -> a -> (Bool, a)
+cascadingTrim _ False tc = (False, tc)
+cascadingTrim trimFunc True tc = (continue, trimmed)
where
- trimmed = dropWhileStart p tc
-
-trimTextsEnd' :: SeparableTextContainer a => (Char -> Bool) -> [a] -> [a]
-trimTextsEnd' _ [] = []
-trimTextsEnd' p (tc : tcs)
- | Text.null (getText trimmed) = trimTextsEnd' p tcs
- | otherwise = reverse $ trimmed : tcs
- where
- trimmed = dropWhileEnd p tc
-
--- | Discard all text from the container by creating a prefix of length 0.
-truncateText :: SeparableTextContainer a => a -> a
-truncateText tc = fst $ splitTextAt8 0 tc
+ trimmed = trimFunc tc
+ continue = Text.null $ getText trimmed
M test/Data/Text/ParagraphLayout/Internal/TextContainerSpec.hs => test/Data/Text/ParagraphLayout/Internal/TextContainerSpec.hs +71 -15
@@ 87,7 87,9 @@ isSpace = (== ' ')
spec :: Spec
spec = do
+
describe "splitTextsBy" $ do
+
it "splits example text containers (start bias)" $ do
splitTextsBy startBiasedBreakPoints exampleContainers `shouldBe`
[ ( [ contain "Vikipedija " 10, contain "(Википеди" 21 ]
@@ 121,6 123,7 @@ spec = do
, [ contain "Vikipedija " 10, contain "(Википедија)" 21 ]
)
]
+
it "splits example text containers (end bias)" $ do
splitTextsBy endBiasedBreakPoints exampleContainers `shouldBe`
[ ( [ contain "Vikipedija " 10, contain "(Википедија)" 21 ]
@@ 154,30 157,83 @@ spec = do
, [ contain "kipedija " 12, contain "(Википедија)" 21 ]
)
]
- describe "trimTextsEnd" $ do
+
+ describe "dropWhileStartCascade" $ do
+
describe "isSpace" $ do
+
it "does nothing on an empty list" $ do
let inputTexts = [] :: [Text]
- trimTextsEnd isSpace inputTexts `shouldBe` inputTexts
- it "does nothing when last run does not end with space" $ do
+ dropWhileStartCascade isSpace inputTexts `shouldBe` inputTexts
+
+ it "does nothing on list of empty texts" $ do
+ let inputTexts = [empty, empty, empty]
+ dropWhileStartCascade isSpace inputTexts `shouldBe` inputTexts
+
+ it "does nothing when first run does not start with space" $ do
let inputTexts = [pack "some ", pack "text"]
- trimTextsEnd isSpace inputTexts `shouldBe` inputTexts
- it "trims empty texts down to an empty list" $ do
+ dropWhileStartCascade isSpace inputTexts `shouldBe` inputTexts
+
+ it "does nothing when first non-empty run does not start with space" $ do
+ let inputTexts = [empty, empty, pack "some ", pack "text"]
+ dropWhileStartCascade isSpace inputTexts `shouldBe` inputTexts
+
+ it "trims spaces from first text" $ do
+ let inputTexts = [pack " some ", pack "text"]
+ dropWhileStartCascade isSpace inputTexts `shouldBe`
+ [pack "some ", pack "text"]
+
+ it "trims texts containing only spaces to empty" $ do
+ let inputTexts = [pack " ", pack "some ", pack "text"]
+ dropWhileStartCascade isSpace inputTexts `shouldBe`
+ [empty, pack "some ", pack "text"]
+
+ it "trims first text that contains non-spaces" $ do
+ let inputTexts = [pack " ", pack " some ", pack "text "]
+ dropWhileStartCascade isSpace inputTexts `shouldBe`
+ [empty, pack "some ", pack "text "]
+
+ it "trims space-only input down to empty texts" $ do
+ let inputTexts = [pack " ", pack " ", pack " "]
+ dropWhileStartCascade isSpace inputTexts `shouldBe`
+ [empty, empty, empty]
+
+ describe "dropWhileEndCascade" $ do
+
+ describe "isSpace" $ do
+
+ it "does nothing on an empty list" $ do
+ let inputTexts = [] :: [Text]
+ dropWhileEndCascade isSpace inputTexts `shouldBe` inputTexts
+
+ it "does nothing on list of empty texts" $ do
let inputTexts = [empty, empty, empty]
- trimTextsEnd isSpace inputTexts `shouldBe` []
- it "trims empty texts from a list" $ do
+ dropWhileEndCascade isSpace inputTexts `shouldBe` inputTexts
+
+ it "does nothing when last run does not end with space" $ do
+ let inputTexts = [pack "some ", pack "text"]
+ dropWhileEndCascade isSpace inputTexts `shouldBe` inputTexts
+
+ it "does nothing when last non-empty run does not end with space" $ do
let inputTexts = [pack "some ", pack "text", empty, empty]
- trimTextsEnd isSpace inputTexts `shouldBe`
- [pack "some ", pack "text"]
+ dropWhileEndCascade isSpace inputTexts `shouldBe` inputTexts
+
it "trims spaces from last text" $ do
let inputTexts = [pack "some ", pack "text "]
- trimTextsEnd isSpace inputTexts `shouldBe`
+ dropWhileEndCascade isSpace inputTexts `shouldBe`
[pack "some ", pack "text"]
- it "trims texts containing only spaces" $ do
+
+ it "trims texts containing only spaces to empty" $ do
let inputTexts = [pack "some ", pack "text", pack " "]
- trimTextsEnd isSpace inputTexts `shouldBe`
- [pack "some ", pack "text"]
+ dropWhileEndCascade isSpace inputTexts `shouldBe`
+ [pack "some ", pack "text", empty]
+
it "trims last text that contains non-spaces" $ do
let inputTexts = [pack "some ", pack "text ", pack " "]
- trimTextsEnd isSpace inputTexts `shouldBe`
- [pack "some ", pack "text"]
+ dropWhileEndCascade isSpace inputTexts `shouldBe`
+ [pack "some ", pack "text", empty]
+
+ it "trims space-only input down to empty texts" $ do
+ let inputTexts = [pack " ", pack " ", pack " "]
+ dropWhileEndCascade isSpace inputTexts `shouldBe`
+ [empty, empty, empty]