~jaro/balkon

ref: b464ad93704970f027c06d6915a07bf3d8011845 balkon/src/Data/Text/ParagraphLayout/Internal/Rich/Paragraph.hs -rw-r--r-- 4.6 KiB
b464ad93Jaro Separate split list functions into a module. 1 year, 5 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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
module Data.Text.ParagraphLayout.Internal.Rich.Paragraph
    ( Paragraph (..)
    , constructParagraph
    , paragraphPrefix
    , paragraphSpanBounds
    , paragraphSpanTexts
    , paragraphText
    )
where

import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Text.Array (Array)
import Data.Text.Internal (Text (Text))

import Data.Text.ParagraphLayout.Internal.ParagraphOptions
import Data.Text.ParagraphLayout.Internal.Tree

-- | Text to be laid out as a single paragraph.
--
-- May be divided into a hierarchy of boxes and spans.
--
-- The input text must be encoded as UTF-8 in a contiguous byte array.
--
-- You may need to use "Data.Text.Internal" in order to determine the byte
-- array and the necessary offsets to construct the paragraph without copying
-- data.
--
-- Alternatively, you can use `constructParagraph` with piecewise text.
data Paragraph d = Paragraph

    Array
    -- ^ A byte array containing the whole text to be laid out, in UTF-8.
    --
    -- This array will be passed to "Data.Text.Glyphize", which passes it to
    -- [@hb_buffer_add_utf8()@]
    -- (https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-buffer-add-utf8).
    --
    -- In the output, `Data.Text.Glyphize.cluster` will be a byte offset of
    -- the corresponding input character from this array.

    Int
    -- ^ Byte offset of the first text node from the start of the byte array.
    -- Any characters preceding this offset will not be shaped, but may still
    -- be used to influence the shape of neighbouring characters.

    (RootNode Int d)
    -- ^ Parts of the text to be laid out, represented as a tree.
    -- The in-order walk of this tree corresponds to the logical order
    -- of the text.

    ParagraphOptions
    -- ^ Options applying to the paragraph as a whole.

-- | Construct a `Paragraph` from a tree containing individual pieces of text,
-- plus an optional prefix and suffix, all encoded as UTF-8.
--
-- This internally concatenates all input text into one contiguous byte array.
--
-- Alternatively, you can construct a `Paragraph` directly to avoid copying
-- data.
constructParagraph
    :: Text
    -- ^ Optional prefix. Will not be laid out but may affect the output.
    -> RootNode Text d
    -- ^ Parts of the text to be laid out, represented as a tree.
    -> Text
    -- ^ Optional suffix. Will not be laid out but may affect the output.
    -> ParagraphOptions
    -- ^ Options to apply to the paragraph as a whole.
    -> Paragraph d
    -- ^ Constructed paragraph that can be passed to
    -- `Data.Text.ParagraphLayout.Rich.layoutRich`.
constructParagraph prefix root suffix = Paragraph arr afterPrefix root'
    where
        (Text arr beforePrefix _) = txt
        afterPrefix = beforePrefix + prefixLen
        (txt, prefixLen, root') = glue prefix root suffix

-- | Calculate the offsets into the `Paragraph`'s underlying `Data.Text.Array`
-- where each text node starts and ends, in ascending order. The resulting list
-- will be one larger than the number of text nodes in the input.
--
-- You can use this function to verify that Balkón will slice the input text
-- correctly.
paragraphSpanBounds :: Paragraph d -> NonEmpty Int
paragraphSpanBounds (Paragraph _ initialOffset root _) =
    -- TODO: Consider adding checks for array bounds.
    NonEmpty.scanl (+) initialOffset $ map len $ flatten root
    where
        len (TextLeaf _ l _ _) = l

-- | Turn each text node from the input `Paragraph` into a `Text`.
--
-- You can use this function to verify that Balkón will slice the input text
-- correctly.
paragraphSpanTexts :: Paragraph d -> [Text]
paragraphSpanTexts p@(Paragraph arr _ _ _) = zipWith toText sStarts sEnds
    where
        toText start end = Text arr start (end - start)
        sStarts = NonEmpty.init sBounds
        sEnds = NonEmpty.tail sBounds
        sBounds = paragraphSpanBounds p

-- | Turn all text nodes from the input `Paragraph` into one combined `Text`.
--
-- You can use this function to verify that Balkón will slice the input text
-- correctly.
paragraphText :: Paragraph d -> Text
paragraphText p@(Paragraph arr _ _ _) = Text arr start (end - start)
    where
        start = NonEmpty.head sBounds
        end = NonEmpty.last sBounds
        sBounds = paragraphSpanBounds p

-- | Turn all text nodes from the input `Paragraph` up to the given end offset
-- into one combined `Text`. No bounds checking is performed!
paragraphPrefix :: Paragraph d -> Int -> Text
paragraphPrefix p@(Paragraph arr _ _ _) end = Text arr start (end - start)
    where
        start = NonEmpty.head sBounds
        sBounds = paragraphSpanBounds p