~alcinnz/CatTrap

ref: 205f3ae75b9f4087f5ee9bdc6da6cdef3e191592 CatTrap/Graphics/Layout/Inline.hs -rw-r--r-- 11.1 KiB
205f3ae7 — Adrian Cochrane Layout inline blocks 8 months ago
                                                                                
f5fbeaa6 Adrian Cochrane
49365312 Adrian Cochrane
9db95983 Adrian Cochrane
48ed1b32 Adrian Cochrane
f5fbeaa6 Adrian Cochrane
b07f5dcb Adrian Cochrane
e8a4642a Adrian Cochrane
d4a87f66 Adrian Cochrane
f7e9b3f4 Adrian Cochrane
205f3ae7 Adrian Cochrane
8308c7a9 Adrian Cochrane
9db95983 Adrian Cochrane
48ed1b32 Adrian Cochrane
f5fbeaa6 Adrian Cochrane
48ed1b32 Adrian Cochrane
e64a1ae7 Adrian Cochrane
f5fbeaa6 Adrian Cochrane
59a70cdb Adrian Cochrane
d4a87f66 Adrian Cochrane
9db95983 Adrian Cochrane
f5fbeaa6 Adrian Cochrane
db049add Adrian Cochrane
59a70cdb Adrian Cochrane
b07f5dcb Adrian Cochrane
db049add Adrian Cochrane
b07f5dcb Adrian Cochrane
d4a87f66 Adrian Cochrane
f5fbeaa6 Adrian Cochrane
db049add Adrian Cochrane
c4626b0b Adrian Cochrane
db049add Adrian Cochrane
c4626b0b Adrian Cochrane
db049add Adrian Cochrane
c4626b0b Adrian Cochrane
85d144a0 Adrian Cochrane
db049add Adrian Cochrane
b07f5dcb Adrian Cochrane
590ef319 Adrian Cochrane
db049add Adrian Cochrane
e8a4642a Adrian Cochrane
f5fbeaa6 Adrian Cochrane
db049add Adrian Cochrane
590ef319 Adrian Cochrane
c4626b0b Adrian Cochrane
590ef319 Adrian Cochrane
c4626b0b Adrian Cochrane
d4a87f66 Adrian Cochrane
9db95983 Adrian Cochrane
c4626b0b Adrian Cochrane
d4a87f66 Adrian Cochrane
205f3ae7 Adrian Cochrane
f7e9b3f4 Adrian Cochrane
205f3ae7 Adrian Cochrane
f5fbeaa6 Adrian Cochrane
9db95983 Adrian Cochrane
66067a7c Adrian Cochrane
9db95983 Adrian Cochrane
3a25ee5a Adrian Cochrane
9db95983 Adrian Cochrane
59a70cdb Adrian Cochrane
6db35caa Adrian Cochrane
59a70cdb Adrian Cochrane
db049add Adrian Cochrane
15a1fd94 Adrian Cochrane
59a70cdb Adrian Cochrane
8308c7a9 Adrian Cochrane
c4626b0b Adrian Cochrane
59a70cdb Adrian Cochrane
e64a1ae7 Adrian Cochrane
8308c7a9 Adrian Cochrane
d4a87f66 Adrian Cochrane
db049add Adrian Cochrane
59a70cdb Adrian Cochrane
f5fbeaa6 Adrian Cochrane
db049add Adrian Cochrane
b07f5dcb Adrian Cochrane
8308c7a9 Adrian Cochrane
f5fbeaa6 Adrian Cochrane
b07f5dcb Adrian Cochrane
66067a7c Adrian Cochrane
e8a4642a Adrian Cochrane
66067a7c Adrian Cochrane
e8a4642a Adrian Cochrane
8308c7a9 Adrian Cochrane
e8a4642a Adrian Cochrane
8308c7a9 Adrian Cochrane
e8a4642a Adrian Cochrane
66067a7c Adrian Cochrane
c4626b0b Adrian Cochrane
15a1fd94 Adrian Cochrane
c4626b0b Adrian Cochrane
15a1fd94 Adrian Cochrane
66067a7c Adrian Cochrane
9db95983 Adrian Cochrane
66067a7c Adrian Cochrane
48ed1b32 Adrian Cochrane
66067a7c Adrian Cochrane
48ed1b32 Adrian Cochrane
66067a7c Adrian Cochrane
48ed1b32 Adrian Cochrane
e64a1ae7 Adrian Cochrane
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
{-# LANGUAGE TupleSections #-}
-- | Sizes inline text & extracts positioned children,
-- wraps Balkón for the actual logic.
module Graphics.Layout.Inline(paragraphMap, layoutMap, treeMap,
    inlineMin, inlineSize, inlineChildren, layoutSize, layoutChildren,
    treeBox, positionTree, treeInner, treeInner', glyphs, codepoints,
    FragmentTree(..)) where

import Data.Text.ParagraphLayout.Rich (Paragraph(..), ParagraphOptions(..),
                                Fragment(..), ParagraphLayout(..), AncestorBox(..),
                                InnerNode(..), Box(..), RootNode(..),
                                layoutRich, boxSpacing, BoxSpacing(..),
                                activateBoxSpacing, paragraphSafeWidth, textAscender)
import Data.Text.ParagraphLayout.Rect (Rect(..),
                                width, height, x_max, x_min, y_min, y_max)
import qualified Data.Text.Glyphize as HB
import Data.Int (Int32)
import Data.Word (Word32)
import Debug.Trace (trace) -- To warn about unexpected branches!

import Graphics.Layout.Box hiding (min, max, width, height)
import qualified Graphics.Layout.Box as Box
import Graphics.Layout.CSS.Font (hbUnit)

-- | Convert from Harfbuzz units to device pixels as a Double
hbScale :: Int32 -> Double
hbScale = (/hbUnit) . fromIntegral
-- | Convert from Harfbuzz units to device pixels as a Double or Length.
c :: CastDouble a => Int32 -> a
c = fromDouble . hbScale
-- | Convert from a CastDouble in device pixels to Harfbuzz units.
unscale :: CastDouble x => x -> Int32
unscale = floor . (*hbUnit) . toDouble

-- | Compute minimum width & height for some richtext.
inlineMin :: (CastDouble x, CastDouble y) => (z -> PaddedBox x y) ->
        Paragraph (a, Either (PaddedBox x y) z, c) -> Size x y
inlineMin cb self = layoutSize' $ layoutRich' cb self 0
-- | Compute width & height of some richtext at configured width.
inlineSize :: (CastDouble x, CastDouble y) => (z -> PaddedBox x y) ->
        Paragraph (a, Either (PaddedBox x y) z, c) -> Size x y
inlineSize cb self@(Paragraph _ _ _ opts) =
    layoutSize' . layoutRich' cb self $ paragraphMaxWidth opts
-- | Retrieve children out of some richtext,
-- associating given userdata with them.
inlineChildren :: (CastDouble x, CastDouble y, Eq x, Eq y, Eq a, Eq c, Eq z) =>
        (z -> PaddedBox x y) ->
        Paragraph (a, Either (PaddedBox x y) z, c) ->
        [FragmentTree (a, Either (PaddedBox x y) z, c)]
inlineChildren cb self = layoutChildren $ layoutRich $ lowerSpacing cb self

-- | Retrieve a laid-out paragraph's rect & convert to CatTrap types.
layoutSize :: (CastDouble x, CastDouble y) => ParagraphLayout a -> Size x y
layoutSize = layoutSize' . paragraphRect
layoutSize' r = Size (c $ width r) (c $ height r)
-- | Retrieve a laid-out paragraph's children & associate with given userdata.
layoutChildren :: Eq a => ParagraphLayout a -> [FragmentTree a]
layoutChildren self = reconstructTree self

-- | Layout a paragraph at given width & retrieve resulting rect.
-- LEGACY.
layoutRich' :: (CastDouble m, CastDouble n) => (x -> PaddedBox m n) ->
        Paragraph (a, Either (PaddedBox m n) x, c) -> Int32 -> Rect Int32
layoutRich' cb (Paragraph a b c d) width =
    (paragraphRect layout) { x_size = paragraphSafeWidth layout}
  where
    layout = layoutRich $ lowerSpacing cb $ Paragraph a b c d {
        paragraphMaxWidth = width
      }

-- | Copy surrounding whitespace into Balkon properties.
lowerSpacing :: (CastDouble m, CastDouble n) => (x -> PaddedBox m n) ->
    Paragraph (a, Either (PaddedBox m n) x, c) ->
    Paragraph (a, Either (PaddedBox m n) x, c)
lowerSpacing cb (Paragraph a b (RootBox c) d) = Paragraph a b (RootBox $ inner c) d
  where
    inner (Box childs opts) = flip Box opts $ map inner' childs
    inner' (InlineBox e@(_, Left box, _) child opts) = inlineBox e box child opts
    inner' (InlineBox e@(_, Right k, _) (Box childs opts') opts) =
        let box = cb k in inlineBox e box (Box childs opts' {
            textAscender = Just $ Box.height $ mapY' unscale box
          }) opts
    inner' self@(TextSequence _ _) = self
    inlineBox dat f child opts = InlineBox dat (inner child) $
        flip activateBoxSpacing opts $
            BoxSpacingLeftRight (leftSpace box) (rightSpace box)
      where box = mapX' unscale $ mapY' unscale f


