~alcinnz/Mondrian

ref: cde2102671563d7535702bd697b3a03ba4e10c67 Mondrian/lib/Graphics/Rendering/Rect/CSS/Backgrounds.hs -rw-r--r-- 10.1 KiB
cde21026 — Adrian Cochrane Minor legibility & theoretical-performance optimizations. 1 year, 6 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
{-# LANGUAGE OverloadedStrings, FlexibleInstances #-}
module Graphics.Rendering.Rect.CSS.Backgrounds (Backgrounds(..),
    Pattern(..), RadialShape(..),
    Resize(..), Length(..), resolveSize) where

import Stylist (PropertyParser(..), parseUnorderedShorthand, parseOperands)
import Data.CSS.Syntax.Tokens (Token(..), NumericValue(..))
import Data.Maybe (isJust, catMaybes)
import Data.Text (Text)
import Data.Scientific (scientific, toRealFloat)

import Graphics.Rendering.Rect.CSS.Colour (ColourPallet, parseColour)
import Data.Colour (AlphaColour, transparent)
import Graphics.Rendering.Rect.Types (Rects(..), Rect(..))

data Backgrounds img = Backgrounds {
    pallet :: ColourPallet,
    background :: C,
    clip :: [Rects -> Rect],
    image :: [Pattern img],
    bgSize :: [Resize]
} deriving (Eq, Show, Read)

type C = AlphaColour Float

data Pattern img = None | Img img | Linear Float [(C, Length)]
    | Radial RadialShape (Length, Length) [(C, Length)] deriving (Eq, Show, Read)
data RadialShape = Circle | Ellipse deriving (Eq, Show, Read)

-- We need to resolve images before we can compute the actual lengths!
data Resize = Cover | Contain | Size Length Length deriving (Eq, Show, Read)
data Length = Absolute Float | Scale Float | Auto deriving (Eq, Show, Read)

instance PropertyParser (Backgrounds Text) where
    temp = Backgrounds {
        pallet = temp, background = transparent, clip = [borderBox],
        image = [None], bgSize = [Size Auto Auto]
      }
    inherit _ = temp
    priority _ = []

    longhand _ self@Backgrounds{ pallet = c } "background-color" toks
        | Just ([], val) <- parseColour c toks = Just self { background = val }
    longhand _ self "background-clip" t | val@(_:_) <- parseCSSList inner t =
        Just self { clip = reverse val }
      where
        inner [Ident "content-box"] = Just contentBox
        inner [Ident "padding-box"] = Just paddingBox
        inner [Ident "border-box"] = Just borderBox
        inner [Ident "initial"] = Just borderBox -- To aid shorthand implementation.
        inner _ = Nothing
    longhand _ self@Backgrounds { pallet = pp } "background-image" t
        | val@(_:_) <- parseCSSList inner t = Just self { image = reverse val }
      where
        inner [Ident "none"] = Just None
        inner [Ident "initial"] = Just None
        inner [Url ret] = Just $ Img ret
        inner [Function "url", String ret, RightParen] = Just $ Img ret
        inner (Function "linear-gradient":toks)
            | Just cs@(_:_:_)<-colourStops pp (Comma:toks) = Just $ Linear pi cs
        inner (Function "linear-gradient":Dimension _ x unit:toks)
            | Just s <- lookup unit [("deg", pi/180), ("grad", pi/200),
                    ("rad", 1), ("turn", 2*pi)],
                Just cs@(_:_:_) <- colourStops pp toks = Just $ Linear (f x*s) cs
        inner (Function "linear-gradient":Ident "to":Ident a:Ident b:toks)
            | Just angle<-corner a b, Just stops@(_:_:_)<-colourStops pp toks =
                Just $ Linear angle stops
            | Just angle<-corner b a, Just stops@(_:_:_)<-colourStops pp toks =
                Just $ Linear angle stops
          where
            corner "top" "right" = Just $ 0.25*pi
            corner "bottom" "right" = Just $ 0.75*pi
            corner "bottom" "left" = Just $ 1.25*pi
            corner "top" "left" = Just $ 1.75*pi
            corner _ _ = Nothing
        inner (Function "linear-gradient":Ident "to":Ident side:toks)
            | Just angle <- lookup side [
                ("top", 0), ("right", pi/2), ("bottom", pi), ("left", pi*1.5)],
                Just cs@(_:_:_) <- colourStops pp toks = Just $ Linear angle cs
        inner (Function "radial-gradient":toks)
            | Just cs@(_:_:_) <- colourStops pp (Comma:toks) =
                Just $ Radial Ellipse center cs
            | Just (shp, org, ts) <- radArgs toks,
                Just cs@(_:_:_) <- colourStops pp ts = Just $ Radial shp org cs
          where
            center = (Scale 0.5, Scale 0.5)
            radArgs ts | (ts', Ident "at":posStops) <- break (== Ident "at") ts,
                    Just (shape, _, []) <- radArgs ts',
                    Just (org, stops) <- position posStops =
                Just (shape, org, stops)
            radArgs (Ident "circle":ts) = Just (Circle, center, ts)
            radArgs (Ident "ellipse":ts) = Just (Ellipse, center, ts)
            radArgs _ = Nothing
            position (x:y:ts) = position' x y ts *> position' y x ts
            position _ = Nothing
            position' x y ts = case ((case x of
                    Ident "left" -> Scale 0
                    Ident "center" -> Scale 0.5
                    Ident "right" -> Scale 1
                    Percentage _ a -> Scale $ p a
                    Dimension _ a "px" -> Absolute $ f a
                    _ -> Auto,
                case y of
                    Ident "top" -> Scale 0
                    Ident "center" -> Scale 0.5
                    Ident "right" -> Scale 1
                    Percentage _ a -> Scale $ p a
                    Dimension _ a "px" -> Absolute $ f a
                    _ -> Auto),
                ts) of
                    ((Auto, _), _) -> Nothing
                    ((_, Auto), _) -> Nothing
                    ret -> Just ret
        inner _ = Nothing
    longhand _ self "background-size" t | val@(_:_) <- parseCSSList inner t =
        Just self { bgSize = reverse val }
      where -- TODO: Add shorthand support, after background-position.
        inner [x, y] | Just a <- l x, Just b <- l y = Just $ Size a b
        inner [Ident "contain"] = Just Contain
        inner [Ident "cover"] = Just Cover
        inner [Ident "auto"] = Just $ Size Auto Auto
        inner [Ident "initial"] = Just $ Size Auto Auto
        inner _ = Nothing
        -- NOTE: Leave lowering other units to CatTrap.
        l (Ident "auto") = Just Auto
        l (Dimension _ x "px") = Just $ Absolute $ f x
        l (Percentage _ x) = Just $ Scale $ p x
        l _ = Nothing
    longhand _ _ _ _ = Nothing

    -- The multi-layered shorthand is one source of parsing complexity.
    shorthand self "background" t = catProps $ reverse $ parseCSSList inner t
      where
        catProps [] = []
        catProps (props:pss)
            | Just [Ident "initial"] <- "background-color" `lookup` catProps pss =
                map (catProp $ catProps pss) props
            | otherwise = [] -- Only allow background-color in bottommost layer.
        catProp _ ret@("background-color", _) = ret
        catProp bases (key, val)
            | Just base <- key `lookup` bases = (key, base ++ Comma:val)
            -- Shouldn't happen, `inner` expands all props at least to "initial"!
            | otherwise = (key, val)
        inner toks | ret@(_:_) <- parseUnorderedShorthand self [
                "background-color", "background-clip", "background-image"
              ] toks = Just ret
          | otherwise = Nothing
    shorthand self key val | Just _ <- longhand self self key val = [(key, val)]
        | otherwise = []

