~alcinnz/CatTrap

CatTrap/Graphics/Layout/CSS/Parse.hs -rw-r--r-- 23.0 KiB
8e7be851 — Adrian Cochrane Release 0.6! 9 months ago
                                                                                
80287f8f Adrian Cochrane
9d1b071d Adrian Cochrane
80287f8f Adrian Cochrane
590ef319 Adrian Cochrane
80287f8f Adrian Cochrane
878e6868 Adrian Cochrane
80287f8f Adrian Cochrane
7301a489 Adrian Cochrane
80287f8f Adrian Cochrane
5c3e14af Adrian Cochrane
80287f8f Adrian Cochrane
5c3e14af Adrian Cochrane
590ef319 Adrian Cochrane
80287f8f Adrian Cochrane
590ef319 Adrian Cochrane
80287f8f Adrian Cochrane
9d1b071d Adrian Cochrane
5c3e14af Adrian Cochrane
878e6868 Adrian Cochrane
7301a489 Adrian Cochrane
80287f8f Adrian Cochrane
7301a489 Adrian Cochrane
80287f8f Adrian Cochrane
79c5cd46 Adrian Cochrane
80287f8f Adrian Cochrane
79c5cd46 Adrian Cochrane
80287f8f Adrian Cochrane
c4626b0b Adrian Cochrane
80287f8f Adrian Cochrane
590ef319 Adrian Cochrane
80287f8f Adrian Cochrane
9d1b071d Adrian Cochrane
5c3e14af Adrian Cochrane
7301a489 Adrian Cochrane
80287f8f Adrian Cochrane
590ef319 Adrian Cochrane
80287f8f Adrian Cochrane
9d1b071d Adrian Cochrane
5c3e14af Adrian Cochrane
7301a489 Adrian Cochrane
80287f8f Adrian Cochrane
7301a489 Adrian Cochrane
80287f8f Adrian Cochrane
5c3e14af Adrian Cochrane
80287f8f Adrian Cochrane
5c3e14af Adrian Cochrane
80287f8f Adrian Cochrane
7301a489 Adrian Cochrane
c4626b0b Adrian Cochrane
80287f8f Adrian Cochrane
878e6868 Adrian Cochrane
80287f8f Adrian Cochrane
7301a489 Adrian Cochrane
80287f8f Adrian Cochrane
878e6868 Adrian Cochrane
7301a489 Adrian Cochrane
590ef319 Adrian Cochrane
80287f8f Adrian Cochrane
878e6868 Adrian Cochrane
590ef319 Adrian Cochrane
80287f8f Adrian Cochrane
590ef319 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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
{-# LANGUAGE OverloadedStrings #-}
module Graphics.Layout.CSS.Parse (
    CSSBox(..), direction, txtOpts, BoxSizing(..), Display(..)) where
import Data.CSS.Syntax.Tokens (Token(..), NumericValue(..))
import Stylist (PropertyParser(..), TrivialPropertyParser, parseOperands,
                parseUnorderedShorthand', parseUnorderedShorthand)
import Data.Text.ParagraphLayout (PageOptions(..))
import Data.Text.ParagraphLayout.Rich (textDirection, ParagraphOptions,
            defaultParagraphOptions, paragraphAlignment, ParagraphAlignment(..))
import Data.Text.Glyphize (Direction(..))

import Graphics.Layout.Box as B
import Graphics.Text.Font.Choose (Pattern, unset)
import Graphics.Layout.CSS.Length (Unitted, parseLength', parseLength, auto, units)
import Graphics.Layout.CSS.Font (CSSFont)
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)
import qualified Data.HashMap.Lazy as HM
import Data.Text (Text, unpack)
import Debug.Trace (trace) -- For debug warnings.

-- | Parsed CSS properties relevant to layout.
data CSSBox a = CSSBox {
    -- | Which layout formula to use, a.k.a. parsed CSS display property.
    display :: Display,
    -- | (Unused) Parsed CSS box-sizing
    boxSizing :: BoxSizing,
    -- | sizing, margins, border-width, & padding CSS properties.
    -- Stores units in case they're needed for font-related units.
    cssBox :: PaddedBox Unitted Unitted, -- calc()?
    -- | Query parameters describing desired font.
    font :: Pattern,
    -- | Additional font-related CSS properties.
    font' :: CSSFont,
    -- | Caller-specified data, to parse additional CSS properties.
    inner :: a,
    -- | Properties to lower size units before passing onto to `inner`
    innerProperties :: [(Text, [Token])],
    -- | Parent to use when parsing length-expanded inner properties.
    innerParent :: a,
    -- | Grid-related CSS properties.
    gridStyles :: CSSGrid,
    -- | Grid item related CSS properties.
    cellStyles :: CSSCell,
    -- | inline-related CSS properties.
    inlineStyles :: CSSInline,
    -- | Parsed widows & orphans controlling pagination.
    pageOptions :: PageOptions,
    -- | Parsed text-alignment & other options which applies per-paragraph.
    paragraphOptions :: ParagraphOptions,
    -- | (Semi-)parsed CSS properties & HTML attributes relating to laying out
    -- HTML table elements.
    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.
txtOpts CSSBox { inlineStyles = CSSInline _ opts _ } = opts
-- | Possible values for CSS box-sizing.
data BoxSizing = BorderBox | ContentBox
-- | Empty border, to use as default value.
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 | Inline' Display deriving Eq
-- | Can the display value contain table-rows?
rowContainer CSSBox { display = d } =
    d `elem` [Table, TableHeaderGroup, TableRowGroup, TableFooterGroup]

instance PropertyParser a => PropertyParser (CSSBox a) where
    temp = CSSBox {
        boxSizing = ContentBox,
        display = Inline,
        cssBox = PaddedBox {
            B.min = Size auto auto,
            size = Size auto auto,
            nat = Size 0 0,
            B.max = Size auto auto,
            padding = noborder,
            border = noborder,
            margin = noborder
        },
        font = temp,
        font' = temp,
        inner = temp,
        innerProperties = [],
        innerParent = trace ("Parent not overriden upon " ++
            "buffering inner properties for length resolution!") temp,
        gridStyles = temp,
        cellStyles = temp,
        inlineStyles = temp,
        pageOptions = PageOptions 0 0 2 2,
        paragraphOptions = defaultParagraphOptions {
            paragraphAlignment = AlignStart
        },
        tableOptions = temp,
        flexOptions = temp
      }
    inherit parent = CSSBox {
        boxSizing = boxSizing parent,
        display = Inline,
        cssBox = cssBox (temp :: CSSBox TrivialPropertyParser),
        font = inherit $ font parent,
        font' = inherit $ font' parent,
        inner = inherit $ inner parent,
        innerProperties = [],
        innerParent = inner parent,
        gridStyles = inherit $ gridStyles parent,
        cellStyles = inherit $ cellStyles parent,
        inlineStyles = inherit $ inlineStyles parent,
        pageOptions = pageOptions parent,
        paragraphOptions = paragraphOptions parent,
        tableOptions = inherit $ tableOptions parent,
        flexOptions = inherit $ flexOptions parent
      }
    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
    longhand _ self "font-family" [Ident "initial"] =
        Just self { font = unset "family" $ font self}

    longhand _ self "box-sizing" [Ident "content-box"] = Just self {boxSizing = ContentBox}
    longhand _ self "box-sizing" [Ident "border-box"] = Just self {boxSizing = BorderBox}
    longhand _ self "box-sizing" [Ident "initial"] = Just self {boxSizing = ContentBox}

    longhand _ self@CSSBox {cssBox = box} "padding-top" toks | Just x <- parseLength toks =
        Just self { cssBox = box { padding = (padding box) { top = x } } }
    longhand _ self@CSSBox {cssBox = box} "padding-bottom" toks | Just x <- parseLength toks =
        Just self { cssBox = box { padding = (padding box) { bottom = x } } }
    longhand _ self@CSSBox {cssBox = box} "padding-left" toks | Just x <- parseLength toks =
        Just self { cssBox = box { padding = (padding box) { left = x } } }
    longhand _ self@CSSBox {cssBox = box} "padding-right" toks | Just x <- parseLength toks =
        Just self { cssBox = box { padding = (padding box) { right = x } } }
    longhand _ self@CSSBox {cssBox = box} "padding-inline-start" toks
        | Just x <- parseLength toks, DirLTR <- direction self =
            Just self { cssBox = box { padding = (padding box) { left = x } } }
        | Just x <- parseLength toks, DirRTL <- direction self =
            Just self { cssBox = box { padding = (padding box) { right = x } } }
    longhand _ self@CSSBox {cssBox = box} "padding-inline-end" toks
        | Just x <- parseLength toks, DirLTR <- direction self =
            Just self { cssBox = box { padding = (padding box) { right = x } } }
        | Just x <- parseLength toks, DirRTL <- direction self =
            Just self { cssBox = box { padding = (padding box) { left = x } } }

    longhand _ self@CSSBox {cssBox = box} "border-top-width" toks | Just x <- parseLength toks =
        Just self { cssBox = box { border = (border box) { top = x } } }
    longhand _ self@CSSBox {cssBox = box} "border-bottom-width" toks | Just x <- parseLength toks =
        Just self { cssBox = box { border = (border box) { bottom = x } } }
    longhand _ self@CSSBox {cssBox = box} "border-left-width" toks | Just x <- parseLength toks =
        Just self { cssBox = box { border = (border box) { left = x } } }
    longhand _ self@CSSBox {cssBox = box} "border-right-width" toks | Just x <- parseLength toks =
        Just self { cssBox = box { border = (border box) { right = x } } }
    longhand p self "border-inline-start-color" toks
        | DirLTR <- direction self = longhand p self "border-left-color" toks
        | DirRTL <- direction self = longhand p self "border-right-color" toks
    longhand p self "border-inline-start-width" toks
        | DirLTR <- direction self = longhand p self "border-left-width" toks
        | DirRTL <- direction self = longhand p self "border-right-width" toks
    longhand p self "border-inline-start-style" toks
        | DirLTR <- direction self = longhand p self "border-left-style" toks
        | DirRTL <- direction self = longhand p self "border-right-style" toks
    longhand p self "border-inline-end-color" toks
        | DirLTR <- direction self = longhand p self "border-right-color" toks
        | DirRTL <- direction self = longhand p self "border-left-color" toks
    longhand p self "border-inline-end-width" toks
        | DirLTR <- direction self = longhand p self "border-right-width" toks
        | DirRTL <- direction self = longhand p self "border-left-width" toks
    longhand p self "border-inline-end-style" toks
        | DirLTR <- direction self = longhand p self "border-right-style" toks
        | DirRTL <- direction self = longhand p self "border-left-style" toks
    longhand p self "border-start-start-radius" t
        | DirLTR <- direction self = longhand p self "border-top-left-radius" t
        | DirRTL <- direction self = longhand p self "border-top-right-radius" t
    longhand p self "border-start-end-radius" t
        | DirLTR <- direction self = longhand p self "border-top-right-radius" t
        | DirRTL <- direction self = longhand p self "border-top-left-radius" t
    longhand p s "border-end-start-radius" t
        | DirLTR <- direction s = longhand p s "border-bottom-left-radius" t
        | DirRTL <- direction s = longhand p s "border-bottom-right-radius" t
    longhand p s "border-end-end-radius" t
        | DirLTR <- direction s = longhand p s "border-bottom-right-radius" t
        | DirRTL <- direction s = longhand p s "border-bottom-left-radius" t

    longhand _ self@CSSBox {cssBox = box} "margin-top" toks | Just x <- parseLength toks =
        Just self { cssBox = box { margin = (margin box) { top = x } } }
    longhand _ self@CSSBox {cssBox = box} "margin-bottom" toks | Just x <- parseLength toks =
        Just self { cssBox = box { margin = (margin box) { bottom = x } } }
    longhand _ self@CSSBox {cssBox = box} "margin-left" toks | Just x <- parseLength toks =
        Just self { cssBox = box { margin = (margin box) { left = x } } }
    longhand _ self@CSSBox {cssBox = box} "margin-right" toks | Just x <- parseLength toks =
        Just self { cssBox = box { margin = (margin box) { right = x } } }
    longhand _ self@CSSBox {cssBox = box} "margin-inline-start" toks
        | Just x <- parseLength toks, DirLTR <- direction self =
            Just self { cssBox = box { margin = (margin box) { left = x } } }
        | Just x <- parseLength toks, DirRTL <- direction self =
            Just self { cssBox = box { margin = (margin box) { right = x } } }
    longhand _ self@CSSBox {cssBox = box} "margin-inline-end" toks
        | Just x <- parseLength toks, DirLTR <- direction self =
            Just self { cssBox = box { margin = (margin box) { right = x } } }
        | Just x <- parseLength toks, DirRTL <- direction self =
            Just self { cssBox = box { margin = (margin box) { left = x } } }

    -- Placeholder implementations until vertical text is implemented.
    longhand p self "padding-block-start" t = longhand p self "padding-top" t
    longhand p self "padding-block-end" t = longhand p self "padding-bottom" t
    longhand p self "margin-block-start" t = longhand p self "margin-top" t
    longhand p self "margin-block-end" t = longhand p self "margin-bottom" t
    longhand p self "border-block-start-color" toks =
        longhand p self "border-top-color" toks
    longhand p self "border-block-start-style" toks =
        longhand p self "border-top-style" toks
    longhand p self "border-block-start-width" toks =
        longhand p self "border-top-width" toks
    longhand p s "border-block-end-color" t = longhand p s "border-bottom-color" t
    longhand p s "border-block-end-style" t = longhand p s "border-bottom-style" t
    longhand p s "border-block-end-width" t = longhand p s "border-bottom-width" t

    longhand _ self@CSSBox {cssBox = box} "width" toks | Just x <- parseLength' toks =
        Just self { cssBox = box { size = (size box) { inline = x } } }
    longhand _ self@CSSBox {cssBox = box} "height" toks | Just x <- parseLength' toks =
        Just self { cssBox = box { size = (size box) { block = x } } }
    longhand _ self@CSSBox {cssBox = box} "max-width" toks | Just x <- parseLength' toks =
        Just self { cssBox = box { B.max = (B.max box) { inline = x } } }
    longhand _ self@CSSBox {cssBox = box} "min-width" toks | Just x <- parseLength' toks =
        Just self { cssBox = box { B.min = (B.min box) { inline = x } } }
    longhand _ self@CSSBox {cssBox = box} "max-height" toks | Just x <- parseLength' toks =
        Just self { cssBox = box { B.max = (B.max box) { block = x } } }
    longhand _ self@CSSBox {cssBox = box} "min-height" toks | Just x <- parseLength' toks =
        Just self { cssBox = box { B.min = (B.min box) { block = x } } }

    longhand _ self "display" [Ident "block"] = Just self { display = Block }
    longhand _ self "display" [Ident "none"] = Just self { display = None }
    longhand _ self "display" [Ident "grid"] = Just self { display = Grid }
    longhand _ self "display" [Ident "table"] = Just self { display = Table }
    longhand CSSBox { display = Table } self "display" [Ident "table-row-group"] =
        Just self { display=TableRowGroup }
    longhand CSSBox { display = Table } self "display" [Ident "table-header-group"] =
        Just self { display = TableHeaderGroup }
    longhand CSSBox { display = Table } self "display" [Ident "table-footer-group"] =
        Just self { display = TableFooterGroup }
    longhand parent self "display" [Ident "table-row"] | rowContainer parent =
        Just self { display = TableRow }
    longhand CSSBox { display = TableRow } self "display" [Ident "table-cell"] =
        Just self { display = TableCell }
    longhand CSSBox { display = Table } self "display" [Ident "table-column-group"] =
        Just self { display = TableColumnGroup }
    longhand CSSBox { display = TableColumnGroup } self "display" [Ident "table-column"] =
        Just self { display = TableColumn }
    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 "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)] =
        Just self { pageOptions = (pageOptions self) { pageOrphans = fromInteger x } }
    longhand _ self "widows" [Number _ (NVInteger x)] =
        Just self { pageOptions = (pageOptions self) { pageWidows = fromInteger x } }

    longhand _ self@CSSBox {paragraphOptions=o} "text-align" [Ident "initial"] =
        Just self { paragraphOptions = o { paragraphAlignment = AlignStart } }
    longhand _ self@CSSBox {paragraphOptions=o} "text-align" [Ident "start"] =
        Just self { paragraphOptions = o { paragraphAlignment = AlignStart } }
    longhand _ self@CSSBox {paragraphOptions=o} "text-align" [Ident "end"] =
        Just self { paragraphOptions = o { paragraphAlignment = AlignEnd } }
    longhand _ self@CSSBox {paragraphOptions=o} "text-align" [Ident "left"] =
        Just self { paragraphOptions = o { paragraphAlignment = AlignLeft } }
    longhand _ self@CSSBox {paragraphOptions=o} "text-align" [Ident "right"] =
        Just self { paragraphOptions = o { paragraphAlignment = AlignRight } }
    longhand _ self@CSSBox {paragraphOptions=o} "text-align" [Ident "center"] =
        Just self { paragraphOptions = o { paragraphAlignment = AlignCentreH } }
    -- text-align: justify is unimplemented.
    longhand p self@CSSBox { paragraphOptions = o } "text-align"
            [Ident "match-parent"] = case paragraphAlignment$paragraphOptions p of
        AlignStart | DirLTR <- direction p -> ret AlignLeft
        AlignStart | DirRTL <- direction p -> ret AlignRight
        AlignEnd | DirLTR <- direction p -> ret AlignRight
        AlignEnd | DirRTL <- direction p -> ret AlignLeft
        x -> ret x
      where ret x = Just self { paragraphOptions = o { paragraphAlignment = x } }

    longhand a b c d | Just x <- longhand (font a) (font b) c d,
        Just y <- longhand (font' a) (font' b) c d =
            Just b { font = x, font' = y } -- Those properties can overlap!
    longhand a b c d | Just font' <- longhand (font a) (font b) c d = Just b {
        font = font'
      }
    longhand a b c d | Just font <- longhand (font' a) (font' b) c d = Just b {
        font' = font
      }
    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 {
                innerProperties = (c, d):innerProperties b,
                innerParent = inner a
            }
    longhand a b c d | Just inner' <- longhand (inner a) (inner b) c d = Just b {
        inner = inner'
      }

    -- Technically a grid shorthand, but we need parent data to parse it!
    longhand CSSBox { gridStyles = parent } self "grid-area" [Ident x]
        | Just ((colS, colE), (rowS, rowE)) <- x `HM.lookup` templateAreas parent
            = Just self { cellStyles = (cellStyles self) {
                columnStart = p colS,
                columnEnd = p colE,
                rowStart = p rowS,
                rowEnd = p $ fromMaybe (length $ templateAreas parent) rowE
            }}
      where p x = Numbered x Nothing

    longhand _ _ _ _ = Nothing

    shorthand self "font" toks = case parseOperands toks of
        (a:b:c:d:toks') | ret@(_:_) <- unordered [a,b,c,d] -> inner ret toks'
        (a:b:c:toks') | ret@(_:_) <- unordered [a,b,c] -> inner ret toks'
        (a:b:toks') | ret@(_:_) <- unordered [a,b] -> inner ret toks'
        (a:toks') | ret@(_:_) <- unordered [a] -> inner ret toks'
        toks' -> inner [] toks'
      where
        unordered operands = parseUnorderedShorthand' self [
            "font-style", "font-variant", "font-weight", "font-stretch"] operands
        inner ret (size:[Delim '/']:height:family)
            | Just _ <- longhand self self "font-size" size,
              Just _ <- longhand self self "line-height" height,
              Just _ <- longhand self self "font-family" $ concat family =
                ("font-size", size):("line-height", height):
                    ("font-family", concat family):ret
            | otherwise = []
        inner ret (size:family)
            | Just _ <- longhand self self "font-size" size,
              Just _ <- longhand self self "font-family" $ concat family =
                ("font-size", size):("line-height", [Ident "initial"]):
                    ("font-family", concat family):ret
            | otherwise = []
        inner _ _ = []
    shorthand self "margin" toks
        | length x > 0 && length x <= 4, all (validProp self "margin-top") x,
            (top:right:bottom:left:_) <- cycle x =
                [("margin-top", top), ("margin-right", right),
                 ("margin-bottom", bottom), ("margin-left", left)]
      where x = parseOperands toks
    shorthand self "padding" toks
        | length x > 0 && length x <= 4, all (validProp self "padding-top") x,
            (top:right:bottom:left:_) <- cycle x =
                [("padding-top", top), ("padding-right", right),
                 ("padding-bottom", bottom), ("padding-left", left)]
      where x = parseOperands toks
    shorthand self "border-width" toks
        | length x > 0 && length x <= 4, (top:right:bottom:left:_) <- cycle x,
            all (validProp self "border-top-width") x =
                [("border-top-width", top), ("border-right-width", right),
                 ("border-bottom-width", bottom), ("border-left-width", left)]
      where x = parseOperands toks

    shorthand self k v | ret@(_:_) <- shorthand (font self) k v = ret
    shorthand self k v | ret@(_:_) <- shorthand (font' self) k v = ret
    shorthand self k v | ret@(_:_) <- shorthand (inlineStyles self) k v = ret
    shorthand self k v | ret@(_:_) <- shorthand (gridStyles self) k v = ret
    shorthand self k v | ret@(_:_) <- shorthand (cellStyles self) k v = ret
    shorthand self k v | ret@(_:_) <- shorthand (tableOptions self) k v = ret
    shorthand self k v | ret@(_:_) <- shorthand (inner self) k v = ret
    shorthand self k v
        | (v', ls)<-testLengthProp v, ret@(_:_)<-shorthand (inner self) k v' =
            [(key, map (restore ls) value) | (key, value) <- ret]
      where
        restore ls (Dimension _ (NVInteger x) "px") | x' < length ls = ls !! x'
          where x' = fromInteger x
        restore _ ret = ret
    shorthand self k v | Just _ <- longhand self self k v = [(k, v)]
        | otherwise = []

validProp self key value = isJust $ longhand self self key value

testLengthProp (tok@(Dimension _ _ unit):toks) | unit `elem` units =
    let (toks', lengths) = testLengthProp toks
    in (Dimension "" (NVInteger $ toInteger $ succ $ length lengths) "px":toks',
        tok:lengths)
testLengthProp (tok:toks) = let (toks',ls) = testLengthProp toks in (tok:toks',ls)
testLengthProp [] = ([], [])