-- | A tree extracted from Balkón's inline layout.
data FragmentTree x = Branch (AncestorBox x) [FragmentTree x]
    | Leaf (Fragment x)
    deriving (Show, Eq)

-- | Apply an operation to the 2nd field of the paragraph's userdata,
-- for it's entire subtree.
paragraphMap :: (b -> b') -> Paragraph (a, b, c) -> Paragraph (a, b', c)
paragraphMap cb (Paragraph a b (RootBox c) d) =
    Paragraph a b (RootBox $ inner c) d
  where
    inner (Box childs opts) = flip Box opts $ map inner' childs
    inner' (InlineBox (e, f, g) child opts) =
        InlineBox (e, cb f, g) (inner child) opts
    inner' (TextSequence (e, f, g) leaf) = TextSequence (e, cb f, g) leaf

-- | Apply an operation to the 2nd field of a laid-out paragraph's userdata,
-- for it's entire subtree.
layoutMap :: (b -> b') -> ParagraphLayout (a, b, c) -> ParagraphLayout (a, b', c)
layoutMap cb (ParagraphLayout a b c) = ParagraphLayout a b $ map inner c
  where
    inner self@Fragment { fragmentUserData = (a, b, c) } = self {
        fragmentUserData = (a, cb b, c),
        fragmentAncestorBoxes = map inner' $ fragmentAncestorBoxes self
      }
    inner' self@AncestorBox { boxUserData = (a, b, c) } = self {
        boxUserData = (a, cb b, c)
      }

