~alcinnz/CatTrap

8fb3f54afe3a41d83f48c35ea10a02daa1d250c4 — Adrian Cochrane 1 year, 8 days ago 7301a48
Integrate FlexBox layout
M Graphics/Layout.hs => Graphics/Layout.hs +37 -1
@@ 58,7 58,7 @@ data LayoutItem m n x =
    | LayoutConst x (PaddedBox m n) [LayoutItem m n x]
    -- | Children of a `LayoutInline` or `LayoutInline'`.
    | LayoutSpan (FragmentTree (UserData m n x))
    | LayoutFlex x (PaddedBox m n) (FlexParent (LayoutItem m n x) m)
    | LayoutFlex x (FlexParent (LayoutItem m n x) m)
    deriving (Show, Eq)
-- | An empty box.
nullLayout :: (PropertyParser x, Zero m, Zero n) => LayoutItem m n x


@@ 88,6 88,10 @@ layoutGetBox (LayoutInline' _ self _) = zero {
}
layoutGetBox (LayoutSpan self) = treeBox self
layoutGetBox (LayoutConst _ ret _) = ret
layoutGetBox (LayoutFlex _ self) = flexGetBox layoutGetBox' self
layoutGetBox' :: (Zero m, Zero n, CastDouble m, CastDouble n) =>
        LayoutItem m n x -> PaddedBox Double Double
layoutGetBox' = mapX' toDouble . mapY' toDouble . layoutGetBox
-- | Retrieve the subtree under a node.
layoutGetChilds (LayoutFlow _ _ ret) = ret
layoutGetChilds (LayoutGrid _ _ _ ret) = ret


