~jaro/balkon

ref: 09dd2f080bc54db671c518ebb0d546e5ef89c379 balkon/src/Data/Text/ParagraphLayout/Internal/Paragraph.hs -rw-r--r-- 4.5 KiB
09dd2f08Jaro Improve test readability with runIO. 1 year, 7 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
123
124
125
126
127
128
module Data.Text.ParagraphLayout.Internal.Paragraph
    (Paragraph(..)
    ,ParagraphLayout(..)
    ,ParagraphOptions(..)
    ,paragraphLayout
    ,paragraphOriginX
    ,paragraphOriginY
    ,paragraphSpanBounds
    ,shapedRuns
    )
where

import Data.Int (Int32)
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Text.Array (Array)
import Data.Text.Glyphize (Font)

import Data.Text.ParagraphLayout.Internal.Fragment
import Data.Text.ParagraphLayout.Internal.LineHeight
import Data.Text.ParagraphLayout.Internal.Rect
import Data.Text.ParagraphLayout.Internal.Span

-- | Text to be laid out as a single paragraph.
--
-- May be divided into any number of neighbouring spans, each of which will
-- be represented as a separate `SpanLayout` in the resulting layout.
--
-- 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.
--
-- For simple use cases, it may be sufficient to construct paragraphs using
-- [ParagraphConstruction]("Data.Text.ParagraphLayout.ParagraphConstruction").
data Paragraph = 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 span 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.

    [Span]
    -- ^ Parts of the text to be laid out, in logical order.
    -- The initial offset plus total length of all spans must not exceed
    -- the bounds of the byte array.
    -- Any characters following the last span will not be shaped, but may still
    -- be used to influence the shape of neighbouring characters.

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

data ParagraphOptions = ParagraphOptions

    { paragraphFont :: Font
    -- ^ Font to be used for shaping and measurement.
    -- Make sure to set its scale (see `Data.Text.Glyphize.optionScale`) using
    -- the same units that you want in the output.

    , paragraphLineHeight :: LineHeight
    -- ^ Preferred line height of the resulting box fragments.

    , paragraphMaxWidth :: Int32
    -- ^ Line width at which line breaking should occur.
    -- Lines will be broken at language-appropriate boundaries.
    -- If a line still exceeds this limit then, it will be broken at character
    -- boundaries, and if it already consists of a single cluster that cannot
    -- be further broken down, it will overflow.

    }
    deriving (Eq, Show)

-- | The resulting layout of the whole paragraph.
data ParagraphLayout = ParagraphLayout
    { paragraphRect :: Rect Int32
    -- ^ The containing block (CSS3).
    , spanLayouts :: [SpanLayout]
    }
    deriving (Eq, Read, Show)

-- | Calculate the offsets into the `Paragraph`'s underlying `Array` where each
-- span starts and ends, in ascending order. The resulting list will be one
-- larger than the list of input spans.
paragraphSpanBounds :: Paragraph -> NonEmpty Int
paragraphSpanBounds (Paragraph _ initialOffset spans _) =
    NonEmpty.scanl (+) initialOffset (map spanLength spans)

paragraphOriginX :: (Num a) => a
paragraphOriginX = 0

paragraphOriginY :: (Num a) => a
paragraphOriginY = 0

empty :: (Num a) => Rect a
empty = Rect
    { x_origin = paragraphOriginX
    , y_origin = paragraphOriginY
    , x_size = 0
    , y_size = 0
    }

containRects :: (Ord a, Num a) => [Rect a] -> Rect a
containRects = foldr union empty

-- | Wrap the given `SpanLayout`s and compute their containing rectangle.
paragraphLayout :: [SpanLayout] -> ParagraphLayout
paragraphLayout sls = ParagraphLayout pRect sls
    where pRect = containRects $ concat $ map spanRects sls

-- | Return all fragments of shaped text in one flat list,
-- discarding information about their associated spans.
paragraphFragments :: ParagraphLayout -> [Fragment]
paragraphFragments pl = concat $ map spanFragments $ spanLayouts pl

-- | Return all shaped runs in the paragraph.
shapedRuns :: ParagraphLayout -> [ShapedRun]
shapedRuns pl = map shapedRun $ paragraphFragments pl