-- | Apply an operation to the 2nd field of the tree extracted from a laid-out
-- paragraph, for all nodes.
treeMap :: (b -> b') -> FragmentTree (a, b, c) -> FragmentTree (a, b', c)
treeMap cb (Branch self@AncestorBox { boxUserData = (a, b, c) } childs) =
    Branch self { boxUserData = (a, cb b, c) } $ map (treeMap cb) childs
treeMap cb (Leaf self@Fragment { fragmentUserData = (a, b, c) }) =
    Leaf self { fragmentUserData = (a, cb b, c), fragmentAncestorBoxes = [] }

-- | Retrieve the rect for a fragment & convert to CatTrap types.
fragmentSize :: (CastDouble x, CastDouble y) =>
        FragmentTree (a, PaddedBox x y, c) -> Size x y
fragmentSize self = Size (c $ width r) (c $ height r)
    where r = treeRect self
-- | Compute the unioned rect for a subtree.
treeRect :: FragmentTree (a, b, c) -> Rect Int32
treeRect (Branch AncestorBox { boxUserData = (_, box', _)} childs) =
        unions $ map treeRect childs
treeRect (Leaf self) = fragmentRect self

-- | Compute the paddedbox for a subtree.
treeBox :: (CastDouble m, CastDouble n) =>
    FragmentTree (a, PaddedBox m n, c) -> PaddedBox m n
treeBox self@(Branch AncestorBox { boxUserData = (_, box', _)} _) = box' {
    Box.min = size', Box.max = size', Box.size = size', Box.nat = size
  } where
    size' = mapSizeX fromDouble $ mapSizeY fromDouble size
    size = mapSizeX (subtract $ hSpace box) $ mapSizeY (subtract $ vSpace box)$
         mapSizeX toDouble $ mapSizeY toDouble $ fragmentSize self
    box = mapX' toDouble $ mapY' toDouble box'
treeBox self@(Leaf Fragment { fragmentUserData = (_, box', _)}) = box' {
    Box.min = size', Box.max = size', Box.size = size', Box.nat = size
  } where
    size' = mapSizeX fromDouble $ mapSizeY fromDouble size
    size = mapSizeX (subtract $ hSpace box) $ mapSizeY (subtract $ vSpace box) $
        mapSizeX toDouble $ mapSizeY toDouble $ fragmentSize self
    box = mapX' toDouble $ mapY' toDouble box'

-- | Variant of `fragmentSize` asserting to the typesystem that both fields
-- of the resulting `Size` are of the same type.
fragmentSize' :: CastDouble x => FragmentTree (a, PaddedBox x x, c) -> Size x x
fragmentSize' = fragmentSize -- Work around for typesystem.
-- | Retrieve the position of a fragment.
fragmentPos :: (Double, Double) -> Fragment a -> (Double, Double)
fragmentPos (x, y) self = (x + hbScale (x_min r), y + hbScale (y_min r))
    where r = fragmentRect self

-- | Extract the tree datastructure out of Balkón's ParagraphLayout
reconstructTree :: Eq x => ParagraphLayout x -> [FragmentTree x]
reconstructTree ParagraphLayout { paragraphFragments = frags } =
    reconstructTree' [frag {
            fragmentAncestorBoxes = reverse $ fragmentAncestorBoxes frag
        } | frag <- frags]
-- | Extract the tree datastructure out of Balkón's fragments.
reconstructTree' :: Eq x => [Fragment x] -> [FragmentTree x]
reconstructTree' (self@Fragment { fragmentAncestorBoxes = [] }:frags) =
    Leaf self:reconstructTree' frags
reconstructTree' frags@(Fragment {
        fragmentAncestorBoxes = branch:_, fragmentLine = line
  }:_) =
    Branch branch (reconstructTree' [ child { fragmentAncestorBoxes = ancestors }
            | child@Fragment { fragmentAncestorBoxes = _:ancestors } <- childs])
        :reconstructTree' sibs
  where
    (childs, sibs) = span sameBranch frags
    -- Cluster ancestor branches, breaking them per-line.
    sameBranch Fragment {fragmentAncestorBoxes=branch':_, fragmentLine=line'} =
        branch == branch' && line == line'
    -- Leaves are always in their own branch.
    sameBranch Fragment { fragmentAncestorBoxes = [] } = False
reconstructTree' [] = []

-- | Add an X,Y offset to all positions, annotating the userdata.
positionTree :: (Double, Double) -> ((Double, Double) -> b -> b') ->
        FragmentTree (a, b, c) -> FragmentTree (a, b', ((Double, Double), c))
positionTree (x, y) cb self@(Branch (AncestorBox (a, b, c) d e f g) childs) =
    Branch (AncestorBox (a, cb pos b, (pos, c)) d e f g) $
        map (positionTree pos cb) childs
  where
    pos = (x + hbScale (x_min rect), y + hbScale (y_min rect))
    rect = treeRect self
positionTree (x, y) cb self@(Leaf (Fragment (a, b, c) d _ f g h i)) =
    Leaf (Fragment (a, cb pos b, (pos, c)) d [] f g h i)
  where
    pos = (x + hbScale (x_min rect), y + hbScale (y_min rect))
    rect = treeRect self
-- | Retrieve 3rd userdata field.
treeInner :: FragmentTree (a, b, c) -> c
treeInner (Branch AncestorBox { boxUserData = (_, _, ret) } _) = ret
treeInner (Leaf Fragment { fragmentUserData = (_, _, ret) }) = ret
-- | Retrieve userdata field.
treeInner' :: FragmentTree a -> a
treeInner' (Branch self _) = boxUserData self
treeInner' (Leaf self) = fragmentUserData self

-- | Retrieve Harfbuzz data out of the tree extracted from Balkón.
glyphs :: FragmentTree x -> [(HB.GlyphInfo, HB.GlyphPos)]
glyphs (Branch _ _) = []
glyphs (Leaf self) = fragmentGlyphs self
-- | Retrieve the Unicode codepoints out of the tree extracted from Balkón.
codepoints :: FragmentTree x -> [Word32]
codepoints self = map HB.codepoint $ map fst $ glyphs self

------
--- Taken from Balkón
------
-- | Calculate the smallest rectangle that completely contains all the given
-- rectangles.
unions [] = trace "No rects to union!" $ Rect 0 0 0 0
unions rects = foldr1 union rects

-- | Calculate the smallest rectangle that completely contains the given two
-- rectangles.
--
-- The origin of the resulting rectangle will be the corner with the lowest
-- X coordinate and the highest Y coordinate, regardless of the origin of the
-- input rectangles.
union :: (Num a, Ord a) => Rect a -> Rect a -> Rect a
union a b = Rect x_low y_high dx (-dy) where
    x_low = x_min a `min` x_min b
    y_low = y_min a `min` y_min b
    x_high = x_max a `max` x_max b
    y_high = y_max a `max` y_max b
    dx = x_high - x_low
    dy = y_high - y_low