@@ 95,6 99,7 @@ layoutGetChilds (LayoutSpan _) = []
layoutGetChilds (LayoutInline _ self _) = map LayoutSpan $ inlineChildren self
layoutGetChilds (LayoutInline' _ self _) = map LayoutSpan $ layoutChildren self
layoutGetChilds (LayoutConst _ _ childs) = childs
layoutGetChilds (LayoutFlex _ x) = map Fl.flexInner $ concat $ Fl.children x
-- | Retrieve the caller-specified data attached to a layout node.
layoutGetInner (LayoutFlow ret _ _) = ret
layoutGetInner (LayoutGrid ret _ _ _) = ret


@@ 102,6 107,7 @@ layoutGetInner (LayoutInline ret _ _) = ret
layoutGetInner (LayoutInline' ret _ _) = ret
layoutGetInner (LayoutConst ret _ _) = ret
layoutGetInner (LayoutSpan x) = treeInner x
layoutGetInner (LayoutFlex ret _ ) = ret

-- | Retrieve the font associated with inline layout.
fragmentFont x = let (ret, _, _) = treeInner' x in ret


@@ 136,6 142,7 @@ boxMinWidth _ self@(LayoutInline' _ _ _) = self
boxMinWidth _ (LayoutConst val self' childs) =
    LayoutConst val self' $ map (boxMinWidth Nothing) childs
boxMinWidth _ self@(LayoutSpan _) = self
boxMinWidth size self@(LayoutFlex a b) = LayoutFlex a $ flexMap (boxMinWidth size) b
-- | Update a (sub)tree to compute & cache ideal width.
boxNatWidth :: (Zero y, CastDouble y, NFData y) =>
        Maybe Double -> LayoutItem y Length x -> LayoutItem y Length x


@@ 163,6 170,7 @@ boxNatWidth _ self@(LayoutInline' _ _ _) = self
boxNatWidth _ (LayoutConst val self' childs) =
    LayoutConst val self' $ map (boxNatWidth Nothing) childs
boxNatWidth _ self@(LayoutSpan _) = self
boxNatWidth size (LayoutFlex a b ) = LayoutFlex a $ flexMap (boxNatWidth size) b
-- | Update a (sub)tree to compute & cache maximum legible width.
boxMaxWidth :: (CastDouble y, Zero y, NFData y) =>
        PaddedBox a Double -> LayoutItem y Length x -> LayoutItem y Length x


@@ 183,6 191,9 @@ boxMaxWidth parent self@(LayoutInline' _ _ _) = self
boxMaxWidth _ (LayoutConst val self' childs) = LayoutConst val self' $
    map (boxMaxWidth $ mapY' toDouble $ mapX' toDouble self') childs
boxMaxWidth parent self@(LayoutSpan _) = self
boxMaxWidth parent (LayoutFlex a b) = LayoutFlex a $ (case Fl.direction b of
    Fl.Row -> flip flexWrap (inline $ B.size parent)
    Fl.Column -> id) $ flexMap (boxMaxWidth parent) b
-- | Update a (sub)tree to compute & cache final width.
boxWidth :: (Zero y, CastDouble y, NFData y) =>
        PaddedBox b Double -> LayoutItem y Length x -> LayoutItem y Double x


@@ 218,6 229,7 @@ boxWidth p (LayoutConst val self childs) = LayoutConst val (mapX' cb self) $
  where cb = lowerLength $ width p
boxWidth parent (LayoutSpan self') =
    LayoutSpan $ treeMap (mapX' $ lowerLength $ width parent) self'
boxWidth parent (LayoutFlex a b) = LayoutFlex a $ flexMap (boxWidth parent) b

-- | Update a (sub)tree to compute & cache ideal legible height.
boxNatHeight :: Double -> LayoutItem Length Double x -> LayoutItem Length Double x


@@ 239,6 251,7 @@ boxNatHeight parent self@(LayoutInline' _ _ _) = self
boxNatHeight p (LayoutConst val self' childs) = LayoutConst val self' $
    map (boxNatHeight $ width $ mapY' (lowerLength p) self') childs
boxNatHeight parent self@(LayoutSpan _) = self
boxNatHeight parent (LayoutFlex a b) = LayoutFlex a $ flexMap (boxNatHeight parent) b
-- | Update a (sub)tree to compute & cache minimum legible height.
boxMinHeight :: Double -> LayoutItem Length Double x -> LayoutItem Length Double x
boxMinHeight parent (LayoutFlow val self childs) = LayoutFlow val self' childs'


@@ 261,6 274,7 @@ boxMinHeight _ self@(LayoutInline' _ _ _) = self
boxMinHeight p (LayoutConst val self' childs) = LayoutConst val self' $
    map (boxMinHeight $ width $ mapY' (lowerLength p) self') childs
boxMinHeight parent self@(LayoutSpan _) = self
boxMinHeight parent self@(LayoutFlex a b) = LayoutFlex a $ flexMap (boxMinHeight parent) b
-- | Update a subtree to compute & cache maximum legible height.
boxMaxHeight :: PaddedBox Double Double -> LayoutItem Length Double x ->
        LayoutItem Length Double x


@@ 284,6 298,7 @@ boxMaxHeight _ (LayoutInline' val self' paging) = LayoutInline' val self' paging
boxMaxHeight p (LayoutConst val self' childs) = LayoutConst val self' $
    map (boxMaxHeight $ mapY' (lowerLength $ width p) self') childs
boxMaxHeight parent (LayoutSpan self') = LayoutSpan self'
boxMaxHeight parent (LayoutFlex a b) = LayoutFlex a $ flexMap (boxMaxHeight parent) b
-- | Update a (sub)tree to compute & cache final height.
boxHeight :: PaddedBox Double Double -> LayoutItem Length Double x -> LayoutItem Double Double x
boxHeight parent (LayoutFlow val self childs) = LayoutFlow val self' childs'


@@ 319,6 334,8 @@ boxHeight p (LayoutConst val self childs) =
    in LayoutConst val self' $ map (boxHeight self') childs
boxHeight p (LayoutSpan self') =
    LayoutSpan $ treeMap (mapY' $ lowerLength $ width p) self'
boxHeight p (LayoutFlex a b) = LayoutFlex a $
    flexResolve (innerMain . layoutGetBox) (width p) $ flexMap (boxHeight p) b

-- | Split a (sub)tree to fit within max-height.
-- May take full page height into account.


@@ 359,6 376,12 @@ boxSplit maxheight pageheight (LayoutInline' a self paging) =
  where
    wrap self' = LayoutInline' a self' paging
boxSplit _ _ self@(LayoutSpan _) = (self, Nothing) -- Can't split!
boxSplit maxheight pageheight (LayoutFlex a self) =
    -- FIXME: What if any children are too big for the page?
    let (p0, p1) = flexSplit ( B.size . layoutGetBox ) maxheight pageheight self
    in if null $ Fl.children p1
    then (LayoutFlex a p0, Nothing)
    else (LayoutFlex a p0, Just $ LayoutFlex a p1)
-- | Generate a list of pages from a node, splitting subtrees where necessary.
boxPaginate pageheight node
    | (page, Just overflow) <- boxSplit pageheight pageheight node =


@@ 368,6 391,19 @@ boxPaginate pageheight node
-- | Compute position of all nodes in the (sub)tree relative to a base coordinate.
boxPosition :: (PropertyParser x, Eq x) => (Double, Double) ->
    LayoutItem Double Double x -> LayoutItem Double Double ((Double, Double), x)
boxPosition (x,y) (LayoutFlow val box [LayoutFlex val' self]) =
    LayoutFlow ((x,y), val) box [ -- Obtaining size from parent
        LayoutFlex (pos', val') $
            flexPosition boxPosition boxSize pos' (B.size box) self
    ]
  where
    boxSize box' = let b = layoutGetBox box' in B.width b `Size` B.height b
    pos' = (x + B.leftSpace box, y + B.rightSpace box)
boxPosition pos self@(LayoutFlex val self') =
    LayoutFlex (pos, val) $ flexPosition boxPosition boxSize pos size self'
  where
    boxSize box' = let b = layoutGetBox box' in B.width b `Size` B.height b
    size = B.size $ layoutGetBox self
boxPosition pos@(x, y) (LayoutFlow val self childs) = LayoutFlow (pos, val) self childs'
  where
    childs' = parMap' recurse $ zip pos' childs

M Graphics/Layout/Box.hs => Graphics/Layout/Box.hs +8 -1
@@ 181,10 181,17 @@ class CastDouble a where
    fromDouble :: Double -> a
    -- | Convert a double or length to a double.
    toDouble :: a -> Double
    toDouble = toDoubleWithin 0
    toDoubleWithin :: Double -> a -> Double
    toDoubleWithin _ = toDouble
    toDoubleWithinAuto :: Double -> Double -> a -> Double
    toDoubleWithinAuto _ = toDoubleWithin

instance CastDouble Double where
    fromDouble = id
    toDouble = id
instance CastDouble Length where
    fromDouble = Pixels
    toDouble = lowerLength 0
    toDoubleWithin = lowerLength
    toDoubleWithinAuto x _ Auto = x
    toDoubleWithinAuto _ x y = toDoubleWithin x y

M Graphics/Layout/CSS.hs => Graphics/Layout/CSS.hs +4 -4
@@ 56,7 56,7 @@ finalizeCSS root parent self@StyleTree {
        (finalizeBox (collapseTBorders' self') font_)
        ([finalizeCSS root font_ child { style = child' { display = Block } }
            | child@StyleTree { style = child'@CSSBox { display = TableCaption } } <- childs] ++
        [finalizeTable root font_ (inner self') opts childs])
        [finalizeTable root font_ temp opts childs])
  where
    font_ = pattern2font (font self') (font' self') parent root
finalizeCSS root parent self@StyleTree {


@@ 73,9 73,9 @@ finalizeCSS root parent self@StyleTree {
finalizeCSS root parent self@StyleTree {
        style = self'@CSSBox { display = Flex, flexOptions = flex },
        children = childs
    } = LayoutFlex (inner' font_ self' ) (finalizeBox self' font_) $
        lowerFlex flex font_ (map flexOptions childs')
            (flip map childs $ finalizeCSS root font_) (map style2font childs')
    } = LayoutFlow (inner' font_ self' ) (finalizeBox self' font_) [
        LayoutFlex temp $ lowerFlex flex font_ (map flexOptions childs')
            (flip map childs $ finalizeCSS root font_) (map style2font childs')]
  where
    font_ = style2font self'
    style2font style = pattern2font (font style) (font' style) parent root

M Graphics/Layout/Flex.hs => Graphics/Layout/Flex.hs +142 -51
@@ 1,10 1,10 @@
module Graphics.Layout.Flex(FlexParent(..), FlexChild(..),
    Direction(..), FlexWrapping(..), Justification(..), Alignment(..),
    flexMaxBasis, flexSumBasis, flexWrap) where
module Graphics.Layout.Flex where

import Graphics.Layout.Box as B (Length, lowerLength, Size(..), PaddedBox(..),
        maxWidth, width, minWidth, maxHeight, height, minHeight)
import Graphics.Layout.Box as B (Length(..), lowerLength, Size(..), PaddedBox(..),
        maxWidth, width, minWidth, maxHeight, height, minHeight, CastDouble(..), Zero(..))
import Data.List (intersperse)
import GHC.Real (infinity)
import Data.Maybe (fromMaybe)

data FlexParent a b = FlexParent {
    direction :: Direction,


@@ 14,7 14,8 @@ data FlexParent a b = FlexParent {
    alignLines :: Maybe Justification, -- `Nothing` is "stretch"
    baseGap :: b,
    crossGap :: b,
    children :: [[FlexChild a b]] -- 2D list to store lines once split.
    children :: [[FlexChild a b]], -- 2D list to store lines once split.
    pageWidth :: Double -- Pagination argument
} deriving (Eq, Show, Read)
data FlexChild a b = FlexChild {
    grow :: Double,


@@ 31,59 32,81 @@ data Justification = JStart | JEnd | JCenter | JSpaceBetween | JSpaceAround | JS
data Alignment = AlStretch | AlStart | AlEnd | AlCenter | AlBaseline
    deriving (Eq, Show, Read)

flexMaxBasis :: FlexParent a Length -> Double -> Double
flexMaxBasis self outersize = maximum [lowerLength outersize $ basis child |
        row <- children self, child <- row]
flexSumBasis :: FlexParent a Length -> Double -> Double
flexSumBasis self size = maximum [Prelude.sum $ map (lowerLength size) $
flexMap :: (a -> b) -> FlexParent a c -> FlexParent b c
flexMap cb self = FlexParent {
    direction = direction self, reverseRows = reverseRows self, wrap = wrap self,
    justify = justify self, alignLines = alignLines self,
    baseGap = baseGap self, crossGap = crossGap self, pageWidth = pageWidth self,
    children = [[FlexChild {
        grow = grow kid, shrink = shrink kid, basis = basis kid,
        alignment = alignment kid,
        flexInner = cb $ flexInner kid -- The important line!
    } | kid <- row] | row <- children self]
  }
flexResolve :: CastDouble b => (a -> Direction -> Double) -> Double ->
        FlexParent a b -> FlexParent a Double
flexResolve cb size self = FlexParent {
    direction = direction self, reverseRows = reverseRows self, wrap = wrap self,
    justify = justify self, alignLines = alignLines self,
    baseGap = toDoubleWithin size $ baseGap self,
    crossGap = toDoubleWithin size $ crossGap self,
    pageWidth = pageWidth self,
    children = [[FlexChild {
        grow = grow kid, shrink = shrink kid,
        basis = toDoubleWithinAuto (flexInner kid `cb` direction self) size $ basis kid,
        alignment = alignment kid, flexInner = flexInner kid
    } | kid <- row] | row <- children self]
  }

flexMaxBasis :: FlexParent a Double -> Double
flexMaxBasis self = maximum [basis child | row <- children self, child <- row]
flexSumBasis :: FlexParent a Double -> Double
flexSumBasis self = maximum [Prelude.sum $
        intersperse (baseGap self) $ map basis row | row <- children self]

flexWrap :: FlexParent a Length -> Double -> FlexParent a Double
flexWrap :: CastDouble b => FlexParent a b -> Double -> FlexParent a b
flexWrap self size
    | NoWrap <- wrap self = post self'
    | Wrap <- wrap self = post wrapped
    | WrapReverse <- wrap self = post wrapped {children=reverse$children wrapped}
    | NoWrap <- wrap self = post self
    | Wrap <- wrap self = post self'
    | WrapReverse <- wrap self = post self' { children=reverse $ children self' }
  where
    self' = FlexParent {
        direction = direction self,
        reverseRows = reverseRows self,
        wrap = wrap self,
        justify = justify self,
        alignLines = alignLines self,
        baseGap = lowerLength size $ baseGap self,
        crossGap = lowerLength size $ crossGap self,
        children = map (map $ child' size) $ children self
      }
    child' size x = FlexChild {
        grow = grow x, shrink = shrink x,
        basis = lowerLength size $ basis x,
        alignment = alignment x, flexInner = flexInner x
      }
    wrapped = self' {
        children = concatMap wrapRow $ children self'
    self' = self {
        children = concatMap wrapRow $ children self
    }
    wrapRow :: [FlexChild a Double] -> [[FlexChild a Double]]
    wrapRow :: CastDouble b => [FlexChild a b] -> [[FlexChild a b]]
    wrapRow [] = []
    wrapRow kids@(kid:_) = let (row, rest) = splitRow kids $ basis kid
        in (row):wrapRow rest
    wrapRow kids@(kid:_) = let (row, rest) = splitRow kids $ basis' kid
        in row:wrapRow rest
    splitRow :: CastDouble b => [FlexChild a b] -> Double -> ([FlexChild a b], [FlexChild a b])
    splitRow (kid:kids) end
        | end > size = ([], kid:kids)
        | otherwise = let (kids', rest) = splitRow kids (end + baseGap self' + basis kid)
        | otherwise = let (kids', rest) = splitRow kids (end + baseGap' self + basis' kid)
            in (kid:kids', rest)
    splitRow [] _ = ([], [])

    post :: CastDouble b => FlexParent a b -> FlexParent a b
    post flex
        | reverseRows self = post' flex { children = map reverse $ children flex }
        | otherwise = post' flex
    post' :: CastDouble b => FlexParent a b -> FlexParent a b
    post' flex = flex { children = map resizeRow $ children flex }
    resizeRow :: CastDouble b => [FlexChild a b] -> [FlexChild a b]
    resizeRow row
        | rowSize > size = [kid { basis = basis kid - shrink kid * sfr } | kid <- row]
        | rowSize < size = [kid { basis = basis kid + grow kid * gfr } | kid <- row]
        | rowSize > size = [kid {
                basis = fromDouble $ basis' kid - shrink kid * sfr
            } | kid <- row]
        | rowSize < size = [kid {
                basis = fromDouble $ basis' kid + grow kid * gfr
            } | kid <- row]
        | otherwise = row
      where
        rowSize = Prelude.sum $ intersperse (baseGap self') $ map basis row
        rowSize = Prelude.sum $ intersperse (baseGap' self) $ map basis' row
        sfr = (rowSize - size)/(Prelude.sum $ map shrink row)
        gfr = (size - rowSize)/(Prelude.sum $ map grow row)
    baseGap' :: CastDouble b => FlexParent a b -> Double
    baseGap' = toDouble . baseGap
    basis' :: CastDouble b => FlexChild a b -> Double
    basis' = toDouble . basis

flexRowSize :: (a -> Double) -> [FlexChild a b] -> Double
flexRowSize cb row = maximum $ map (cb . flexInner) row


@@ 98,12 121,22 @@ justifyOffset outersize ks g JCenter = half $ outersize - innersize g ks
justifyOffset _ _ _ JSpaceBetween = 0
justifyOffset outersize ks g JSpaceAround =
    half $ (outersize - innersize g ks)/length' ks
justifyOffset _ ks _ _ | length ks <= 1 = 0 -- No gaps to space, avoid numeric errors.
justifyOffset size ks g JSpaceEvenly = (size - innersize g ks)/(length' ks + 1)
justifySpacing size ks g JSpaceBetween = (size - innersize g ks)/(length' ks - 1)
justifySpacing size ks g JSpaceAround = (size - innersize g ks)/length' ks
justifySpacing size ks g JSpaceEvenly = (size - innersize g ks)/(length' ks + 1)
justifySpacing _ _ _ _ = 0

flexJustify :: (a -> Double) -> Double -> [a] -> Double -> Justification -> [(Double, a)]
flexJustify cb size kids gap just = inner kids offs
  where
    offs = justifyOffset size kids' gap just
    spacing = justifySpacing size kids' gap just
    kids' = map cb kids
    inner (k:ks) start = (start, k):inner ks (start + cb k + gap)
    inner [] _ = []

alignOffset :: Double -> Double -> Alignment -> Double
alignOffset _ _ AlStretch = 0 -- Needs special handling elsewhere
alignOffset _ _ AlStart = 0


@@ 137,17 170,75 @@ outerMaxCross box Row = maxHeight box
outerMaxCross box Column = maxWidth box

innerMinMain, innerMain, innerMaxMain :: Num m => PaddedBox m m -> Direction -> m
innerMinMain box = innerMain' $ B.min box
innerMain box = innerMain' $ B.size box
innerMaxMain box = innerMain' $ B.max box
innerMinMain box = sizeMain $ B.min box
innerMain box = sizeMain $ B.size box
innerMaxMain box = sizeMain $ B.max box

innerMinCross, innerCross, innerMaxCross :: Num m => PaddedBox m m -> Direction -> m
innerMinCross box = innerCross' $ B.min box
innerCross box = innerCross' $ B.size box
innerMaxCross box = innerCross' $ B.max box

innerMain', innerCross' :: Num m => Size m m -> Direction -> m
innerMain' self Row = inline self
innerMain' self Column = block self
innerCross' self Row = block self
innerCross' self Column = inline self
innerMinCross box = sizeCross $ B.min box
innerCross box = sizeCross $ B.size box
innerMaxCross box = sizeCross $ B.max box

sizeMain, sizeCross :: Num m => Size m m -> Direction -> m
sizeMain self Row = inline self
sizeMain self Column = block self
sizeCross self Row = block self
sizeCross self Column = inline self

flexGetBox :: (Zero m, CastDouble m, Zero n, CastDouble n) =>
    (a -> PaddedBox Double Double) -> FlexParent a m -> PaddedBox m n
flexGetBox cb self = zero {
    B.min = flexMaxBasis self' `size` flexRowsSize (cb' innerMinCross) self',
    B.max = fromRational infinity `size` fromRational infinity,
    B.nat = flexSumBasis self' `size` flexRowsSize (cb' innerCross) self',
    B.size = flexSumBasis self' `size` flexRowsSize (cb' innerCross) self'
  } where
    size main cross
        | Row <- direction self = fromDouble main `Size` fromDouble cross
        | otherwise = fromDouble cross `Size` fromDouble main
    cb' cb_ = flip cb_ (direction self) . cb
    self' = flexResolve (innerMain . cb) 0 self

flexSplit :: (a -> Size Double Double) -> Double -> Double -> FlexParent a Double ->
    (FlexParent a Double, FlexParent a Double)
flexSplit cb h _ self@FlexParent { direction = Row, pageWidth = w } =
    (self' { children = page0 }, self' { children = page1 })
  where
    self' = flexWrap self w
    (page0, page1) = splitRows (-crossGap self) $ children self
    splitRows start (row:rows)
        | start > h = ([row], rows)
        | otherwise =
            let (rows', rest) = flip splitRows rows $
                    start + crossGap self + flexRowSize (inline . cb) row
            in (row:rows', rest)
    splitRows _ [] = ([], [])
flexSplit cb h h' self@FlexParent { direction = Column, pageWidth = w }
    | measure h = (flexWrap self h, self { children = [] })
    -- If it fits on neither page... Row-direction is more versatile!
    | not $ measure h' = flexSplit cb h h' self { direction = Row }
    | otherwise = (self { children = [] }, flexWrap self h')
  where
    measure space = (block . cb) `flexRowsSize` flexWrap self space <= w

flexPosition :: ((Double, Double) -> a -> b) -> (a -> Size Double Double) ->
        (Double, Double) -> Size Double Double ->
        FlexParent a Double -> FlexParent b Double
flexPosition cb cb' (x,y) size self@FlexParent { direction = dir } = self {
    children = map rowPosition $ flexJustify rowsize (sizeCross size dir)
            (children self) (crossGap self) (justify self)
  } where
    rowsize = flexRowSize $ flip sizeCross dir . cb'
    -- TODO: Handle stretch properly
    rowPosition (rpos, row) =
        let rsize = flexRowSize (flip sizeCross dir . cb') row
        in map (alignChild rsize rpos) $ flexJustify basis rsize row
                (baseGap self) (fromMaybe JSpaceAround $ alignLines self)
    alignChild rsize rpos (kpos, kid@FlexChild {
        flexInner = kid', alignment = align'
      }) = kid {
        flexInner = flip cb kid' $ sz kpos $
                rpos + alignOffset rsize (flip sizeCross dir $ cb' kid') align'
      }
    sz m c | Row <- direction self = (x + m, y + c)
        | otherwise = (x + c, y + m)

M Graphics/Layout/Flex/CSS.hs => Graphics/Layout/Flex/CSS.hs +1 -0
@@ 183,6 183,7 @@ lowerFlex self font kids kids' fonts' = FlexParent {
    crossGap = case directionCSS self of
        Row -> flip finalizeLength font $ columnGapCSS self
        F.Column -> flip finalizeLength font $ rowGapCSS self,
    pageWidth = 0,
    children = [[FlexChild {
        grow = growCSS kid,
        shrink = shrinkCSS kid,