~alcinnz/CatTrap

c4626b0b1ba761355ad57c1657d92e0d25f96cb9 — Adrian Cochrane 9 months ago 4a889fb
Incorporate inline-blocks/etc into the layout model.
M Graphics/Layout.hs => Graphics/Layout.hs +51 -22
@@ 39,7 39,7 @@ import Data.Text.Array (Array(..))
import Unsafe.Coerce (unsafeCoerce)

-- | Additional data routed through Balkon.
type UserData m n x = ((Font', Int), PaddedBox m n, x)
type UserData m n x = ((Font', Int), Either (PaddedBox m n) (LayoutItem m n x), x)

-- | A tree of different layout algorithms.
-- More to come...


@@ 69,7 69,7 @@ instance (Zero m, CastDouble m, NFData m, Zero n, CastDouble n, NFData n) =>
    rnf = rnf . layoutGetBox -- Avoid auxiliary properties that don't cleanly `rnf`

-- | Retrieve the surrounding box for a layout item.
layoutGetBox :: (Zero m, Zero n, CastDouble m, CastDouble n) =>
layoutGetBox, b :: (Zero m, Zero n, CastDouble m, CastDouble n) =>
        LayoutItem m n x -> PaddedBox m n
layoutGetBox (LayoutFlow _ ret _) = ret
layoutGetBox (LayoutGrid _ self _ _) = zero {


@@ 81,14 81,19 @@ layoutGetBox (LayoutGrid _ self _ _) = zero {
            (fromDouble $ trackNat toDouble $ block self)
}
layoutGetBox (LayoutInline _ self _) = zero {
    B.min = inlineMin self, B.size = inlineSize self, B.max = inlineSize self
}
    B.min = inlineMin b self, B.size = inlineSize b self, B.max = inlineSize b self
  }
layoutGetBox (LayoutInline' _ self _) = zero {
    B.min = layoutSize self, B.size = layoutSize self, B.max = layoutSize self
}
layoutGetBox (LayoutSpan self) = treeBox self
layoutGetBox (LayoutSpan self) = treeBox $ treeMap layoutGetBoxRight self
layoutGetBox (LayoutConst _ ret _) = ret
layoutGetBox (LayoutFlex _ self) = flexGetBox layoutGetBox' self

b = layoutGetBox
layoutGetBoxRight (Right self) = layoutGetBox self
layoutGetBoxRight (Left ret) = ret

layoutGetBox' :: (Zero m, Zero n, CastDouble m, CastDouble n) =>
        LayoutItem m n x -> PaddedBox Double Double
layoutGetBox' = mapX' toDouble . mapY' toDouble . layoutGetBox


@@ 97,7 102,7 @@ layoutGetChilds (LayoutFlow _ _ ret) = ret
layoutGetChilds (LayoutGrid _ _ _ ret) = ret
layoutGetChilds (LayoutSpan (Leaf _)) = []
layoutGetChilds (LayoutSpan (Branch _ ret)) = map LayoutSpan ret
layoutGetChilds (LayoutInline _ self _) = map LayoutSpan $ inlineChildren self
layoutGetChilds (LayoutInline _ self _) = map LayoutSpan $ inlineChildren b self
layoutGetChilds (LayoutInline' _ self _) = map LayoutSpan $ layoutChildren self
layoutGetChilds (LayoutConst _ _ childs) = childs
layoutGetChilds (LayoutFlex _ x) = map Fl.flexInner $ concat $ Fl.children x


@@ 219,17 224,27 @@ boxWidth parent (LayoutGrid val self cells childs) = LayoutGrid val self' cells'
    }
    outerwidth = inline $ size parent
    widths = sizeTrackMaxs (inline $ size parent) $ inline self
boxWidth parent (LayoutInline val (Paragraph a b c d) paging) =
    LayoutInline val (paragraphMap (mapX' $ lowerLength width) $
        Paragraph a b c d { paragraphMaxWidth = round width }) paging
  where width = B.inline $ B.size parent
boxWidth p (LayoutInline' a b c) =
    LayoutInline' a (layoutMap (mapX' $ lowerLength $ B.inline $ B.size p) b) c
boxWidth parent self@(LayoutInline val (Paragraph a b c d) paging) =
    LayoutInline val (paragraphMap inner $ Paragraph a b c d {
        paragraphMaxWidth = round $ B.inline $ B.size parent
      }) paging
  where
    inner (Left a) = Left $ resolveWidth a
    inner (Right a) = Right $ boxWidth (resolveWidth $ layoutGetBox self) a
    resolveWidth = mapX' $ lowerLength $ B.inline $ B.size parent
boxWidth parent self@(LayoutInline' a b c) = LayoutInline' a (layoutMap inner b) c
  where
    inner (Left a) = Left $ resolveWidth a
    inner (Right a) = Right $ boxWidth (resolveWidth $ layoutGetBox self) a
    resolveWidth = mapX' $ lowerLength $ B.inline $ B.size parent
boxWidth p (LayoutConst val self childs) = LayoutConst val (mapX' cb self) $
    map (boxWidth $ mapY' toDouble $ mapX' cb self) childs
  where cb = lowerLength $ width p
boxWidth parent (LayoutSpan self') =
    LayoutSpan $ treeMap (mapX' $ lowerLength $ width parent) self'
boxWidth parent self@(LayoutSpan self') = LayoutSpan $ treeMap inner self'
  where
    inner (Left a) = Left $ resolveWidth a
    inner (Right a) = Right $ boxWidth (resolveWidth $ layoutGetBox self) a
    resolveWidth = mapX' $ lowerLength $ B.inline $ B.size parent
boxWidth parent (LayoutFlex a b) = LayoutFlex a $ flexMap (boxWidth parent) b

-- | Update a (sub)tree to compute & cache ideal legible height.


@@ 326,15 341,26 @@ boxHeight parent (LayoutGrid val self cells0 childs) = LayoutGrid val self' cell
    lowerSize (Left x) = Left $ lowerLength width x
    lowerSize (Right x) = Right x
    width = inline $ size parent
boxHeight p (LayoutInline val self' paging) =
    LayoutInline val (paragraphMap (mapY' $ lowerLength $ width p) self') paging
boxHeight p (LayoutInline' val self' paging) =
    LayoutInline' val (layoutMap (mapY' $ lowerLength $ width p) self') paging
boxHeight parent self@(LayoutInline val self' paging) =
    LayoutInline val (paragraphMap inner self') paging
  where
    inner (Left a) = Left $ resolveHeight a
    inner (Right a) = Right $ boxHeight (resolveHeight $ layoutGetBox self) a
    resolveHeight = mapY' $ lowerLength $ B.inline $ B.size parent
boxHeight parent self@(LayoutInline' val self' paging) =
    LayoutInline' val (layoutMap inner self') paging
  where
    inner (Left a) = Left $ resolveHeight a
    inner (Right a) = Right $ boxHeight (resolveHeight $ layoutGetBox self) a
    resolveHeight = mapY' $ lowerLength $ B.inline $ B.size parent
boxHeight p (LayoutConst val self childs) =
    let self' = mapY' (lowerLength $ width p) self
    in LayoutConst val self' $ map (boxHeight self') childs
boxHeight p (LayoutSpan self') =
    LayoutSpan $ treeMap (mapY' $ lowerLength $ width p) self'
boxHeight parent self@(LayoutSpan self') = LayoutSpan $ treeMap inner self'
  where
    inner (Left a) = Left $ resolveHeight a
    inner (Right a) = Right $ boxHeight (resolveHeight $ layoutGetBox self) a
    resolveHeight = mapY' $ lowerLength $ B.inline $ B.size parent
boxHeight p (LayoutFlex a b) = LayoutFlex a $
    flexResolve (innerMain . layoutGetBox) (width p) $ flexMap (boxHeight p) b



@@ 421,7 447,10 @@ boxPosition pos@(x, y) self@(LayoutInline' val _ _) =
    boxPosition pos $ LayoutConst val (layoutGetBox self) $ layoutGetChilds self
boxPosition pos (LayoutConst val self childs) =
    LayoutConst (pos, val) self $ parMap' (boxPosition pos) childs
boxPosition pos (LayoutSpan self) = LayoutSpan $ positionTree pos self
boxPosition pos (LayoutSpan self) = LayoutSpan $ positionTree pos inner self
  where
    inner _ (Left ret) = Left ret
    inner pos' (Right kid) = Right $ boxPosition pos' kid
-- | Compute sizes & position information for all nodes in the (sub)tree.
boxLayout :: (PropertyParser x, Eq x) => PaddedBox Double Double ->
        LayoutItem Length Length x -> Bool -> 


@@ 443,7 472,7 @@ boxLayout parent self paginate = self9
-- | Compute a mapping from a layout tree indicating which glyphs for which fonts
-- are required.
-- Useful for assembling glyph atlases.
glyphsPerFont :: (CastDouble x, CastDouble y, Eq x, Eq y, Eq z) =>
glyphsPerFont :: (CastDouble x, CastDouble y, Eq x, Eq y, Eq z, Zero x, Zero y) =>
        LayoutItem x y z -> M.Map (Pattern, Double) IS.IntSet
glyphsPerFont (LayoutSpan self@(Leaf _)) | (_:_) <- glyphs =
        (pattern font, fontSize font) `M.singleton` IS.fromList glyphs

M Graphics/Layout/CSS.hs => Graphics/Layout/CSS.hs +13 -5
@@ 10,7 10,7 @@ import qualified Data.Text as Txt
import Stylist (PropertyParser(..))
import Stylist.Tree (StyleTree(..))
import Data.Text.ParagraphLayout.Rich (constructParagraph, defaultBoxOptions,
        LineHeight(..), InnerNode(..), Box(..), RootNode(..))
        LineHeight(..), InnerNode(..), Box(..), RootNode(..), strut)

import Graphics.Layout.Box as B
import Graphics.Layout


@@ 27,7 27,7 @@ import Graphics.Layout.CSS.Parse
import Data.Maybe (fromMaybe)

instance (PropertyParser x, Zero m, Zero n) => Default (UserData m n x) where
    def = ((placeholderFont, 0), zero, temp)
    def = ((placeholderFont, 0), Left zero, temp)

-- | Resolves length units in properties handled by downstream components.
inner' :: PropertyParser x => Font' -> CSSBox x -> x


@@ 109,6 109,7 @@ finalizeChilds root parent style' childs@(child:_)
    isInlineTree = all isInlineTree0
    isInlineTree0 StyleTree { style = CSSBox { display = Inline }, children = childs } =
        isInlineTree childs
    isInlineTree0 StyleTree { style = CSSBox { display = Inline' _ } } = True
    isInlineTree0 _ = False
    spanInlines childs = case span isInlineTree0 childs of
        (inlines, (StyleTree {


@@ 116,21 117,28 @@ finalizeChilds root parent style' childs@(child:_)
          }:blocks)) -> let (inlines', blocks') = spanInlines tail
            in (inlines ++ inlines', blocks' ++ blocks)
        ret -> ret
    flattenTree0 childs
    flattenTree0 = strut def . flattenTree0'
    flattenTree0' childs
        | iStyle@(CSSInline _ _ bidi) <- inlineStyles style',
            bidi `elem` [BdOverride, BdIsolateOverride] = RootBox $ Box
                (applyBidi iStyle $ map (flattenTree parent) $ enumerate childs)
                $ flip applyFontInline parent $ txtOpts style'
        | otherwise = RootBox $ Box (map (flattenTree parent) $ enumerate childs)
            $ flip applyFontInline parent $ txtOpts style'
    flattenTree p (i, StyleTree self@CSSBox { display = Inline' disp' } kids) =
        buildInline' (pattern2font (font self) (font' self) p root) i
            (Right $ finalizeCSS root p $ StyleTree self { display = disp' } kids)
            self []
    flattenTree p (i, StyleTree self child@(_:_)) =
        buildInline f i self $ map (flattenTree f) $ enumerate child
      where f = pattern2font (font self) (font' self) p root
    flattenTree f (i,StyleTree {style=self@CSSBox {inlineStyles=CSSInline txt _ _}})
        = buildInline f i self [
            TextSequence ((f, 0), zero, inherit $ inner' parent self) txt]
            TextSequence ((f, 0), Left zero, inherit $ inner' parent self) txt]
    buildInline f i self childs =
        InlineBox ((f, i), finalizeBox self f, inner' parent self)
        buildInline' f i (Left $ finalizeBox self f) self childs
    buildInline' f i val self childs =
        InlineBox ((f, i), val, inner' parent self)
                (Box childs' $ flip applyFontInline f $ txtOpts self)
                $ resolveBoxOpts f (tableOptions self)
      where childs' = applyBidi (inlineStyles self) childs

M Graphics/Layout/CSS/Parse.hs => Graphics/Layout/CSS/Parse.hs +14 -1
@@ 73,7 73,7 @@ noborder = Border (0,"px") (0,"px") (0,"px") (0,"px")
-- | Possibly values for CSS display property.
data Display = Block | Grid | Inline | Table | None |
    TableRow | TableHeaderGroup | TableRowGroup | TableFooterGroup | TableCell |
    TableColumn | TableColumnGroup | TableCaption | Flex deriving Eq
    TableColumn | TableColumnGroup | TableCaption | Flex | Inline' Display deriving Eq
-- | Can the display value contain table-rows?
rowContainer CSSBox { display = d } =
    d `elem` [Table, TableHeaderGroup, TableRowGroup, TableFooterGroup]


@@ 263,6 263,19 @@ instance PropertyParser a => PropertyParser (CSSBox a) where
        Just self { display=TableCaption }
    longhand _ self "display" [Ident "inline"] = Just self { display = Inline }
    longhand _ self "display" [Ident "flex"] = Just self { display = Flex }
    longhand _ self "display" [Ident "inline-block"] = Just self { display = Inline' Block }
    longhand _ self "display" [Ident "inline", Ident "block"] =
        Just self { display = Inline' Block }
    longhand _ self "display" [Ident "inline-flex"] = Just self { display = Inline' Flex }
    longhand _ self "display" [Ident "inline", Ident "flex"] =
        Just self { display = Inline' Flex }
    longhand _ self "display" [Ident "inline-grid"] = Just self { display = Inline' Grid }
    longhand _ self "display" [Ident "inline", Ident "grid"] =
        Just self { display = Inline' Grid }
    longhand _ self "display" [Ident "inline-table"] = Just self { display = Inline' Table }
    longhand _ self "display" [Ident "inline", Ident "table"] =
        Just self { display = Inline' Table}
    longhand p self "display" (Ident "block":v) = longhand p self "display" v
    longhand _ self "display" [Ident "initial"] = Just self { display = Inline }

    longhand _ self "orphans" [Number _ (NVInteger x)] =

M Graphics/Layout/Inline.hs => Graphics/Layout/Inline.hs +38 -30
@@ 33,19 33,21 @@ unscale :: CastDouble x => x -> Int32
unscale = floor . (*hbUnit) . toDouble

-- | Compute minimum width & height for some richtext.
inlineMin :: (CastDouble x, CastDouble y) =>
        Paragraph (a, PaddedBox x y, c) -> Size x y
inlineMin = layoutSize' . flip layoutRich' 0
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) =>
        Paragraph (a, PaddedBox x y, c) -> Size x y
inlineSize self@(Paragraph _ _ _ opts) =
    layoutSize' . layoutRich' self $ paragraphMaxWidth opts
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) =>
        Paragraph (a, PaddedBox x y, c) -> [FragmentTree (a, PaddedBox x y, c)]
inlineChildren self = layoutChildren $ layoutRich $ lowerSpacing self
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


@@ 57,23 59,26 @@ layoutChildren self = reconstructTree self

-- | Layout a paragraph at given width & retrieve resulting rect.
-- LEGACY.
layoutRich' :: (CastDouble m, CastDouble n) =>
        Paragraph (a, PaddedBox m n, c) -> Int32 -> Rect Int32
layoutRich' (Paragraph a b c d) width =
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$Paragraph a b c d {paragraphMaxWidth=width}
    layout = layoutRich $ lowerSpacing cb $ Paragraph a b c d {
        paragraphMaxWidth = width
      }

-- | Copy surrounding whitespace into Balkon properties.
lowerSpacing :: (CastDouble m, CastDouble n) =>
    Paragraph (a, PaddedBox m n, c) -> Paragraph (a, PaddedBox m n, c)
lowerSpacing (Paragraph a b (RootBox c) d) = Paragraph a b (RootBox $ inner c) d
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@(_, f, _) child opts) = InlineBox e (inner child) $
        flip activateBoxSpacing opts $
            BoxSpacingLeftRight (leftSpace box) (rightSpace box)
      where box = mapX' unscale $ mapY' unscale f
      where box = mapX' unscale $ mapY' unscale $ mapRight cb f
    inner' self@(TextSequence _ _) = self




@@ 120,13 125,9 @@ fragmentSize :: (CastDouble x, CastDouble y) =>
fragmentSize self = Size (c $ width r) (c $ height r)
    where r = treeRect self
-- | Compute the unioned rect for a subtree.
treeRect :: (CastDouble m, CastDouble n) =>
        FragmentTree (a, PaddedBox m n, c) -> Rect Int32
treeRect :: FragmentTree (a, b, c) -> Rect Int32
treeRect (Branch AncestorBox { boxUserData = (_, box', _)} childs) =
        unions $ map treeRect childs
    where
        box :: PaddedBox Int32 Int32
        box = mapX' unscale $ mapY' unscale box'
treeRect (Leaf self) = fragmentRect self

-- | Compute the paddedbox for a subtree.


@@ 182,16 183,16 @@ reconstructTree' frags@(Fragment {
reconstructTree' [] = []

-- | Add an X,Y offset to all positions, annotating the userdata.
positionTree :: (CastDouble m, CastDouble n) => (Double, Double) ->
        FragmentTree (a, PaddedBox m n, c) ->
        FragmentTree (a, PaddedBox m n, ((Double, Double), c))
positionTree (x, y) self@(Branch (AncestorBox (a, b, c) d e f g) childs) =
    Branch (AncestorBox (a, b, (pos, c)) d e f g) $ map (positionTree pos) childs
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) self@(Leaf (Fragment (a, b, c) d _ f g h i)) =
    Leaf (Fragment (a, b, (pos, c)) d [] f g h i)
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


@@ 234,3 235,10 @@ union a b = Rect x_low y_high dx (-dy) where
    y_high = y_max a `max` y_max b
    dx = x_high - x_low
    dy = y_high - y_low

------
--- Supporting utils
------

mapRight cb (Right self) = cb self
mapRight _ (Left ret) = ret

M cattrap.cabal => cattrap.cabal +1 -1
@@ 2,7 2,7 @@
-- documentation, see http://haskell.org/cabal/users-guide/

name:                cattrap
version:             0.4.0.0
version:             0.5.0.0
synopsis:            Lays out boxes according to the CSS Box Model.
description:         Computes where to place e.g. images, paragraphs, containers, tables, etc onscreen given desired amounts of whitespace.
homepage:            https://argonaut-constellation.org/