M CHANGELOG.md => CHANGELOG.md +2 -0
@@ 4,6 4,8 @@
* Added support for forced (hard) line breaks in the input text.
+* Now also trimming white space at the beginning of lines.
+
* Internally, language tags will be cut at the first invalid character before
being passed to ICU.
M src/Data/Text/ParagraphLayout/Internal/Plain.hs => src/Data/Text/ParagraphLayout/Internal/Plain.hs +10 -2
@@ 179,7 179,10 @@ hardSplit :: [WithSpan Run] -> ([WithSpan Run], [WithSpan Run])
hardSplit runs = trimFst $ NonEmpty.last $ splits
where
trimFst (runs1, runs2) = (trim runs1, runs2)
- trim = trimTextsEndPreserve isEndSpace . trimTextsEndPreserve isNewline
+ trim
+ = trimTextsStartPreserve isStartSpace
+ . trimTextsEndPreserve isEndSpace
+ . trimTextsEndPreserve isNewline
-- TODO: Consider optimising.
-- We do not need to look for any line breaks further than the
-- shortest hard break.
@@ 205,7 208,7 @@ softSplits :: [WithSpan Run] -> [([WithSpan Run], [WithSpan Run])]
softSplits runs = map trimFst splits
where
trimFst (runs1, runs2) = (trim runs1, runs2)
- trim = trimTextsEnd isEndSpace
+ trim = trimTextsStart isStartSpace . trimTextsEnd isEndSpace
splits = lSplits ++ cSplits
lSplits = splitTextsBy (map fst . runLineBreaks) runs
-- TODO: Consider optimising.
@@ 315,6 318,11 @@ runBreaksFromSpan run spanBreaks =
valid (off, _) = off < runLength
runLength = lengthWord8 $ getText run
+-- | Predicate for characters that can be potentially removed from the
+-- beginning of a line according to the CSS Text Module.
+isStartSpace :: Char -> Bool
+isStartSpace c = c `elem` [' ', '\t']
+
-- | Predicate for characters that can be potentially removed from the end of
-- a line according to the CSS Text Module.
isEndSpace :: Char -> Bool
M src/Data/Text/ParagraphLayout/Internal/ResolvedSpan.hs => src/Data/Text/ParagraphLayout/Internal/ResolvedSpan.hs +1 -0
@@ 45,6 45,7 @@ instance TextContainer a => TextContainer (WithSpan a) where
instance SeparableTextContainer a => SeparableTextContainer (WithSpan a) where
splitTextAt8 n (WithSpan rs c) = (WithSpan rs c1, WithSpan rs c2)
where (c1, c2) = splitTextAt8 n c
+ dropWhileStart p (WithSpan rs c) = WithSpan rs (dropWhileStart p c)
dropWhileEnd p (WithSpan rs c) = WithSpan rs (dropWhileEnd p c)
splitBySpanIndex :: [WithSpan a] -> [[a]]
M src/Data/Text/ParagraphLayout/Internal/Run.hs => src/Data/Text/ParagraphLayout/Internal/Run.hs +8 -0
@@ 40,6 40,14 @@ instance SeparableTextContainer Run where
t1 = takeWord8 (fromIntegral n) t
t2 = dropWord8 (fromIntegral n) t
t = getText r
+ dropWhileStart p r = r { runText = t', runOffsetInSpan = o' }
+ where
+ t = runText r
+ t' = Text.dropWhile p t
+ l = lengthWord8 t
+ l' = lengthWord8 t'
+ o = runOffsetInSpan r
+ o' = o + l - l'
dropWhileEnd p r = r { runText = Text.dropWhileEnd p (runText r) }
type ProtoRun = (Zipper, Maybe Direction, ScriptCode)
M src/Data/Text/ParagraphLayout/Internal/TextContainer.hs => src/Data/Text/ParagraphLayout/Internal/TextContainer.hs +38 -2
@@ 2,11 2,14 @@ module Data.Text.ParagraphLayout.Internal.TextContainer
(SeparableTextContainer
,TextContainer
,dropWhileEnd
+ ,dropWhileStart
,getText
,splitTextAt8
,splitTextsBy
,trimTextsEnd
,trimTextsEndPreserve
+ ,trimTextsStart
+ ,trimTextsStartPreserve
)
where
@@ 33,8 36,12 @@ class TextContainer a => SeparableTextContainer a where
-- constraints the instance requires.
splitTextAt8 :: Int -> a -> (a, a)
- -- | Return the prefix remaining after dropping characters that satisfy
- -- the given predicate from the end of the given `SeparableTextContainer`.
+ -- | Return the suffix remaining after dropping characters that satisfy the
+ -- given predicate from the beginning of the given `SeparableTextContainer`.
+ dropWhileStart :: (Char -> Bool) -> a -> a
+
+ -- | Return the prefix remaining after dropping characters that satisfy the
+ -- given predicate from the end of the given `SeparableTextContainer`.
dropWhileEnd :: (Char -> Bool) -> a -> a
-- | As a trivial instance, each `Text` can be split directly.
@@ 43,6 50,7 @@ instance SeparableTextContainer Text where
where
t1 = takeWord8 (fromIntegral n) t
t2 = dropWord8 (fromIntegral n) t
+ dropWhileStart = Text.dropWhile
dropWhileEnd = Text.dropWhileEnd
-- | Treat a list of text containers as a contiguous sequence,
@@ 77,6 85,26 @@ collapse (tc :| tcs)
| otherwise = 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 => (Char -> Bool) -> [a] -> [a]
+trimTextsStart p tcs = trimTextsStart' p 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) -> [a] -> [a]
+trimTextsStartPreserve _ [] = []
+trimTextsStartPreserve p ins@(in1:_) = case trimTextsStart' p ins of
+ [] -> [truncateText in1]
+ out -> out
+
+-- | 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
@@ 96,6 124,14 @@ trimTextsEndPreserve p ins@(in1:_) = case trimTextsEnd' p (reverse ins) of
[] -> [truncateText in1]
out -> out
+trimTextsStart' :: SeparableTextContainer a => (Char -> Bool) -> [a] -> [a]
+trimTextsStart' _ [] = []
+trimTextsStart' p (tc:tcs)
+ | Text.null (getText trimmed) = trimTextsStart' p tcs
+ | otherwise = trimmed:tcs
+ where
+ trimmed = dropWhileStart p tc
+
trimTextsEnd' :: SeparableTextContainer a => (Char -> Bool) -> [a] -> [a]
trimTextsEnd' _ [] = []
trimTextsEnd' p (tc:tcs)
M test/Data/Text/ParagraphLayout/Internal/TextContainerSpec.hs => test/Data/Text/ParagraphLayout/Internal/TextContainerSpec.hs +6 -0
@@ 21,6 21,12 @@ instance SeparableTextContainer ExampleContainer where
(t1, t2) = splitTextAt8 n t
o1 = o
o2 = o + lengthWord8 t1
+ dropWhileStart p (Contain t o) = Contain t' o'
+ where
+ l = lengthWord8 t
+ t' = dropWhileStart p t
+ l' = lengthWord8 t
+ o' = o + l - l'
dropWhileEnd p (Contain t o) = Contain (dropWhileEnd p t) o
exampleContainers :: [ExampleContainer]