~alcinnz/CatTrap

ref: 5bd4424d5c976900b9944270bc6eca157605a62c CatTrap/Graphics/Layout.hs -rw-r--r-- 16.9 KiB
5bd4424d — Adrian Cochrane Fix imports for abstracted CSS API. 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
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
{-# LANGUAGE OverloadedStrings, RecordWildCards #-}
module Graphics.Layout where

import Data.Text.ParagraphLayout (Paragraph(..), ParagraphOptions(..), Fragment(..))

import Graphics.Layout.Box as B
import Graphics.Layout.Grid as G
import Graphics.Layout.Flow as F
import Graphics.Layout.Inline as I
import Graphics.Layout.CSS.Font (Font'(..))

import Data.Maybe (fromMaybe)

-- To gather glyphs for atlases.
import qualified Data.IntSet as IS
import qualified Data.Map.Strict as M
import qualified Data.Text.Glyphize as Hb
import Graphics.Text.Font.Choose (Pattern)

data LayoutItem m n x =
    LayoutFlow x (PaddedBox m n) [LayoutItem m n x]
    | LayoutGrid x (Grid m n) [(GridItem m n, LayoutItem m n x)]
    | LayoutInline x Font' Paragraph [x] -- Balkon holds children.
    | LayoutSpan x Font' Fragment
-- More to come...

layoutGetBox :: (Zero m, Zero n, CastDouble m, CastDouble n) =>
        LayoutItem m n x -> PaddedBox m n
layoutGetBox (LayoutFlow _ ret _) = ret
layoutGetBox (LayoutGrid _ self _) = zero {
    B.min = containerMin self,
    B.size = containerSize self,
    B.max = containerMax self
}
layoutGetBox (LayoutInline _ f self _) = zero {
    B.min = inlineSize f self, B.size = inlineSize f self, B.max = inlineSize f self
}
layoutGetBox (LayoutSpan _ f self) = zero {
    B.min = fragmentSize f self, B.size = fragmentSize f self, B.max = fragmentSize f self
}
layoutGetChilds (LayoutFlow _ _ ret) = ret
layoutGetChilds (LayoutGrid _ _ ret) = map snd ret
layoutGetChilds (LayoutSpan _ _ _) = []
layoutGetChilds (LayoutInline _ font self vals) = map inner $ inlineChildren vals self
  where inner (val, fragment) = LayoutSpan val font fragment
layoutGetInner (LayoutFlow ret _ _) = ret
layoutGetInner (LayoutGrid ret _ _) = ret
layoutGetInner (LayoutInline ret _ _ _) = ret
layoutGetInner (LayoutSpan ret _ _) = ret

setCellBox' (child, cell) = cell { gridItemBox = layoutGetBox child }

boxMinWidth :: (Zero y, CastDouble y) =>
        Maybe Double -> LayoutItem y Length x -> (Double, LayoutItem y Length x)
boxMinWidth parent (LayoutFlow val self childs) = (min', LayoutFlow val self' childs')
  where
    self' = self {B.min = Size (Pixels min') (block $ B.min self) }
    min' = flowMinWidth parent' self childs''
    childs'' = map (mapX' $ lowerLength selfWidth) $ map layoutGetBox childs'
    childs' = map snd $ map (boxMinWidth $ Just selfWidth) childs
    selfWidth = width $ mapX' (lowerLength parent') self
    parent' = fromMaybe 0 parent
boxMinWidth parent (LayoutGrid val self childs) =
    (min', LayoutGrid val self' $ zip cells' childs')
  where
    self' = self {
        containerMin = Size (Pixels min') (block $ containerMin self),
        colBounds = zip cells (map snd (colBounds self) ++ repeat 0)
      }
    (min', cells) = gridMinWidths parent' self cells''
    cells'' = [ setCellBox (mapX' (lowerLength selfWidth) $ gridItemBox cell) cell
                | cell <- cells']
    cells' = map setCellBox' $ zip childs' $ map fst childs
    childs'' = map (mapX' $ lowerLength selfWidth) $ map layoutGetBox childs'
    childs' = map snd $ map (boxMinWidth $ Just selfWidth) $ map snd childs
    selfWidth = lowerLength parent' $ inline $ containerSize self
    parent' = fromMaybe (gridEstWidth self [
        GridItem startRow endRow startCol endCol alignment zeroBox |
        (GridItem {..}, _) <- childs]) parent
    zeroBox :: PaddedBox Double Double
    zeroBox = zero
boxMinWidth _ self@(LayoutInline _ font self' _) = (inlineMinWidth font self', self)
boxMinWidth _ self@(LayoutSpan _ f self') = (B.inline $ fragmentSize' f self', self)
boxNatWidth :: (Zero y, CastDouble y) =>
        Maybe Double -> LayoutItem y Length x -> (Double, LayoutItem y Length x)
boxNatWidth parent (LayoutFlow val self childs) = (size', LayoutFlow val self childs')
  -- NOTE: Need to preserve auto/percentage in actual width calculation.
  -- self' doesn't preserve this. CatTrap will need a decent refactor!
  where
    self' = self {size = Size (Pixels size') (block $ size self) }
    size' = flowNatWidth parent' self childs''
    childs'' = map (mapX' $ lowerLength selfWidth) $ map layoutGetBox childs'
    childs' = map snd $ map (boxNatWidth $ Just selfWidth) childs
    selfWidth = width $ mapX' (lowerLength parent') self
    parent' = fromMaybe 0 parent
boxNatWidth parent (LayoutGrid val self childs) =
    (size', LayoutGrid val self' $ zip cells' childs')
  where
    self' = self {
        containerSize = Size (Pixels size') (block $ containerSize self),
        colBounds = zip (map fst (colBounds self) ++ repeat 0) cells
      }
    (size', cells) = gridNatWidths parent' self cells''
    cells'' = [
        cell { gridItemBox = mapX' (lowerLength selfWidth) $ gridItemBox cell }
        | cell <- cells']
    cells' = map setCellBox $ zip childs' $ map fst childs
    setCellBox (child, cell) = cell { gridItemBox = layoutGetBox child }
    childs'' = map (mapX' $ lowerLength selfWidth) $ map layoutGetBox childs'
    childs' = map snd $ map (boxNatWidth $ Just selfWidth) $ map snd childs
    selfWidth = lowerLength parent' $ inline $ containerSize self
    parent' = fromMaybe (gridEstWidth self [
        GridItem startRow endRow startCol endCol alignment zeroBox |
        (GridItem {..}, _) <- childs]) parent
    zeroBox :: PaddedBox Double Double
    zeroBox = zero
boxNatWidth _ self@(LayoutInline _ font self' _) = (inlineNatWidth font self', self)
boxNatWidth _ self@(LayoutSpan _ f self') = (B.inline $ fragmentSize' f self', self)
boxMaxWidth :: PaddedBox a Double -> LayoutItem y Length x -> (Double, LayoutItem y Length x)
boxMaxWidth parent (LayoutFlow val self childs) = (max', LayoutFlow val self' childs)
  where
    self' = self { B.max = Size (Pixels max') (block $ B.max self) }
    max' = flowMaxWidth parent self
boxMaxWidth parent (LayoutGrid val self childs) =
    (max', LayoutGrid val self' childs)
  where
    self' = self { containerMax = Size (Pixels max') (block $ containerMax self) }
    (max', _) = gridMaxWidths parent self $ colBounds self
boxMaxWidth parent self@(LayoutInline _ _ _ _) = (B.inline $ B.max parent, self)
boxMaxWidth parent self@(LayoutSpan _ f self') = (B.inline $ fragmentSize' f self', self)
boxWidth :: (Zero y, CastDouble y) => PaddedBox b Double -> LayoutItem y Length x ->
        (Double, LayoutItem y Double x)
boxWidth parent (LayoutFlow val self childs) = (size', LayoutFlow val self' childs')
  where
    childs' = map (snd . boxWidth self') childs
    self' = (mapX' (lowerLength $ inline $ size parent) self) {
        size = Size size' $ block $ B.max self
      }
    size' = flowWidth parent self
boxWidth parent (LayoutGrid val self childs) = (size', LayoutGrid val self' childs')
  where
    childs' = map recurse childs
    recurse (cell, child) = (cell', child')
      where
        cell' = cell { gridItemBox = layoutGetBox child' }
        (_, child') = boxWidth parent' child
        parent' = zero {
            B.min = containerMin self',
            B.size = containerSize self',
            B.max = containerMax self'
          }
    self' = Grid {
        containerSize = Size size' $ block $ containerSize self,
        containerMin = mapSizeX (lowerLength outerwidth) $ containerMin self,
        containerMax = mapSizeX (lowerLength outerwidth) $ containerMax self,
        gap = mapSizeX (lowerLength outerwidth) $ gap self,
        columns = map Left widths,

        rows = rows self,
        rowBounds = rowBounds self,
        colBounds = colBounds self,
        subgridRows = subgridRows self, subgridColumns = subgridColumns self
      }
    outerwidth = inline $ size parent
    (size', widths) = gridWidths parent self $ colBounds self
boxWidth parent (LayoutInline val font (Paragraph a b c d) vals) =
    (width, LayoutInline val font (Paragraph a b c d { paragraphMaxWidth = round width }) vals)
  where width = B.inline $ B.size parent
boxWidth parent (LayoutSpan val font self') =
    (B.inline $ fragmentSize' font self', LayoutSpan val font self')

boxNatHeight :: Double -> LayoutItem Length Double x -> (Double, LayoutItem Length Double x)
boxNatHeight parent (LayoutFlow val self childs) = (size', LayoutFlow val self' childs')
  where
    self' = self {
        size = Size width (Pixels size')
      }
    size' = flowNatHeight parent self childs''
    childs'' = map (mapY' (lowerLength parent)) $ map layoutGetBox childs'
    childs' = map snd $ map (boxNatHeight width) childs
    width = inline $ size self
boxNatHeight parent (LayoutGrid val self childs) =
    (size', LayoutGrid val self' $ zip cells childs')
  where
    self' = self {
        containerSize = Size width $ Pixels size'
    }
    lowerGridUnit (Left length) = Left $ lowerLength width length
    lowerGridUnit (Right x) = Right x
    (size', heights) = gridNatHeights parent self cells'
    cells' = [setCellBox (mapY' (lowerLength width) $ gridItemBox cell) cell | cell <- cells]
    cells = map setCellBox' $ zip childs' $ map fst childs
    childs' = map snd $ map (boxNatHeight width) $ map snd childs
    width = inline $ containerSize self
boxNatHeight parent self@(LayoutInline _ font self' _) = (inlineHeight font parent self', self)
boxNatHeight parent self@(LayoutSpan _ font self') = (B.block $ fragmentSize' font self', self)
boxMinHeight :: Double -> LayoutItem Length Double x -> (Double, LayoutItem Length Double x)
boxMinHeight parent (LayoutFlow val self childs) = (min', LayoutFlow val self' childs')
  where
    childs' = map snd $ map (boxMinHeight $ inline $ size self) childs
    self' = self {
        B.min = Size (inline $ B.min self) (Pixels min')
      }
    min' = flowMinHeight parent self
boxMinHeight parent (LayoutGrid val self childs) = (min', LayoutGrid val self' childs')
  where
    childs' = map recurse childs
    recurse (cell, child) = (cell', child')
      where
        cell' = setCellBox (layoutGetBox child') cell
        (_, child') = boxMinHeight width child
    self' = self {
        containerMin = Size width $ Pixels min',
        rowBounds = zip heights (map snd (rowBounds self) ++ repeat 0)
      }
    (min', heights) = gridMinHeights width self childs0
    childs0 = [ GridItem {
        gridItemBox = mapY' (lowerLength width) $ gridItemBox cell,
        startRow = startRow cell, endRow = endRow cell,
        startCol = startCol cell, endCol = endCol cell, alignment = alignment cell
      } | (cell, _) <- childs]
    width = inline $ containerSize self
boxMinHeight parent self@(LayoutInline _ font self' _) = (inlineHeight font parent self', self)
boxMinHeight parent self@(LayoutSpan _ font self') = (B.block $ fragmentSize' font self', self)
boxMaxHeight :: PaddedBox Double Double -> LayoutItem Length Double x ->
        (Double, LayoutItem Length Double x)
boxMaxHeight parent (LayoutFlow val self childs) = (max', LayoutFlow val self' childs')
  where
    childs' = map snd $ map (boxMaxHeight $ mapY' (lowerLength width) self') childs
    self' = self {
        B.max = Size (inline $ B.max self) (Pixels max')
      }
    max' = flowMaxHeight (inline $ size parent) self
    width = inline $ size self
boxMaxHeight parent (LayoutGrid val self childs) = (max', LayoutGrid val self' childs')
  where
    childs' = map recurse childs
    recurse (cell, child) = (cell', child')
      where
        cell' = setCellBox (layoutGetBox child') cell
        (_, child') = boxMaxHeight parent' child
        parent' :: PaddedBox Double Double
        parent' = zero {
            B.min = mapSizeY (lowerLength width) $ containerMin self,
            B.size = mapSizeY (lowerLength width) $ containerSize self,
            B.max = Size (inline $ containerMax self') max'
        }
    self' = self {
        containerMax = Size (inline $ containerSize self) (Pixels max')
      }
    (max', heights) = gridMaxHeights parent self $ rowBounds self
    width = inline $ size parent
boxMaxHeight parent (LayoutInline val font self' vals) =
    (inlineHeight font (B.inline $ B.size parent) self',
    LayoutInline val font self' vals)
boxMaxHeight parent (LayoutSpan val font self') =
    (B.block $ fragmentSize' font self', LayoutSpan val font self')
boxHeight :: PaddedBox Double Double -> LayoutItem Length Double x ->
        (Double, LayoutItem Double Double x)
boxHeight parent (LayoutFlow val self childs) = (size', LayoutFlow val self' childs')
  where
    childs' = map snd $ map (boxHeight self') childs
    self' = (mapY' (lowerLength $ inline $ size parent) self) {
        size = Size (inline $ size self) size'
      }
    size' = flowHeight parent self
    width = inline $ size self
boxHeight parent (LayoutGrid val self childs) = (size', LayoutGrid val self' childs')
  where
    childs' = map recurse childs
    recurse (cell, child) = (cell', child')
      where
        cell' = setCellBox (layoutGetBox child') cell
        (_, child') = boxHeight (layoutGetBox $ LayoutGrid val self' []) child
    self' = Grid {
        containerSize = Size (inline $ containerSize self) size',
        containerMin = mapSizeY (lowerLength width) $ containerMin self,
        containerMax = mapSizeY (lowerLength width) $ containerMax self,
        gap = mapSizeY (lowerLength width) $ gap self,

        rows = map lowerSize $ rows self, rowBounds = rowBounds self,
        columns = columns self, colBounds = colBounds self,
        subgridRows = subgridRows self, subgridColumns = subgridColumns self
      }
    (size', heights) = gridHeights parent self $ rowBounds self
    lowerSize (Left x) = Left $ lowerLength width x
    lowerSize (Right x) = Right x
    width = inline $ size parent
boxHeight parent (LayoutInline val font self' vals) =
    (inlineHeight font (B.inline $ B.size parent) self',
    LayoutInline val font self' vals)
boxHeight _ (LayoutSpan val font self') =
    (B.block $ fragmentSize' font self', LayoutSpan val font self')

boxSplit :: Double -> Double -> LayoutItem Double Double x ->
    (LayoutItem Double Double x, Maybe (LayoutItem Double Double x))
boxSplit maxheight _ node | height (layoutGetBox node) <= maxheight = (node, Nothing)
boxSplit maxheight pageheight (LayoutFlow val self childs)
    | (next:_) <- childs1, ((y,_):_) <- childs0',
        (tail,Just nextpage) <- boxSplit (maxheight - y) pageheight next =
            (LayoutFlow val self {
                size = (size self) { B.block = y }
            } (childs0 ++ [tail]),
             Just $ LayoutFlow val self {
                size = (size self) { B.block = B.block (size self) - y }
             } (nextpage:childs1))
    | otherwise =
        (LayoutFlow val self { size = (size self) { B.block = maxheight } } childs0,
         Just $ LayoutFlow val self childs1) -- TODO recompute height
  where
    childs0 = map snd childs0'
    childs1 = map snd childs1'
    (childs0', childs1') = break overflowed $ inner 0 childs
    overflowed (y, _) = y >= maxheight
    inner start (child:childs) = (start', child):inner start' childs -- TODO margin collapse?
        where start' = start + height (layoutGetBox child)
    inner _ [] = []
boxSplit _ _ self@(LayoutGrid _ _ _) = (self, Nothing) -- TODO
boxSplit _ _ self@(LayoutInline _ _ _ _) = (self, Nothing) -- TODO
boxSplit _ _ self@(LayoutSpan _ _ _) = (self, Nothing) -- Can't split!
boxPaginate maxheight pageheight node
    | (page, Just overflow) <- boxSplit maxheight pageheight node =
        page:boxPaginate maxheight pageheight overflow
    | otherwise = [node]

boxPosition :: (Double, Double) -> LayoutItem Double Double x ->
    LayoutItem Double Double ((Double, Double), x)
boxPosition pos@(x, y) (LayoutFlow val self childs) = LayoutFlow (pos, val) self childs'
  where
    childs' = map recurse $ zip pos' childs
    recurse ((Size x' y'), child) = boxPosition (x + x', y + y') child
    pos' = positionFlow $ map layoutGetBox childs
boxPosition pos@(x, y) (LayoutGrid val self childs) = LayoutGrid (pos, val) self childs'
  where
    childs' = map recurse $ zip pos' childs
    recurse ((Size x' y'), (cell, child)) = (cell, boxPosition (x + x', y + y') child)
    pos' = gridPosition self $ map fst childs
boxPosition pos@(x, y) (LayoutInline val font self vals) =
    LayoutInline (pos, val) font self $ map (\(x, y) -> (fragmentPos font pos y, x)) $
            inlineChildren vals self
boxPosition pos (LayoutSpan val f self) = LayoutSpan (pos, val) f self -- No children...
boxLayout :: PaddedBox Double Double -> LayoutItem Length Length x -> Bool ->
        LayoutItem Double Double ((Double, Double), x)
boxLayout parent self paginate = self8
  where
    (_, self0) = boxMinWidth Nothing self
    (_, self1) = boxNatWidth Nothing self0
    (_, self2) = boxMaxWidth parent self1
    (_, self3) = boxWidth parent self2
    (natsize, self4) = boxNatHeight (inline $ size parent) self3
    (_, self5) = boxMinHeight natsize self4
    (_, self6) = boxMaxHeight parent self5
    (_, self7) = boxHeight parent self6
    self8 = boxPosition (0, 0) self7

-- Useful for assembling glyph atlases.
glyphsPerFont :: LayoutItem x y z -> M.Map (Pattern, Double) IS.IntSet
glyphsPerFont (LayoutSpan _ font self) =
    (pattern font, fontSize font) `M.singleton` IS.fromList glyphs
  where glyphs = map fromEnum $ map Hb.codepoint $ map fst $ fragmentGlyphs self
glyphsPerFont node = M.unionsWith IS.union $ map glyphsPerFont $ layoutGetChilds node