~jaro/balkon

ref: 7f7bfdd8094bf942ed135a210c7485552b69d015 balkon/src/Data/Text/ParagraphLayout/Internal/TextContainer.hs -rw-r--r-- 3.1 KiB
7f7bfdd8Jaro Test forming and breaking ligatures. 1 year, 8 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
module Data.Text.ParagraphLayout.Internal.TextContainer
    (SeparableTextContainer
    ,TextContainer
    ,collapse
    ,dropWhileEnd
    ,getText
    ,splitTextAt8
    ,splitTextsAt8
    ,trimTextsEnd
    )
where

import Data.List.NonEmpty (NonEmpty((:|)))
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Text.Foreign (dropWord8, lengthWord8, takeWord8)

-- | Class of data types containing `Text` that can be accessed.
class TextContainer a where
    -- | Extract a `Text` from its container.
    getText :: a -> Text

-- | As a trivial instance, each `Text` contains itself.
instance TextContainer Text where
    getText = id

-- | Class of data types containing `Text` that can be split at a given number
-- of `Data.Word.Word8` units from the start of the text.
class TextContainer a => SeparableTextContainer a where

    -- | Split the given `SeparableTextContainer` at the given number of
    -- `Data.Word.Word8` units from the start of the text, preserving whatever
    -- 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`.
    dropWhileEnd :: (Char -> Bool) -> a -> a

-- | As a trivial instance, each `Text` can be split directly.
instance SeparableTextContainer Text where
    splitTextAt8 n t = (t1, t2)
        where
            t1 = takeWord8 (fromIntegral n) t
            t2 = dropWord8 (fromIntegral n) t
    dropWhileEnd = Text.dropWhileEnd

-- | Treat a list of text containers as a contiguous sequence,
-- and make a split at the given number of `Data.Word.Word8` from the beginning
-- of this sequence.
--
-- If @n@ falls on a container boundary, the total number of output containers
-- will equal the number of input containers; otherwise, it will be one larger.
splitTextsAt8 :: SeparableTextContainer a => Int -> [a] -> ([a], [a])
splitTextsAt8 n rs = (pre, post)
    where
        pre = reverse rpre
        (rpre, post) = splitTextsAt8' n [] rs

splitTextsAt8' :: SeparableTextContainer a => Int -> [a] -> [a] -> ([a], [a])
splitTextsAt8' _ rpre [] = (rpre, [])
splitTextsAt8' n rpre (r:rs)
    | n <= 0 = (rpre, r:rs)
    | n >= l = splitTextsAt8' (n - l) (r:rpre) (rs)
    | otherwise = let (r1, r2) = splitTextAt8 n r in (r1:rpre, r2:rs)
    where
        l = lengthWord8 $ getText r

-- | If the first container in the list is empty, remove it.
collapse :: SeparableTextContainer a => NonEmpty a -> [a]
collapse (x :| xs)
    | Text.null (getText x) = xs
    | otherwise = x:xs

-- | Treat a list of text containers as a contiguous sequence,
-- and remove a suffix of characters that match the given predicate.
trimTextsEnd :: SeparableTextContainer a => (Char -> Bool) -> [a] -> [a]
trimTextsEnd p tcs = trimTextsEnd' p (reverse tcs)

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