cattrap 0.4.0.0 → 0.5.0.0
raw patch · 10 files changed
+1004/−20 lines, 10 filesdep ~xml-conduitPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: xml-conduit
API changes (from Hackage documentation)
+ Graphics.Layout: LayoutFlex :: x -> Flex (LayoutItem m n x) m -> LayoutItem m n x
+ Graphics.Layout.Box: toDoubleWithin :: CastDouble a => Double -> a -> Double
+ Graphics.Layout.Box: toDoubleWithinAuto :: CastDouble a => Double -> Double -> a -> Double
+ Graphics.Layout.CSS: Flex :: Display
+ Graphics.Layout.CSS: [flexOptions] :: CSSBox a -> CSSFlex
+ Graphics.Layout.Flex: AlBaseline :: Alignment
+ Graphics.Layout.Flex: AlCenter :: Alignment
+ Graphics.Layout.Flex: AlEnd :: Alignment
+ Graphics.Layout.Flex: AlStart :: Alignment
+ Graphics.Layout.Flex: AlStretch :: Alignment
+ Graphics.Layout.Flex: Column :: Direction
+ Graphics.Layout.Flex: Flex :: Direction -> Bool -> FlexWrapping -> Justification -> Maybe Justification -> b -> b -> [[FlexChild a b]] -> Double -> Flex a b
+ Graphics.Layout.Flex: FlexChild :: Double -> Double -> b -> Alignment -> a -> FlexChild a b
+ Graphics.Layout.Flex: JCenter :: Justification
+ Graphics.Layout.Flex: JEnd :: Justification
+ Graphics.Layout.Flex: JSpaceAround :: Justification
+ Graphics.Layout.Flex: JSpaceBetween :: Justification
+ Graphics.Layout.Flex: JSpaceEvenly :: Justification
+ Graphics.Layout.Flex: JStart :: Justification
+ Graphics.Layout.Flex: NoWrap :: FlexWrapping
+ Graphics.Layout.Flex: Row :: Direction
+ Graphics.Layout.Flex: Wrap :: FlexWrapping
+ Graphics.Layout.Flex: WrapReverse :: FlexWrapping
+ Graphics.Layout.Flex: [alignLines] :: Flex a b -> Maybe Justification
+ Graphics.Layout.Flex: [alignment] :: FlexChild a b -> Alignment
+ Graphics.Layout.Flex: [baseGap] :: Flex a b -> b
+ Graphics.Layout.Flex: [basis] :: FlexChild a b -> b
+ Graphics.Layout.Flex: [children] :: Flex a b -> [[FlexChild a b]]
+ Graphics.Layout.Flex: [crossGap] :: Flex a b -> b
+ Graphics.Layout.Flex: [direction] :: Flex a b -> Direction
+ Graphics.Layout.Flex: [flexInner] :: FlexChild a b -> a
+ Graphics.Layout.Flex: [grow] :: FlexChild a b -> Double
+ Graphics.Layout.Flex: [justify] :: Flex a b -> Justification
+ Graphics.Layout.Flex: [pageWidth] :: Flex a b -> Double
+ Graphics.Layout.Flex: [reverseRows] :: Flex a b -> Bool
+ Graphics.Layout.Flex: [shrink] :: FlexChild a b -> Double
+ Graphics.Layout.Flex: [wrap] :: Flex a b -> FlexWrapping
+ Graphics.Layout.Flex: alignOffset :: Double -> Double -> Alignment -> Double
+ Graphics.Layout.Flex: data Alignment
+ Graphics.Layout.Flex: data Direction
+ Graphics.Layout.Flex: data Flex a b
+ Graphics.Layout.Flex: data FlexChild a b
+ Graphics.Layout.Flex: data FlexWrapping
+ Graphics.Layout.Flex: data Justification
+ Graphics.Layout.Flex: flexGetBox :: (Zero m, CastDouble m, Zero n, CastDouble n) => (a -> PaddedBox Double Double) -> Flex a m -> PaddedBox m n
+ Graphics.Layout.Flex: flexJustify :: (a -> Double) -> Double -> [a] -> Double -> Justification -> [(Double, a)]
+ Graphics.Layout.Flex: flexMap :: (a -> b) -> Flex a c -> Flex b c
+ Graphics.Layout.Flex: flexMaxBasis :: Flex a Double -> Double
+ Graphics.Layout.Flex: flexPosition :: ((Double, Double) -> a -> b) -> (a -> Size Double Double) -> (Double, Double) -> Size Double Double -> Flex a Double -> Flex b Double
+ Graphics.Layout.Flex: flexResolve :: CastDouble b => (a -> Direction -> Double) -> Double -> Flex a b -> Flex a Double
+ Graphics.Layout.Flex: flexRowSize :: (a -> Double) -> [FlexChild a b] -> Double
+ Graphics.Layout.Flex: flexRowsSize :: (a -> Double) -> Flex a Double -> Double
+ Graphics.Layout.Flex: flexSplit :: (a -> Size Double Double) -> Double -> Double -> Flex a Double -> (Flex a Double, Flex a Double)
+ Graphics.Layout.Flex: flexSumBasis :: Flex a Double -> Double
+ Graphics.Layout.Flex: flexWrap :: CastDouble b => Flex a b -> Double -> Flex a b
+ Graphics.Layout.Flex: innerCross :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: innerMain :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: innerMaxCross :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: innerMaxMain :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: innerMinCross :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: innerMinMain :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: instance (GHC.Classes.Eq b, GHC.Classes.Eq a) => GHC.Classes.Eq (Graphics.Layout.Flex.Flex a b)
+ Graphics.Layout.Flex: instance (GHC.Classes.Eq b, GHC.Classes.Eq a) => GHC.Classes.Eq (Graphics.Layout.Flex.FlexChild a b)
+ Graphics.Layout.Flex: instance (GHC.Read.Read b, GHC.Read.Read a) => GHC.Read.Read (Graphics.Layout.Flex.Flex a b)
+ Graphics.Layout.Flex: instance (GHC.Read.Read b, GHC.Read.Read a) => GHC.Read.Read (Graphics.Layout.Flex.FlexChild a b)
+ Graphics.Layout.Flex: instance (GHC.Show.Show b, GHC.Show.Show a) => GHC.Show.Show (Graphics.Layout.Flex.Flex a b)
+ Graphics.Layout.Flex: instance (GHC.Show.Show b, GHC.Show.Show a) => GHC.Show.Show (Graphics.Layout.Flex.FlexChild a b)
+ Graphics.Layout.Flex: instance GHC.Classes.Eq Graphics.Layout.Flex.Alignment
+ Graphics.Layout.Flex: instance GHC.Classes.Eq Graphics.Layout.Flex.Direction
+ Graphics.Layout.Flex: instance GHC.Classes.Eq Graphics.Layout.Flex.FlexWrapping
+ Graphics.Layout.Flex: instance GHC.Classes.Eq Graphics.Layout.Flex.Justification
+ Graphics.Layout.Flex: instance GHC.Read.Read Graphics.Layout.Flex.Alignment
+ Graphics.Layout.Flex: instance GHC.Read.Read Graphics.Layout.Flex.Direction
+ Graphics.Layout.Flex: instance GHC.Read.Read Graphics.Layout.Flex.FlexWrapping
+ Graphics.Layout.Flex: instance GHC.Read.Read Graphics.Layout.Flex.Justification
+ Graphics.Layout.Flex: instance GHC.Show.Show Graphics.Layout.Flex.Alignment
+ Graphics.Layout.Flex: instance GHC.Show.Show Graphics.Layout.Flex.Direction
+ Graphics.Layout.Flex: instance GHC.Show.Show Graphics.Layout.Flex.FlexWrapping
+ Graphics.Layout.Flex: instance GHC.Show.Show Graphics.Layout.Flex.Justification
+ Graphics.Layout.Flex: justifyOffset :: Double -> [Double] -> Double -> Justification -> Double
+ Graphics.Layout.Flex: justifySpacing :: Double -> [Double] -> Double -> Justification -> Double
+ Graphics.Layout.Flex: outerCross :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: outerMain :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: outerMaxCross :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: outerMaxMain :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: outerMinCross :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex: outerMinMain :: Num m => PaddedBox m m -> Direction -> m
+ Graphics.Layout.Flex.CSS: CSSFlex :: Direction -> Bool -> FlexWrapping -> Maybe Justification -> Alignment -> Maybe Justification -> Unitted -> Unitted -> Integer -> Double -> Double -> Unitted -> Alignment -> Bool -> CSSFlex
+ Graphics.Layout.Flex.CSS: [alignItemsCSS] :: CSSFlex -> Alignment
+ Graphics.Layout.Flex.CSS: [alignLinesCSS] :: CSSFlex -> Maybe Justification
+ Graphics.Layout.Flex.CSS: [alignSelfCSS] :: CSSFlex -> Alignment
+ Graphics.Layout.Flex.CSS: [basisCSS] :: CSSFlex -> Unitted
+ Graphics.Layout.Flex.CSS: [columnGapCSS] :: CSSFlex -> Unitted
+ Graphics.Layout.Flex.CSS: [directionCSS] :: CSSFlex -> Direction
+ Graphics.Layout.Flex.CSS: [growCSS] :: CSSFlex -> Double
+ Graphics.Layout.Flex.CSS: [justifyCSS] :: CSSFlex -> Maybe Justification
+ Graphics.Layout.Flex.CSS: [orderCSS] :: CSSFlex -> Integer
+ Graphics.Layout.Flex.CSS: [reverseRowsCSS] :: CSSFlex -> Bool
+ Graphics.Layout.Flex.CSS: [rowGapCSS] :: CSSFlex -> Unitted
+ Graphics.Layout.Flex.CSS: [shrinkCSS] :: CSSFlex -> Double
+ Graphics.Layout.Flex.CSS: [textRTL] :: CSSFlex -> Bool
+ Graphics.Layout.Flex.CSS: [wrapCSS] :: CSSFlex -> FlexWrapping
+ Graphics.Layout.Flex.CSS: data CSSFlex
+ Graphics.Layout.Flex.CSS: instance Stylist.PropertyParser Graphics.Layout.Flex.CSS.CSSFlex
+ Graphics.Layout.Flex.CSS: lowerFlex :: CSSFlex -> Font' -> [CSSFlex] -> [a] -> [Font'] -> Flex a Length
- Graphics.Layout: layoutGetChilds :: (CastDouble m, CastDouble n, Eq m, Eq n, Eq x) => LayoutItem m n x -> [LayoutItem m n x]
+ Graphics.Layout: layoutGetChilds :: (CastDouble b, CastDouble n, Eq b, Eq n, Eq x) => LayoutItem b n x -> [LayoutItem b n x]
- Graphics.Layout.CSS: CSSBox :: Display -> BoxSizing -> PaddedBox Unitted Unitted -> Pattern -> CSSFont -> a -> [(Text, [Token])] -> a -> CSSGrid -> CSSCell -> CSSInline -> PageOptions -> ParagraphOptions -> TableOptions -> CSSBox a
+ Graphics.Layout.CSS: CSSBox :: Display -> BoxSizing -> PaddedBox Unitted Unitted -> Pattern -> CSSFont -> a -> [(Text, [Token])] -> a -> CSSGrid -> CSSCell -> CSSInline -> PageOptions -> ParagraphOptions -> TableOptions -> CSSFlex -> CSSBox a
Files
- Graphics/Layout.hs +41/−2
- Graphics/Layout/Box.hs +8/−1
- Graphics/Layout/CSS.hs +12/−1
- Graphics/Layout/CSS/Parse.hs +22/−5
- Graphics/Layout/Flex.hs +293/−0
- Graphics/Layout/Flex/CSS.hs +212/−0
- app/Integration.hs +5/−2
- app/Integration2.hs +8/−2
- cattrap.cabal +7/−5
- test/Test.hs +396/−2
Graphics/Layout.hs view
@@ -23,6 +23,7 @@ import Graphics.Layout.Flow as F import Graphics.Layout.Inline as I import Graphics.Layout.CSS.Font (Font'(..))+import Graphics.Layout.Flex as Fl import Data.Maybe (fromMaybe) @@ -57,6 +58,7 @@ | LayoutConst x (PaddedBox m n) [LayoutItem m n x] -- | Children of a `LayoutInline` or `LayoutInline'`. | LayoutSpan (FragmentTree (UserData m n x))+ | LayoutFlex x (Flex (LayoutItem m n x) m) deriving (Show, Eq) -- | An empty box. nullLayout :: (PropertyParser x, Zero m, Zero n) => LayoutItem m n x@@ -86,13 +88,19 @@ } 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-layoutGetChilds (LayoutSpan _) = []+layoutGetChilds (LayoutSpan (Leaf _)) = []+layoutGetChilds (LayoutSpan (Branch _ ret)) = map LayoutSpan ret 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@@ -100,6 +108,7 @@ 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@@ -134,6 +143,7 @@ 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@@ -161,6 +171,7 @@ 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@@ -181,6 +192,9 @@ 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@@ -216,6 +230,7 @@ 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@@ -237,6 +252,7 @@ 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'@@ -259,6 +275,7 @@ 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@@ -282,6 +299,7 @@ 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'@@ -317,6 +335,8 @@ 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.@@ -357,6 +377,12 @@ 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 =@@ -366,6 +392,19 @@ -- | 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@@ -406,7 +445,7 @@ -- Useful for assembling glyph atlases. glyphsPerFont :: (CastDouble x, CastDouble y, Eq x, Eq y, Eq z) => LayoutItem x y z -> M.Map (Pattern, Double) IS.IntSet-glyphsPerFont (LayoutSpan self) | (_:_) <- glyphs =+glyphsPerFont (LayoutSpan self@(Leaf _)) | (_:_) <- glyphs = (pattern font, fontSize font) `M.singleton` IS.fromList glyphs | otherwise = M.empty where
Graphics/Layout/Box.hs view
@@ -181,10 +181,17 @@ 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
Graphics/Layout/CSS.hs view
@@ -20,6 +20,7 @@ import Graphics.Layout.Grid import Graphics.Layout.Grid.Table import Graphics.Layout.Inline.CSS+import Graphics.Layout.Flex.CSS import Data.Char (isSpace) import Graphics.Layout.CSS.Parse@@ -55,7 +56,7 @@ (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 {@@ -69,6 +70,16 @@ | child@StyleTree { style = child'@CSSBox { display = TableCaption } } <- childs]) where font_ = pattern2font (font self') (font' self') parent root+finalizeCSS root parent self@StyleTree {+ style = self'@CSSBox { display = Flex, flexOptions = flex },+ children = 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+ childs' = map style childs finalizeCSS root parent self@StyleTree { style = self', children = childs } = LayoutFlow (inner' font_ self') (finalizeBox self' font_)
Graphics/Layout/CSS/Parse.hs view
@@ -16,6 +16,7 @@ import Graphics.Layout.Grid.CSS (CSSGrid(..), CSSCell(..), Placement(..)) import Graphics.Layout.Grid.Table (TableOptions) import Graphics.Layout.Inline.CSS (CSSInline(..))+import Graphics.Layout.Flex.CSS (CSSFlex(..)) import Data.Maybe (isJust, fromMaybe) import Text.Read (readMaybe)@@ -54,8 +55,12 @@ paragraphOptions :: ParagraphOptions, -- | (Semi-)parsed CSS properties & HTML attributes relating to laying out -- HTML table elements.- tableOptions :: TableOptions+ tableOptions :: TableOptions,+ -- | Semi-parsed CSS properties relating to FlexBox layouts.+ flexOptions :: CSSFlex }+-- | FlexOptions getter with `textLTR` set+flexOpts' self@CSSBox { flexOptions = ret } = ret { textRTL = direction self == DirRTL } -- | Accessor for inlineStyle's `textDirection` attribute. direction CSSBox { inlineStyles = CSSInline _ opts _ } = textDirection opts -- | Accessor for inlineStyle's options.@@ -68,7 +73,7 @@ -- | Possibly values for CSS display property. data Display = Block | Grid | Inline | Table | None | TableRow | TableHeaderGroup | TableRowGroup | TableFooterGroup | TableCell |- TableColumn | TableColumnGroup | TableCaption deriving Eq+ TableColumn | TableColumnGroup | TableCaption | Flex deriving Eq -- | Can the display value contain table-rows? rowContainer CSSBox { display = d } = d `elem` [Table, TableHeaderGroup, TableRowGroup, TableFooterGroup]@@ -99,7 +104,8 @@ paragraphOptions = defaultParagraphOptions { paragraphAlignment = AlignStart },- tableOptions = temp+ tableOptions = temp,+ flexOptions = temp } inherit parent = CSSBox { boxSizing = boxSizing parent,@@ -115,9 +121,11 @@ inlineStyles = inherit $ inlineStyles parent, pageOptions = pageOptions parent, paragraphOptions = paragraphOptions parent,- tableOptions = inherit $ tableOptions parent+ tableOptions = inherit $ tableOptions parent,+ flexOptions = inherit $ flexOptions parent }- priority self = concat [x font, x font', x gridStyles, x cellStyles, x inner]+ priority self = concat [x inlineStyles, x font, x font', x gridStyles,+ x cellStyles, x flexOptions, x inner] where x getter = priority $ getter self -- Wasn't sure how to implement in FontConfig-Pure@@ -254,6 +262,7 @@ longhand CSSBox { display = Table } self "display" [Ident "table-caption"] = 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 "initial"] = Just self { display = Inline } longhand _ self "orphans" [Number _ (NVInteger x)] =@@ -294,12 +303,20 @@ } longhand a b c d | Just inline' <- longhand (inlineStyles a) (inlineStyles b) c d = Just b { inlineStyles = inline' }+ longhand a b c d | Just grid' <- longhand (gridStyles a) (gridStyles b) c d,+ Just flex' <- longhand (flexOpts' a) (flexOpts' b) c d =+ Just b { gridStyles = grid', flexOptions = flex' }+ longhand a b c d | Just cell' <- longhand (cellStyles a) (cellStyles b) c d,+ Just flex' <- longhand (flexOpts' a) (flexOpts' b) c d =+ Just b { cellStyles = cell', flexOptions = flex' } longhand a b c d | Just grid' <- longhand (gridStyles a) (gridStyles b) c d = Just b { gridStyles = grid' } longhand a b c d | Just cell' <- longhand (cellStyles a) (cellStyles b) c d = Just b { cellStyles = cell' } longhand a b c d | Just table'<-longhand (tableOptions a) (tableOptions b) c d = Just b { tableOptions = table' }+ longhand a b c d | Just flex' <- longhand (flexOpts' a) (flexOpts' b) c d =+ Just b { flexOptions = flex' } longhand a b c d | (d', _:_)<-testLengthProp d, Just _<-longhand (inner a) (inner b) c d' = Just b {
+ Graphics/Layout/Flex.hs view
@@ -0,0 +1,293 @@+-- | Layout formula positioning children horizontally or vertically, with or without wrapping.+module Graphics.Layout.Flex(+ Flex(..), FlexChild(..), Direction(..), FlexWrapping(..), Justification(..), Alignment(..),+ flexMap, flexResolve, flexMaxBasis, flexSumBasis, flexWrap, flexRowSize, flexRowsSize,+ justifyOffset, justifySpacing, flexJustify, alignOffset,+ outerMinMain, outerMain, outerMaxMain, outerMinCross, outerCross, outerMaxCross,+ innerMinMain, innerMain, innerMaxMain, innerMinCross, innerCross, innerMaxCross,+ flexGetBox, flexSplit, flexPosition) where++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)++-- | Parameters to flexbox layout+data Flex a b = Flex {+ -- | On which axis to position children+ direction :: Direction,+ -- | Whether to reverse each row+ reverseRows :: Bool,+ -- | Whether to wrap children to multiple lines+ wrap :: FlexWrapping,+ -- | How to justify children within lines+ justify :: Justification,+ -- | How to justify lines within the flexbox. Nothing is CSS "stretch".+ alignLines :: Maybe Justification,+ -- | Gap between children+ baseGap :: b,+ -- | Gap between lines+ crossGap :: b,+ -- | The children to layout, 2D list so as to store lines once split.+ children :: [[FlexChild a b]],+ -- | How wide to consider the page when paginating.+ pageWidth :: Double+} deriving (Eq, Show, Read)+-- | Properties for positioning a child of a flexbox+data FlexChild a b = FlexChild {+ grow :: Double,+ shrink :: Double,+ basis :: b,+ alignment :: Alignment,+ flexInner :: a+} deriving (Eq, Show, Read)++-- | Which axis to position children along+data Direction = Row | Column deriving (Eq, Show, Read)+-- | Whether to wrap or reverse the wrapped lines+data FlexWrapping = NoWrap | Wrap | WrapReverse deriving (Eq, Show, Read)+-- | How to position lines within a flexbox, or children within a line+data Justification = JStart | JEnd | JCenter | JSpaceBetween | JSpaceAround | JSpaceEvenly+ deriving (Eq, Show, Read)+-- | How to align children along the cross-axis (opposite axis from which they're laid out)+data Alignment = AlStretch | AlStart | AlEnd | AlCenter | AlBaseline+ deriving (Eq, Show, Read)++-- | Apply an operation to every child+flexMap :: (a -> b) -> Flex a c -> Flex b c+flexMap cb self = Flex {+ 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]+ }+-- | Resolve lengths in the flexbox to doubles.+flexResolve :: CastDouble b => (a -> Direction -> Double) -> Double ->+ Flex a b -> Flex a Double+flexResolve cb size self = Flex {+ 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]+ }++-- | The minimum size of the flexbox along `direction`, i.e. maximum size of a child.+flexMaxBasis :: Flex a Double -> Double+flexMaxBasis self = maximum [basis child | row <- children self, child <- row]+-- | The maximum width of each row of the flexbox.+flexSumBasis :: Flex a Double -> Double+flexSumBasis self = maximum [Prelude.sum $+ intersperse (baseGap self) $ map basis row | row <- children self]++-- | Wrap all lines to a given size reassigning overflow or underflow space.+-- NOTE: shrink propery may yield negative sizes. Caller will want to enforce min-sizes.+flexWrap :: CastDouble b => Flex a b -> Double -> Flex a b+flexWrap self size+ | NoWrap <- wrap self = post self+ | Wrap <- wrap self = post self'+ | WrapReverse <- wrap self = post self' { children=reverse $ children self' }+ where+ self' = self {+ children = concatMap wrapRow $ children self+ }+ wrapRow :: CastDouble b => [FlexChild a b] -> [[FlexChild a b]]+ wrapRow [] = []+ wrapRow kids@(kid:_) = let (row, rest) = splitRow' kids $ basis' kid+ in row:wrapRow rest+ splitRow, splitRow' :: CastDouble b => [FlexChild a b] -> Double ->+ ([FlexChild a b], [FlexChild a b])+ -- This wrapper function ensures we don't end up with empty rows, or infinite loops.+ splitRow' (kid:kids) end =+ let (kids', rest) = splitRow kids (end + baseGap' self + basis' kid)+ in (kid:kids', rest)+ splitRow' [] _ = ([], [])+ splitRow (kid:kids) end+ | end > size = ([], kid:kids)+ | otherwise = let (kids', rest) = splitRow kids (end + baseGap' self + basis' kid)+ in (kid:kids', rest)+ splitRow [] _ = ([], [])++ post :: CastDouble b => Flex a b -> Flex a b+ post flex+ | reverseRows self = post' flex { children = map reverse $ children flex }+ | otherwise = post' flex+ post' :: CastDouble b => Flex a b -> Flex 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 = fromDouble $ basis' kid - shrink kid * nanguard sfr+ } | kid <- row]+ | rowSize < size = [kid {+ basis = fromDouble $ basis' kid + grow kid * nanguard gfr+ } | kid <- row]+ | otherwise = row+ where+ 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)+ nanguard x | isNaN x = 0+ | isInfinite x = 0+ | otherwise = x+ baseGap' :: CastDouble b => Flex a b -> Double+ baseGap' = toDouble . baseGap+ basis' :: CastDouble b => FlexChild a b -> Double+ basis' = toDouble . basis++-- | The cross (opposite from `direction` axis) size of a row.+flexRowSize :: (a -> Double) -> [FlexChild a b] -> Double+flexRowSize cb row = maximum $ map (cb . flexInner) row+-- | The cross (opposite from `direction` axis) size of all rows.+flexRowsSize :: (a -> Double) -> Flex a Double -> Double+flexRowsSize cb Flex { crossGap = gap, children = kids } =+ sum $ intersperse gap $ flexRowSize cb `map` kids++justifyOffset, justifySpacing :: Double -> [Double] -> Double -> Justification -> Double+-- | How far right to shift some elements to achieve desired justification.+justifyOffset _ _ _ JStart = 0+justifyOffset outersize ks g JEnd = outersize - innersize g ks+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)+-- | How much space to add between elements.+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++-- | Position new positions for the given items according to the given justification.+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 [] _ = []++-- | How far right to shift some elements to achieve desired alignment.+alignOffset :: Double -> Double -> Alignment -> Double+alignOffset _ _ AlStretch = 0 -- Needs special handling elsewhere+alignOffset _ _ AlStart = 0+alignOffset outer inner AlEnd = outer - inner+alignOffset outer inner AlCenter = half $ outer - inner+alignOffset outer inner AlBaseline = half $ outer - inner -- FIXME: Implement properly!++-- | Sum given sizes with a specified gap between them.+innersize gap = sum . intersperse gap+-- | divide by 2, can be syntactically in certain circumstances.+half = (/2)+-- | length of an list, as a double.+length' :: [a] -> Double+length' = toEnum . length++------+--- Mapping Box Model axes <-> Flex Box axes+------++outerMinMain, outerMain, outerMaxMain :: Num m => PaddedBox m m -> Direction -> m+outerMinMain box Row = minWidth box+outerMinMain box Column = minHeight box+outerMain box Row = width box+outerMain box Column = height box+outerMaxMain box Row = maxWidth box+outerMaxMain box Column = maxHeight box++outerMinCross, outerCross, outerMaxCross :: Num m => PaddedBox m m -> Direction -> m+outerMinCross box Row = minHeight box+outerMinCross box Column = minWidth box+outerCross box Row = height box+outerCross box Column = width box+outerMaxCross box Row = maxHeight box+outerMaxCross box Column = maxWidth box++innerMinMain, innerMain, innerMaxMain :: Num m => PaddedBox m m -> Direction -> m+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 = 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++-- | Compute the size bounds of a flexbox.+flexGetBox :: (Zero m, CastDouble m, Zero n, CastDouble n) =>+ (a -> PaddedBox Double Double) -> Flex 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++-- | Split a flexbox over multiple pages.+flexSplit :: (a -> Size Double Double) -> Double -> Double -> Flex a Double ->+ (Flex a Double, Flex a Double)+flexSplit cb h _ self@Flex { 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@Flex { 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++-- | Compute final position of a flexbox's children.+flexPosition :: ((Double, Double) -> a -> b) -> (a -> Size Double Double) ->+ (Double, Double) -> Size Double Double ->+ Flex a Double -> Flex b Double+flexPosition cb cb' (x,y) size self@Flex { 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)
+ Graphics/Layout/Flex/CSS.hs view
@@ -0,0 +1,212 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Parse FlexBox-related CSS properties+module Graphics.Layout.Flex.CSS(CSSFlex(..), lowerFlex) where++import Data.CSS.Syntax.Tokens (Token(..), NumericValue(..))+import Stylist (PropertyParser(..), TrivialPropertyParser, parseOperands,+ parseUnorderedShorthand', parseUnorderedShorthand)+import Graphics.Layout.Flex as F+import Graphics.Layout.CSS.Length (parseLength, finalizeLength, n2f, Unitted, Font')+import Graphics.Layout.Box (Length)+import Data.Maybe (isJust)++-- | Parsed FlexBox-related CSS properties.+data CSSFlex = CSSFlex {+ -- | Parsed CSS flex-direction, axis component.+ directionCSS :: Direction,+ -- | Parsed CSS flex-direction, reversed flag+ reverseRowsCSS :: Bool,+ -- | Parsed CSS flex-wrap+ wrapCSS :: FlexWrapping,+ -- | Parsed CSS justify-content+ justifyCSS :: Maybe Justification,+ -- | Parsed CSS align-items+ alignItemsCSS :: Alignment,+ -- | Parsed CSS align-content, `Nothing` is "stretch"+ alignLinesCSS :: Maybe Justification,+ -- | Parsed CSS row-gap+ rowGapCSS :: Unitted,+ -- | Parsed CSS column-gap+ columnGapCSS :: Unitted,++ -- flex children+ -- | Parsed CSS order+ orderCSS :: Integer,+ -- | Parsed CSS flex-grow+ growCSS :: Double,+ -- | Parsed CSS flex-shrink+ shrinkCSS :: Double,+ -- | Parsed CSS flex-basis+ basisCSS :: Unitted,+ -- | Parsed CSS align-self+ alignSelfCSS :: Alignment,+ -- | Whether justification or alignment properties should be parsed as right-to-left or left-to-right.+ textRTL :: Bool -- Extra parameter from caller.+}++setDir self dir rev = Just self { directionCSS = dir, reverseRowsCSS = rev }++parseJustify self "flex-start" | reverseRowsCSS self = Just JEnd+ | otherwise = Just JStart+parseJustify self "flex-end" | reverseRowsCSS self = Just JStart+ | otherwise = Just JEnd+parseJustify self "start" | textRTL self = Just JEnd+ | otherwise = Just JStart+parseJustify self "end" | textRTL self = Just JStart+ | otherwise = Just JEnd+parseJustify _ "left" = Just JStart+parseJustify _ "right" = Just JEnd+parseJustify _ "center" = Just JCenter+parseJustify _ "space-between" = Just JSpaceBetween+parseJustify _ "space-around" = Just JSpaceAround+parseJustify _ "space-evenly" = Just JSpaceEvenly+parseJustify _ _ = Nothing++parseAlign _ "stretch" = Just AlStretch+parseAlign self "flex-start" | reverseRowsCSS self = Just AlEnd+ | otherwise = Just AlStart+parseAlign _ "start" = Just AlStart+parseAlign self "self-start" | textRTL self = Just AlEnd+ | otherwise = Just AlStart+parseAlign self "flex-end" | reverseRowsCSS self = Just AlStart+ | otherwise = Just AlEnd+parseAlign _ "end" = Just AlEnd+parseAlign self "self-end" | textRTL self = Just AlStart+ | otherwise = Just AlEnd+parseAlign _ "center" = Just AlCenter+parseAlign _ "baseline" = Just AlBaseline+parseAlign _ _ = Nothing++instance PropertyParser CSSFlex where+ temp = CSSFlex {+ directionCSS = Row,+ reverseRowsCSS = False,+ wrapCSS = NoWrap,+ justifyCSS = Nothing, -- flex-start, conditional on directionCSS+ alignItemsCSS = AlStretch,+ alignLinesCSS = Just JStart,+ rowGapCSS = (0,"px"),+ columnGapCSS = (0,"px"),+ orderCSS = 0,+ growCSS = 0,+ shrinkCSS = 1,+ basisCSS = (0,"auto"),+ alignSelfCSS = AlStretch, -- Should be auto, but we're implementing that in `inherit`.+ textRTL = False+ }+ inherit parent = temp { alignSelfCSS = alignItemsCSS parent }+ priority _ = ["flex-direction"]++ longhand _ self "flex-direction" [Ident "row"] = setDir self Row False+ longhand _ self "flex-direction" [Ident "row-reverse"] = setDir self Row True+ longhand _ self "flex-direction" [Ident "column"] = setDir self F.Column False+ longhand _ self "flex-direction" [Ident "column-reverse"] =+ setDir self F.Column True+ longhand _ self "flex-direction" [Ident "initial"] = setDir self Row False++ longhand _ self "flex-wrap" [Ident "no-wrap"] = Just self { wrapCSS = NoWrap }+ longhand _ self "flex-wrap" [Ident "wrap"] = Just self { wrapCSS = Wrap }+ longhand _ self "flex-wrap" [Ident "wrap-reverse"] =+ Just self { wrapCSS = WrapReverse }+ longhand _ self "flex-wrap" [Ident "initial"] = Just self { wrapCSS = NoWrap }++ longhand _ self "justify-content" [Ident x]+ | x == "initial" = Just self { justifyCSS = parseJustify self "flex-start" }+ | y@(Just _) <- parseJustify self x = Just self { justifyCSS = y }++ longhand _ self "align-items" [Ident x]+ | x == "initial" = Just self { alignItemsCSS = AlStretch }+ | Just y <- parseAlign self x = Just self { alignItemsCSS = y }++ longhand _ self "align-content" [Ident x] | x `elem` ["initial", "normal"] =+ Just self { alignLinesCSS = parseJustify self "start" }+ | x == "stretch" = Just self { alignLinesCSS = Nothing }+ | y@(Just _) <- parseJustify self x = Just self { alignLinesCSS = y }++ longhand _ self "row-gap" [Ident x]+ | x `elem` ["initial", "normal"] = Just self { rowGapCSS = (0,"px") }+ | otherwise = Nothing+ longhand _ self "row-gap" toks+ | Just x <- parseLength toks = Just self { rowGapCSS = x}+ longhand _ self "column-gap" [Ident x]+ | x `elem` ["initial", "normal"] = Just self { columnGapCSS = (0,"px") }+ | otherwise = Nothing+ longhand _ self "column-gap" toks+ | Just x <- parseLength toks = Just self { columnGapCSS = x }++ longhand _ self "order" [Number _ (NVInteger x)] = Just self { orderCSS = x }+ longhand _ self "order" [Ident "initial"] = Just self { orderCSS = 0 }++ longhand _ self "flex-grow" [Number _ x] | n2f x>0 = Just self {growCSS=n2f x}+ longhand _ self "flex-grow" [Ident "initial"] = Just self { growCSS = 0 }+ longhand _ self "flex-shrink" [Number _ x] | n2f x > 0 =+ Just self { shrinkCSS = n2f x }+ longhand _ self "flex-shrink" [Ident "initial"] = Just self { shrinkCSS = 1 }++ longhand _ self "flex-basis" toks | Just x <- parseLength toks =+ Just self { basisCSS = x }++ longhand parent self "align-self" [Ident x] | x `elem` ["initial", "auto"] =+ Just self { alignSelfCSS = alignItemsCSS parent }+ | Just y <- parseAlign self x = Just self { alignSelfCSS = y }++ longhand _ _ _ _ = Nothing++ shorthand self "flex-flow" toks =+ parseUnorderedShorthand self ["flex-direction", "flex-wrap"] toks+ shorthand self "gap" toks | [x] <- parseOperands toks =+ parseUnorderedShorthand' self ["row-gap", "column-gap"] [x, x]+ | otherwise = parseUnorderedShorthand self ["row-gap", "column-gap"] toks+ shorthand self "flex" toks+ | [Ident "initial"] <- toks =+ [("flex-grow", init), ("flex-shrink", init), ("flex-basis", px0)]+ | [Ident "auto"] <- toks =+ [("flex-grow", n1), ("flex-shrink", n1), ("flex-basis", init)]+ | [Ident "none"] <- toks =+ [("flex-grow", n0), ("flex-shrink", n0), ("flex-basis", init)]+ | [a] <- operands, test "flex-grow" a =+ [("flex-grow", a), ("flex-shrink", init), ("flex-basis", px0)]+ | [a] <- operands, test "flex-basis" a =+ [("flex-grow", n1), ("flex-shrink", init), ("flex-basis", a)]+ | [a, b] <- operands, test "flex-grow" a, test "flex-shrink" b =+ [("flex-grow", a), ("flex-shrink", b), ("flex-basis", px0)]+ | [a, b] <- operands, test "flex-grow" a, test "flex-basis" b =+ [("flex-grow", a), ("flex-shrink", init), ("flex-basis", b)]+ | [a, b, c] <- operands, test "flex-grow" a, test "flex-shrink" b, test "flex-basis" c =+ [("flex-grow", a), ("flex-shrink", b), ("flex-basis", c)]+ where+ operands = parseOperands toks+ test a = isJust . longhand self self a+ init = [Ident "initial"]+ px0 = [Dimension "0" (NVInteger 0) "px"]+ n1 = [Number "1" (NVInteger 1)]+ n0 = [Number "0" (NVInteger 0)]+ shorthand self k v | Just _ <- longhand self self k v = [(k, v)]+ | otherwise = []++-- | Lower the Flexbox styling tree to the Layout tree.+lowerFlex :: CSSFlex -> Font' -> [CSSFlex] -> [a] -> [Font'] -> Flex a Length+lowerFlex self font kids kids' fonts' = Flex {+ direction = directionCSS self,+ reverseRows = reverseRowsCSS self,+ wrap = wrapCSS self,+ justify = case justifyCSS self of+ Just x -> x+ Nothing | reverseRowsCSS self -> JEnd+ Nothing -> JStart,+ alignLines = alignLinesCSS self,+ baseGap = case directionCSS self of+ Row -> flip finalizeLength font $ rowGapCSS self+ F.Column -> flip finalizeLength font $ columnGapCSS self,+ 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,+ basis = flip finalizeLength font' $ rowGapCSS kid,+ alignment = alignSelfCSS kid,+ flexInner = kid'+ } | (kid, kid', font') <- zip3 kids kids' fonts']]+ }
app/Integration.hs view
@@ -12,7 +12,7 @@ import Graphics.Layout.CSS (CSSBox(..), finalizeCSS') import Graphics.Layout.CSS.Font (placeholderFont)-import Graphics.Layout (LayoutItem, boxLayout,+import Graphics.Layout (LayoutItem, boxLayout, glyphsPerFont, layoutGetBox, layoutGetChilds, layoutGetInner) import Graphics.Layout.Box (zeroBox) import qualified Graphics.Layout.Box as B@@ -42,6 +42,7 @@ import Foreign.C.Types (CInt) import Data.Function (fix) import Control.Monad (unless)+import Control.Exception (evaluate) import qualified Graphics.Text.Font.Choose as FC initReferer :: IO (Page (CSSCond.ConditionalStyles (CSSBox Nil)))@@ -140,7 +141,9 @@ pages <- tryReadMVar pages' case pages of- Just (display:_) -> renderDisplay renderer display+ Just (display:_) -> do+ evaluate $ glyphsPerFont display + renderDisplay renderer display _ -> return () present renderer
app/Integration2.hs view
@@ -14,7 +14,7 @@ import Graphics.Layout.CSS.Font (placeholderFont) import Graphics.Layout.CSS (finalizeCSS', CSSBox)-import Graphics.Layout (LayoutItem, boxLayout)+import Graphics.Layout (LayoutItem, boxLayout, glyphsPerFont) import Graphics.Layout.Box (Length, Size(..), PaddedBox(..), zeroBox) import Control.Exception (evaluate)@@ -51,7 +51,13 @@ layout = finalizeCSS' placeholderFont styles layout `seq` print "Laying out page!" res <- forkCompute $ boxLayout zeroBox { size = Size 1280 480 } layout False- readMVar res+ res' <- readMVar res+ case res' of+ (page:_) -> do+ print "Gathering atlas"+ evaluate $ glyphsPerFont page+ return ()+ _ -> return () --performGC --FC.fini -- FIXME: GC still left FontConfig references... return ()
cattrap.cabal view
@@ -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/@@ -27,7 +27,8 @@ Graphics.Layout.Box, Graphics.Layout.Arithmetic, Graphics.Layout.CSS.Length, Graphics.Layout.CSS.Font, Graphics.Layout.Inline, Graphics.Layout.Inline.CSS,- Graphics.Layout.Grid.Table+ Graphics.Layout.Grid.Table,+ Graphics.Layout.Flex, Graphics.Layout.Flex.CSS other-modules: Graphics.Layout.CSS.Parse -- other-extensions: build-depends: base >=4.12 && <5, containers >= 0.6 && < 1, parallel >= 3 && <4,@@ -58,7 +59,7 @@ stylist-traits , stylist>=2.7.0.1 && <3, hurl-xml >= 0.2 && < 1, hurl >= 2.3 && < 3, sdl2 >= 2.5.4 && < 2.6, containers, network-uri >=2.6 && <3,- xml-conduit, directory >= 1.3 && < 2, xml-conduit-stylist,+ xml-conduit >= 1.8 && < 2, directory >= 1.3 && < 2, xml-conduit-stylist, bytestring, file-embed >= 0.0.15 && < 0.1, deepseq, fontconfig-pure hs-source-dirs: app default-language: Haskell2010@@ -69,8 +70,9 @@ -- other-modules: -- other-extensions: build-depends: base >=4.12 && <5, cattrap, text>=2.0.2 && <3, css-syntax,- stylist-traits, stylist>=2.7.0.1 && <3, network-uri, html-conduit >=1.3 && <2,- xml-conduit, xml-conduit-stylist >= 3 && < 4, deepseq, fontconfig-pure+ stylist-traits, stylist>=2.7.0.1 && <3, network-uri,+ html-conduit >=1.3 && <2, xml-conduit >= 1.8 && < 2,+ xml-conduit-stylist >= 3 && < 4, deepseq, fontconfig-pure hs-source-dirs: app default-language: Haskell2010
test/Test.hs view
@@ -10,7 +10,7 @@ import Data.Maybe (fromJust) import Graphics.Layout.Box as B-import Graphics.Layout.Grid+import Graphics.Layout.Grid as Grid import Graphics.Layout.Flow import Graphics.Layout @@ -18,6 +18,7 @@ import Graphics.Layout.Grid.Table import Graphics.Layout.Inline.CSS import Graphics.Layout.CSS.Font (placeholderFont)+import Graphics.Layout.Flex as Flex import Data.Text.ParagraphLayout.Rich (constructParagraph, defaultParagraphOptions, defaultTextOptions,@@ -360,7 +361,7 @@ let defaultPageOptions = PageOptions 0 0 2 2 let gridItem x y = GridItem { cellStart = x, cellEnd = y,- alignment = Start,+ Grid.alignment = Start, minSize = 0, natSize = 0 } let track cells' = Track {@@ -410,6 +411,399 @@ LayoutFlow () lengthBox [inline "H"] ] ]+ describe "Flexbox" $ do+ it "wraps properly" $ do+ let child l = FlexChild {+ grow = 0,+ shrink = 0,+ basis = l,+ Flex.alignment = AlStart,+ flexInner = ()+ }+ let baseFlex = Flex.Flex {+ direction = Row,+ reverseRows = False,+ wrap = NoWrap,+ justify = JStart,+ alignLines = Just JStart,+ baseGap = 2,+ crossGap = 2,+ Flex.children = [[(child 10) { grow = 1 }, (child 20) { shrink = 1 },+ (child 30) { grow = 2 }, (child 40) { shrink = 2 }]],+ pageWidth = 0+ } :: Flex.Flex () Double+ -- These test results don't look right...+ flexWrap baseFlex 50 `shouldBe` baseFlex {+ Flex.children = [[(child 10) { grow = 1 },+ (child 1.3333333333333321) { shrink = 1 }, (child 30) { grow = 2 },+ (child 2.6666666666666643) { shrink = 2}]]+ }+ flexWrap baseFlex 40 `shouldBe` baseFlex {+ Flex.children = [[(child 10) { grow = 1 },+ (child $ -2) { shrink = 1 }, (child 30) { grow = 2 },+ (child $ -4) { shrink = 2}]]+ }+ flexWrap baseFlex 30 `shouldBe` baseFlex {+ Flex.children = [[(child 10) { grow = 1 },+ (child $ -5.333333333333332) { shrink = 1 }, (child 30) { grow = 2 },+ (child $ -10.666666666666664) { shrink = 2}]]+ }+ flexWrap baseFlex 20 `shouldBe` baseFlex {+ Flex.children = [[(child 10) { grow = 1 },+ (child $ -8.666666666666668) { shrink = 1 }, (child 30) { grow = 2 },+ (child $ -17.333333333333336) { shrink = 2}]]+ }+ flexWrap baseFlex 10 `shouldBe` baseFlex {+ Flex.children = [[(child 10) { grow = 1 },+ (child $ -12) { shrink = 1 }, (child 30) { grow = 2 },+ (child $ -24) { shrink = 2}]]+ }+ flexWrap baseFlex { direction = Flex.Column } 50 `shouldBe` baseFlex {+ direction = Flex.Column,+ Flex.children = [[(child 10) { grow = 1 },+ (child 1.3333333333333321) { shrink = 1 }, (child 30) { grow = 2 },+ (child 2.6666666666666643) { shrink = 2}]]+ }+ flexWrap baseFlex { direction = Flex.Column } 40 `shouldBe` baseFlex {+ direction = Flex.Column,+ Flex.children = [[(child 10) { grow = 1 },+ (child $ -2) { shrink = 1 }, (child 30) { grow = 2 },+ (child $ -4) { shrink = 2}]]+ }+ flexWrap baseFlex { direction = Flex.Column } 30 `shouldBe` baseFlex {+ direction = Flex.Column,+ Flex.children = [[(child 10) { grow = 1 },+ (child $ -5.333333333333332) { shrink = 1 }, (child 30) { grow = 2 },+ (child $ -10.666666666666664) { shrink = 2}]]+ }+ flexWrap baseFlex { direction = Flex.Column } 20 `shouldBe` baseFlex {+ direction = Flex.Column,+ Flex.children = [[(child 10) { grow = 1 },+ (child $ -8.666666666666668) { shrink = 1 }, (child 30) { grow = 2 },+ (child $ -17.333333333333336) { shrink = 2}]]+ }+ flexWrap baseFlex { direction = Flex.Column } 10 `shouldBe` baseFlex {+ direction = Flex.Column,+ Flex.children = [[(child 10) { grow = 1 },+ (child $ -12) { shrink = 1 }, (child 30) { grow = 2 },+ (child $ -24) { shrink = 2}]]+ }+ flexWrap baseFlex { reverseRows = True } 50 `shouldBe` baseFlex {+ reverseRows = True,+ Flex.children = [[+ FlexChild {+ grow = 0.0,+ shrink = 2.0,+ basis = 2.6666666666666643,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 2.0,+ shrink = 0.0,+ basis = 30.0,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 0.0,+ shrink = 1.0,+ basis = 1.3333333333333321,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 1.0,+ shrink = 0.0,+ basis = 10.0,+ Flex.alignment = AlStart,+ flexInner = ()+ }+ ]]+ }+ flexWrap baseFlex { reverseRows = True } 40 `shouldBe` baseFlex {+ reverseRows = True,+ Flex.children = [[+ FlexChild {+ grow = 0.0,+ shrink = 2.0,+ basis = -4,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 2.0,+ shrink = 0.0,+ basis = 30.0,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 0.0,+ shrink = 1.0,+ basis = -2,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 1.0,+ shrink = 0.0,+ basis = 10.0,+ Flex.alignment = AlStart,+ flexInner = ()+ }+ ]]+ }+ flexWrap baseFlex { reverseRows = True } 30 `shouldBe` baseFlex {+ reverseRows = True,+ Flex.children = [[+ FlexChild {+ grow = 0.0,+ shrink = 2.0,+ basis = -10.666666666666664,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 2.0,+ shrink = 0.0,+ basis = 30.0,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 0.0,+ shrink = 1.0,+ basis = -5.333333333333332,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 1.0,+ shrink = 0.0,+ basis = 10.0,+ Flex.alignment = AlStart,+ flexInner = ()+ }+ ]]+ }+ flexWrap baseFlex { reverseRows = True } 20 `shouldBe` baseFlex {+ reverseRows = True,+ Flex.children = [[+ FlexChild {+ grow = 0.0,+ shrink = 2.0,+ basis = -17.333333333333336,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 2.0,+ shrink = 0.0,+ basis = 30.0,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 0.0,+ shrink = 1.0,+ basis = -8.666666666666668,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 1.0,+ shrink = 0.0,+ basis = 10.0,+ Flex.alignment = AlStart,+ flexInner = ()+ }+ ]]+ }+ flexWrap baseFlex { reverseRows = True } 10 `shouldBe` baseFlex {+ reverseRows = True,+ Flex.children = [[+ FlexChild {+ grow = 0.0,+ shrink = 2.0,+ basis = -24,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 2.0,+ shrink = 0.0,+ basis = 30.0,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 0.0,+ shrink = 1.0,+ basis = -12,+ Flex.alignment = AlStart,+ flexInner = ()+ },+ FlexChild {+ grow = 1.0,+ shrink = 0.0,+ basis = 10.0,+ Flex.alignment = AlStart,+ flexInner = ()+ }+ ]]+ }+ flexWrap baseFlex { wrap = Wrap } 50 `shouldBe` baseFlex {+ wrap = Wrap,+ Flex.children = [[(child 10) { grow = 1 },+ (child 6) { shrink = 1 }, (child 30) { grow = 2 }], [+ (child 40) { shrink = 2}]]+ }+ flexWrap baseFlex { wrap = Wrap } 40 `shouldBe` baseFlex {+ wrap = Wrap,+ Flex.children = [[(child 18) { grow = 1 },+ (child 20) { shrink = 1 }], [(child 40) { grow = 2 }],+ [(child 40) { shrink = 2}]]+ }+ flexWrap baseFlex { wrap = Wrap } 30 `shouldBe` baseFlex {+ wrap = Wrap,+ Flex.children = [[(child 10) { grow = 1 },+ (child 18) { shrink = 1 }], [(child 30) { grow = 2 }],+ [(child 30) { shrink = 2}]]+ }+ flexWrap baseFlex { wrap = Wrap } 20 `shouldBe` baseFlex {+ wrap = Wrap,+ Flex.children = [[(child 20) { grow = 1 }],+ [(child 20) { shrink = 1 }], [(child 30) { grow = 2 }],+ [(child 20) { shrink = 2}]]+ }+ flexWrap baseFlex { wrap = Wrap } 10 `shouldBe` baseFlex {+ wrap = Wrap,+ Flex.children = [[(child 10) { grow = 1 }], [(child 10) { shrink = 1 }],+ [(child 30) { grow = 2 }], [(child 10) { shrink = 2}]]+ }+ flexWrap baseFlex { wrap = WrapReverse } 50 `shouldBe` baseFlex {+ wrap = WrapReverse,+ Flex.children = [[(child 40) { shrink = 2}], [(child 10) { grow = 1 },+ (child 6) { shrink = 1 }, (child 30) { grow = 2 }]]+ }+ flexWrap baseFlex { wrap = WrapReverse } 40 `shouldBe` baseFlex {+ wrap = WrapReverse,+ Flex.children = [[(child 40) { shrink = 2}], [(child 40) { grow = 2 }],+ [(child 18) { grow = 1 }, (child 20) { shrink = 1 }]]+ }+ flexWrap baseFlex { wrap = WrapReverse } 30 `shouldBe` baseFlex {+ wrap = WrapReverse,+ Flex.children = [[(child 30) { shrink = 2}], [(child 30) { grow = 2 }],+ [(child 10) { grow = 1 }, (child 18) { shrink = 1 }]]+ }+ flexWrap baseFlex { wrap = WrapReverse } 20 `shouldBe` baseFlex {+ wrap = WrapReverse,+ Flex.children = [[(child 20) { shrink = 2}], [(child 30) { grow = 2 }],+ [(child 20) { shrink = 1 }], [(child 20) { grow = 1 }]]+ }+ flexWrap baseFlex { wrap = WrapReverse } 10 `shouldBe` baseFlex {+ wrap = WrapReverse,+ Flex.children = [[(child 10) { shrink = 2}], [(child 30) { grow = 2 }],+ [(child 10) { shrink = 1 }], [(child 10) { grow = 1 }]]+ }+ it "Paginates properly" $ do+ let child l align = FlexChild {+ grow = 0,+ shrink = 0,+ basis = l,+ Flex.alignment = align,+ flexInner = l+ }+ let baseFlex = Flex.Flex {+ direction = Row,+ reverseRows = False,+ wrap = Wrap,+ justify = JStart,+ alignLines = Just JStart,+ baseGap = 2,+ crossGap = 2,+ Flex.children = [[(child 10 AlStart) { grow = 1 },+ (child 20 AlCenter) { shrink = 1 },+ (child 30 AlEnd) { grow = 2 },+ (child 40 AlStretch) { shrink = 2 }]],+ pageWidth = 80+ } :: Flex.Flex Double Double+ let self = flexWrap baseFlex 50+ self `shouldBe` baseFlex {+ Flex.children = [[(child 10 AlStart) { grow = 1 },+ (child 6 AlCenter) { shrink = 1, flexInner = 20 },+ (child 30 AlEnd) { grow = 2 }], [+ (child 40 AlStretch) { shrink = 2}]]+ }+ flexSplit (\x -> Size x x) 30 50 self `shouldBe` (baseFlex {+ Flex.children = [[(child 10 AlStart) { grow = 1 },+ (child 6 AlCenter) { shrink = 1, flexInner = 20 },+ (child 30 AlEnd) { grow = 2 }]]+ }, baseFlex {+ Flex.children = [[(child 40 AlStretch) { shrink = 2}]]+ })+ print self { direction = Flex.Column }+ flexSplit (\x -> Size x x) 40 50 self { direction = Flex.Column } `shouldBe` (baseFlex {+ direction = Flex.Column,+ Flex.children = [[(child 10 AlStart) { grow = 1 },+ (child (-4) AlCenter) { shrink = 1, flexInner = 20 },+ (child 30 AlEnd) { grow = 2 }], [(child 40 AlStretch) { shrink = 2}]]+ }, baseFlex {+ direction = Flex.Column,+ Flex.children = []+ })+ flexSplit (\x -> Size x x) 10 50 self { direction = Flex.Column } `shouldBe` (baseFlex {+ direction = Flex.Column,+ Flex.children = []+ }, baseFlex {+ direction = Flex.Column,+ Flex.children = [[(child 10 AlStart) { grow = 1 },+ (child 6 AlCenter) { shrink = 1, flexInner = 20 },+ (child 30 AlEnd) { grow = 2 }], [(child 40 AlStretch) { shrink = 2}]]+ })+ flexSplit (\x -> Size x x) 10 50 self { direction = Flex.Column, pageWidth = 40 } `shouldBe` (baseFlex {+ Flex.children = [[(child 10 AlStart) { grow = 1 },+ (child 6 AlCenter) { shrink = 1, flexInner = 20 },+ (child 30 AlEnd) { grow = 2 }]],+ pageWidth = 40+ }, baseFlex {+ Flex.children = [[(child 40 AlStretch) { shrink = 2}]],+ pageWidth = 40+ })+ it "positions correctly" $ do+ let child l align = FlexChild {+ grow = 0,+ shrink = 0,+ basis = l,+ Flex.alignment = align,+ flexInner = l+ }+ let child' l align pos = FlexChild {+ grow = 0, shrink = 0, basis = l,+ Flex.alignment = align, flexInner = (pos, l)+ }+ let flex childs = Flex.Flex {+ direction = Row,+ reverseRows = False,+ wrap = Wrap,+ justify = JStart,+ alignLines = Just JStart,+ baseGap = 2,+ crossGap = 2,+ Flex.children = childs,+ pageWidth = 80+ }+ let baseFlex = flex [[(child 10 AlStart) { grow = 1 },+ (child 20 AlCenter) { shrink = 1 },+ (child 30 AlEnd) { grow = 2 },+ (child 40 AlStretch) { shrink = 2 }]] :: Flex.Flex Double Double+ flexPosition (,) (\a -> Size a a) (10, 10) (Size 50 50) baseFlex `shouldBe` flex [[+ (child' 10 AlStart (10, 10)) { grow = 1 },+ (child' 20 AlCenter (22, 20)) { shrink = 1 },+ (child' 30 AlEnd (44, 20)) { grow = 2 },+ (child' 40 AlStretch (76, 10)) { shrink = 2 }+ ]] runMath = flip evalCalc [] . mapCalc fst . flip parseCalc [] . filter (/= Whitespace) . tokenize