~jaro/balkon

balkon/src/Data/Text/ParagraphLayout/Internal/ProtoLine.hs -rw-r--r-- 4.2 KiB
9e3b0ec7Jaro Set release date for v1.3.0.0. 1 year, 4 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
module Data.Text.ParagraphLayout.Internal.ProtoLine
    ( ProtoLine (..)
    , nonEmpty
    , visible
    , width
    , applyBoxes
    , mapFragments
    )
where

import Data.Int (Int32)
import Data.List.NonEmpty (NonEmpty ((:|)))

import qualified Data.Text.ParagraphLayout.Internal.ApplyBoxes as Algo
import Data.Text.ParagraphLayout.Internal.BoxOptions
import Data.Text.ParagraphLayout.Internal.ProtoFragment
import Data.Text.ParagraphLayout.Internal.ResolvedBox
import Data.Text.ParagraphLayout.Internal.ResolvedSpan
import Data.Text.ParagraphLayout.Internal.WithSpan

-- | Contents of a line that have not been visually ordered or positioned yet.
data ProtoLine f d = ProtoLine

    { protoFragments :: f (WithSpan d ProtoFragment)
    -- ^ Fragments on the line, in logical order.
    -- Intended to be either a regular list or a non-empty list.

    , prevOpenBoxes :: [ResolvedBox d]
    -- ^ Boxes whose starts are located on preceding lines.
    -- The spacing for their start edge will not count towards this line.

    , nextOpenBoxes :: [ResolvedBox d]
    -- ^ Boxes whose ends are located on following lines.
    -- The spacing for their end edge will not count towards this line.

    }

-- | Covert a line with a regular list of fragments into `Just` a line with
-- a non-empty list of fragments, or `Nothing` if there are no fragments.
nonEmpty :: ProtoLine [] d -> Maybe (ProtoLine NonEmpty d)
nonEmpty (ProtoLine [] _ _) = Nothing
nonEmpty pl@(ProtoLine { protoFragments = (f : fs) }) =
    Just pl { protoFragments = f :| fs }

-- | `True` if this line should generate a visible line box,
-- or `False` if this line should generate an /invisible line box/
-- as defined by <https://www.w3.org/TR/css-inline-3/#invisible-line-boxes>.
visible :: Foldable f => ProtoLine f d -> Bool
visible pl = any visibleProtoFragment $ protoFragments pl

visibleProtoFragment :: WithSpan d ProtoFragment -> Bool
visibleProtoFragment (WithSpan rs pf) =
    hasGlyphs || hasHardBreak || hasNonCollapsibleBoxes
    where
        hasGlyphs = not $ null $ glyphs pf
        hasHardBreak = hardBreak pf
        hasNonCollapsibleBoxes =
            any (== AvoidBoxCollapse) $ map (boxCollapse . boxOptions) boxes
        boxes = spanBoxes rs

-- | Total width of the line (content and spacing).
width :: (Foldable f, Functor f) => ProtoLine f d -> Int32
width pl = totalAdvances pl + totalBoxesStart pl + totalBoxesEnd pl

-- | Determine which horizontal fragments are the leftmost and which are the
-- rightmost within their ancestor inline boxes.
--
-- The input must be ordered from left to right.
applyBoxes :: ProtoLine NonEmpty d ->
    NonEmpty (Algo.WithBoxes d (WithSpan d ProtoFragment))
applyBoxes pl = Algo.applyBoxes prevOpen nextOpen pfs
    where
        prevOpen = prevOpenBoxes pl
        nextOpen = nextOpenBoxes pl
        pfs = protoFragments pl

-- | Total glyph advances on the line.
totalAdvances :: (Foldable f, Functor f) => ProtoLine f d -> Int32
totalAdvances pl = sum $ fmap getAdvance $ protoFragments pl
    where getAdvance (WithSpan _ pf) = advance pf

-- | Total spacing for boxes starting on the line.
totalBoxesStart :: (Foldable f, Functor f) => ProtoLine f d -> Int32
totalBoxesStart pl = sum $ fmap boxStartSpacing $ boxesStart pl

-- | Total spacing for boxes ending on the line.
totalBoxesEnd :: (Foldable f, Functor f) => ProtoLine f d -> Int32
totalBoxesEnd pl = sum $ fmap boxEndSpacing $ boxesEnd pl

-- | Boxes that start on the given line.
boxesStart :: (Foldable f, Functor f) => ProtoLine f d -> [ResolvedBox d]
boxesStart pl = allBoxes (protoFragments pl) `diff` prevOpenBoxes pl

-- | Boxes that end on the given line.
boxesEnd :: (Foldable f, Functor f) => ProtoLine f d -> [ResolvedBox d]
boxesEnd pl = allBoxes (protoFragments pl) `diff` nextOpenBoxes pl

-- | Apply a function to every fragment on the line.
--
-- Note that `ResolvedSpan` cannot be changed in this manner and is
-- only provided on the input of the mapping function.
mapFragments
    :: Functor f
    => (ResolvedSpan d -> ProtoFragment -> ProtoFragment)
    -> ProtoLine f d
    -> ProtoLine f d
mapFragments mapFunc pl =
    pl { protoFragments = fmap mapFunc' $ protoFragments pl }
    where
        mapFunc' (WithSpan rs pf) = WithSpan rs $ mapFunc rs pf