~jaro/balkon

ref: a4ff73944e8ba02f82dbfa8c7aaff20ac438ba2c balkon/src/Data/Text/ParagraphLayout/Internal/Paragraph.hs -rw-r--r-- 4.1 KiB
a4ff7394Jaro Remove (~) operator from ParagraphConstruction. 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
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
module Data.Text.ParagraphLayout.Internal.Paragraph
    (Paragraph(..)
    ,ParagraphLayout(..)
    ,ParagraphOptions(..)
    ,paragraphLayout
    ,paragraphOriginX
    ,paragraphOriginY
    ,paragraphSpanBounds
    )
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.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