~alcinnz/Mondrian

ref: 396e7db5dd94429a8522e091d806bf80f25dbc55 Mondrian/lib/Graphics/Rendering/Rect/CSS/Backgrounds.hs -rw-r--r-- 13.9 KiB
396e7db5 — Adrian Cochrane Implement conic gradients, support elliptical extents. 1 year, 6 months ago
                                                                                
94547420 Adrian Cochrane
483226a3 Adrian Cochrane
396e7db5 Adrian Cochrane
4fb39760 Adrian Cochrane
6236cf8a Adrian Cochrane
1adb7b35 Adrian Cochrane
4fb39760 Adrian Cochrane
6236cf8a Adrian Cochrane
dc6acbc9 Adrian Cochrane
4fb39760 Adrian Cochrane
6236cf8a Adrian Cochrane
94547420 Adrian Cochrane
6236cf8a Adrian Cochrane
1adb7b35 Adrian Cochrane
dc6acbc9 Adrian Cochrane
1adb7b35 Adrian Cochrane
4fb39760 Adrian Cochrane
94547420 Adrian Cochrane
6236cf8a Adrian Cochrane
1adb7b35 Adrian Cochrane
745d80f2 Adrian Cochrane
ec7d175b Adrian Cochrane
396e7db5 Adrian Cochrane
ec7d175b Adrian Cochrane
483226a3 Adrian Cochrane
ec7d175b Adrian Cochrane
1adb7b35 Adrian Cochrane
4fb39760 Adrian Cochrane
94547420 Adrian Cochrane
6236cf8a Adrian Cochrane
4fb39760 Adrian Cochrane
1adb7b35 Adrian Cochrane
6236cf8a Adrian Cochrane
94547420 Adrian Cochrane
6236cf8a Adrian Cochrane
745d80f2 Adrian Cochrane
dc6acbc9 Adrian Cochrane
1adb7b35 Adrian Cochrane
745d80f2 Adrian Cochrane
c4d7272d Adrian Cochrane
396e7db5 Adrian Cochrane
745d80f2 Adrian Cochrane
c4d7272d Adrian Cochrane
745d80f2 Adrian Cochrane
e8160393 Adrian Cochrane
745d80f2 Adrian Cochrane
e8160393 Adrian Cochrane
c4d7272d Adrian Cochrane
745d80f2 Adrian Cochrane
483226a3 Adrian Cochrane
ec7d175b Adrian Cochrane
483226a3 Adrian Cochrane
ec7d175b Adrian Cochrane
396e7db5 Adrian Cochrane
dc6acbc9 Adrian Cochrane
396e7db5 Adrian Cochrane
4fb39760 Adrian Cochrane
745d80f2 Adrian Cochrane
4fb39760 Adrian Cochrane
745d80f2 Adrian Cochrane
6236cf8a Adrian Cochrane
94547420 Adrian Cochrane
6236cf8a Adrian Cochrane
4fb39760 Adrian Cochrane
6236cf8a Adrian Cochrane
745d80f2 Adrian Cochrane
6236cf8a Adrian Cochrane
1adb7b35 Adrian Cochrane
6236cf8a Adrian Cochrane
4fb39760 Adrian Cochrane
6236cf8a Adrian Cochrane
745d80f2 Adrian Cochrane
6236cf8a Adrian Cochrane
745d80f2 Adrian Cochrane
4fb39760 Adrian Cochrane
745d80f2 Adrian Cochrane
4fb39760 Adrian Cochrane
745d80f2 Adrian Cochrane
4fb39760 Adrian Cochrane
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
{-# LANGUAGE OverloadedStrings, FlexibleInstances #-}
module Graphics.Rendering.Rect.CSS.Backgrounds (Backgrounds(..),
    Pattern(..), RadialShape(..), Extent(..),
    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 Extent (Length, Length) [(C, Length)]
    | Conical Float (Length, Length) [(C, Length)]
    deriving (Eq, Show, Read)
data RadialShape = Circle | Ellipse deriving (Eq, Show, Read)
data Extent = Extent Length Length | ClosestCorner
    | ClosestSide | FarthestCorner | FarthestSide 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 angularUnits,
                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 FarthestCorner center cs
            | (shp, org, ext, ts) <- radArgs toks,
                Just cs@(_:_:_) <- colourStops pp ts = Just $ Radial shp ext org cs
          where
            radArgs (Ident s:Ident "at":ts) | Just shape <- radShape s,
                    Just (org, ts') <- position ts =
                (shape, org, FarthestCorner, ts')
            radArgs (Ident "at":ts) | Just (org, ts') <- position ts =
                (Ellipse, org, FarthestCorner, ts')
            radArgs (Ident "circle":Ident "at":ts)
                | Just (org, ts') <- position ts = (Circle,org,FarthestCorner,ts')
            radArgs (Ident "circle":ts)
                | Just (_, Ident "at":ts') <- circleExt ts,
                        Just (org, stops) <- position ts' =
                    (Circle, org, FarthestCorner, stops)
                | Just (_,ts')<-circleExt ts=(Circle,center,FarthestCorner,ts')
                | otherwise = (Circle, center, FarthestCorner, ts)
            radArgs (Ident "ellipse":Ident "at":ts)
                | Just (org,ts')<-position ts=(Ellipse,org,FarthestCorner,ts')
            radArgs (Ident "ellipse":ts)
                | Just (_, Ident "at":ts') <- ellipseExt ts,
                        Just (org, stops) <- position ts' =
                    (Ellipse, org, FarthestCorner, stops)
                | Just (_,ts')<-ellipseExt ts=(Ellipse,center,FarthestCorner,ts')
                | otherwise = (Ellipse, center, FarthestCorner, ts)
            radArgs ts | Just (_, Ident "at":ts') <- ellipseExt ts,
                        Just (org, stops) <- position ts' =
                    (Ellipse, org, FarthestCorner, stops)
                | Just (_, Ident "ellipse":Ident "at":ts') <- ellipseExt ts,
                        Just (org, stops) <- position ts' =
                    (Ellipse, org, FarthestCorner, stops)
                | Just (_, Ident "ellipse":ts') <- ellipseExt ts =
                    (Ellipse, center, FarthestCorner, ts')
                | Just (_, Ident "circle":Ident "at":ts') <- circleExt ts,
                        Just (org, stops) <- position ts' =
                    (Circle, org, FarthestCorner, stops)
                | Just (_, Ident "circle":ts') <- circleExt ts =
                    (Circle, center, FarthestCorner, ts')
                | otherwise = (Ellipse, center, FarthestCorner, ts)
            radShape "circle" = Just Circle
            radShape "ellipse" = Just Ellipse
            radShape _ = Nothing
            radExt (Ident "closest-corner":ts) = Just (ClosestCorner, ts)
            radExt (Ident "closest-side":ts) = Just (ClosestSide, ts)
            radExt (Ident "farthest-corner":ts) = Just (FarthestCorner, ts)
            radExt (Ident "farthest-side":ts) = Just (FarthestSide, ts)
            radExt _ = Nothing
            ellipseExt ts | Just ret <- radExt ts = Just ret
            ellipseExt (Percentage _ x:Percentage _ y:ts) =
                Just (p' x `Extent` p' y, ts)
            ellipseExt (Percentage _ x:Dimension _ y "px":ts) =
                Just (p' x `Extent` f' y, ts)
            ellipseExt (Dimension _ x "px":Percentage _ y:ts) =
                Just (f' x `Extent` p' y, ts)
            ellipseExt (Dimension _ x "px":Dimension _ y "px":ts) =
                Just (f' x `Extent` p' y, ts)
            ellipseExt _ = Nothing
            circleExt (Dimension _ x "px":ts) = Just (f' x `Extent` f' x, ts)
            circleExt ts = radExt ts
        -- NOTE: Not implementing colourspaces yet...
        inner (Function "conic-gradient":Ident "from":Dimension _ x unit:
                    Ident "at":ts)
            | Just (org, ts') <- position ts, Just s <- lookup unit angularUnits,
              Just stops@(_:_:_) <- colourStops pp ts' =
                Just $ Conical (f x*s) org stops
        inner (Function "conic-gradient":Ident "from":Dimension _ x unit:ts)
            | Just s <- lookup unit angularUnits,
              Just stops@(_:_:_) <- colourStops pp ts =
                Just $ Conical (f x*s) center stops
        inner (Function "conic-gradient":Ident "at":ts)
            | Just (org, ts') <- position ts,
                Just cs@(_:_:_) <- colourStops pp ts' = Just $ Conical 0 org cs
        inner (Function "conic-gradient":ts)
            | Just stops@(_:_:_) <- colourStops pp (Comma:ts) =
                Just $ Conical 0 center stops
        inner _ = Nothing

        angularUnits = [("deg",pi/180),("grad",pi/200),("rad",1),("turn",2*pi)]
        center = (Scale 0.5, Scale 0.5)
        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 -> p' a
                Dimension _ a "px" -> f' a
                _ -> Auto,
            case y of
                Ident "top" -> Scale 0
                Ident "center" -> Scale 0.5
                Ident "right" -> Scale 1
                Percentage _ a -> p' a
                Dimension _ a "px" -> f' a
                _ -> Auto),
            ts) of
                ((Auto, _), _) -> Nothing
                ((_, Auto), _) -> Nothing
                ret -> Just ret
        p' = Scale . p
        f' = Absolute . f
    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