colourStops :: ColourPallet
        -> [Token] -> Maybe [(AlphaColour Float, Length)]
colourStops _ [RightParen] = Just []
colourStops cs (Comma:toks)
    | Just (Percentage _ x:toks', c) <- parseColour cs toks,
        Just ret <- colourStops cs toks' = Just $ (c, Scale $ p x):ret
    | Just (Dimension _ x "px":toks', c) <- parseColour cs toks,
        Just ret <- colourStops cs toks' = Just $ (c, Absolute $ f x):ret
    | Just (toks', c) <- parseColour cs toks,
        Just ret <- colourStops cs toks' = Just $ (c, Auto):ret
colourStops cs (Comma:Percentage _ x:toks)
    | Just (toks', c) <- parseColour cs toks,
        Just ret <- colourStops cs toks' = Just $ (c, Scale $ p x):ret
colourStops cs (Comma:Dimension _ x "px":toks)
    | Just (toks', c) <- parseColour cs toks,
        Just ret <- colourStops cs toks' = Just $ (c, Absolute $ f x):ret
colourStops _ _ = Nothing

parseCSSList :: ([Token] -> Maybe a) -> [Token] -> [a]
parseCSSList cb toks | all isJust ret = catMaybes ret
    | otherwise = []
  where ret = map cb $ concat $ splitList [Comma] $ parseOperands toks

f :: NumericValue -> Float
f (NVInteger x) = fromInteger x
f (NVNumber x) = toRealFloat x
p :: NumericValue -> Float
p (NVInteger x) = fromInteger x / 100
-- Do the division while we're in base-10!
p (NVNumber x) = toRealFloat (x/scientific 1 2)

------
--- Utils taken from HappStack
------

-- | Repeadly splits a list by the provided separator and collects the results
splitList :: Eq a => a -> [a] -> [[a]]
splitList _   [] = []
splitList sep list = h:splitList sep t
        where (h,t)=split (==sep) list

-- | Split is like break, but the matching element is dropped.
split :: (a -> Bool) -> [a] -> ([a], [a])
split filt s = (x,y)
        where
        (x,y')=break filt s
        y = if null y' then [] else tail y'

------
--- Dynamically-computed properties
------

resolveSize :: (Float, Float) -> (Float, Float) -> Resize -> (Float, Float)
resolveSize (owidth, oheight) (width, height) Contain
    | width > owidth, height*sw > oheight, height > width = (width*sh, height*sh)
    | width > owidth = (width*sw, height*sw)
    | height > oheight = (width*sh, height*sh)
    | height > width = (width*sw, height*sw)
    | otherwise = (width*sh, height*sh)
  where
    sh = oheight/height
    sw = owidth/width
resolveSize (owidth, oheight) (width, height) Cover
    | owidth > width, oheight > height*sw = (width*sh, height*sh)
    | oheight > height, owidth > width*sh = (width*sw, height*sw)
    | owidth > width = (width*sw, height*sw)
    | oheight > height = (width*sh, height*sh)
    | oheight > height*sw = (width*sh, height*sh)
    | owidth > width*sh = (width*sw, height*sw)
    | height > width = (width*sw, height*sw)
    | otherwise = (width*sh, height*sh)
  where
    sh = oheight/height
    sw = owidth/width
resolveSize _ ret (Size Auto Auto) = ret
resolveSize _ (width, height) (Size x y) = (x', y')
  where
    x' | Absolute ret <- x = ret
        | Scale s <- x = width*s
        | Auto <- x = y' * width/height
    y' | Absolute ret <- y = ret
        | Scale s <- y = height*s
    -- NOTE: If Auto,Auto case wasn't handled above this'd be an infinite loop.
        | Auto <- y = x' * height/width