{-# 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