shorthand.ml 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. (* CSS shorthand creation based on:
  2. * - http://www.cssshorthand.com/
  3. *)
  4. open Types
  5. open Util
  6. module SS = Set.Make(String)
  7. let pattern = Str.regexp ("^\\(background\\|border\\|font\\|list-style" ^
  8. "\\|outline\\|padding\\|margin\\)-\\(.*\\)$")
  9. let order = function
  10. | "background" -> ["color"; "image"; "repeat"; "attachment"; "position-x";
  11. "position-y"]
  12. | "border" -> ["width"; "style"; "color"]
  13. | "font" -> ["style"; "variant"; "weight"; "size"; "family"]
  14. | "list-style" -> ["type"; "position"; "image"]
  15. | "outline" -> ["color"; "style"; "width"]
  16. | "margin"
  17. | "padding" -> ["top"; "right"; "bottom"; "left"]
  18. | _ -> failwith "not a shorthand property"
  19. let rec decls_mem name = function
  20. | [] -> false
  21. | (nm, _, _) :: _ when nm = name -> true
  22. | _ :: tl -> decls_mem name tl
  23. (* find the value of the last declaration of some property (since the earlier
  24. * values are overridden), unless an earlier !important value was found *)
  25. let decls_find name decls =
  26. let rec wrap known = function
  27. | [] -> known
  28. | (nm, value, true) :: _ when nm = name -> Some value
  29. | (nm, value, false) :: tl when nm = name -> wrap (Some value) tl
  30. | _ :: tl -> wrap known tl
  31. in
  32. match wrap None decls with
  33. | None -> raise Not_found
  34. | Some value -> value
  35. let fold base decls =
  36. let rec filter = function
  37. | [] -> []
  38. (* `font-size` and `line-height` are slash-separated in `font` *)
  39. | "size" :: tl when base = "font" && decls_mem "line-height" decls ->
  40. let font_size = decls_find "font-size" decls in
  41. let line_height = decls_find "line-height" decls in
  42. Nary ("/", [font_size; line_height]) :: filter tl
  43. | name :: tl when decls_mem (base ^ "-" ^ name) decls ->
  44. decls_find (base ^ "-" ^ name) decls :: filter tl
  45. | _ :: tl -> filter tl
  46. in
  47. filter (order base)
  48. let shorten_box_dims = function
  49. | [top; right; bottom; left]
  50. when top = bottom && right = left && top = right -> [top]
  51. | [top; right; bottom; left] when top = bottom && right = left -> [top; right]
  52. | [top; right; bottom; left] when right = left -> [top; right; bottom]
  53. | dims -> dims
  54. let shorten decls = function
  55. (* `font-size` and `font-family` are required for `font` *)
  56. | "font" when decls_mem "font-size" decls && decls_mem "font-family" decls ->
  57. Some (Concat (fold "font" decls))
  58. (* `border-style` is required for `border` *)
  59. | "border" when decls_mem "border-style" decls ->
  60. Some (Concat (fold "border" decls))
  61. (* others require at least one property, which is the case when this function
  62. * is called *)
  63. | ("background" | "list-style" | "outline") as base ->
  64. Some (Concat (fold base decls))
  65. (* margin and padding can only be shorthanded when all directions are known,
  66. * merging into even shorter shorthands is done by `shorten_box_dims` *)
  67. | ("margin" | "padding") as base when
  68. let has dir = decls_mem (base ^ "-" ^ dir) decls in
  69. has "top" && has "right" && has "bottom" && has "left" ->
  70. let get dir = decls_find (base ^ "-" ^ dir) decls in
  71. Some (Concat (shorten_box_dims [get "top"; get "right";
  72. get "bottom"; get "left"]))
  73. | _ -> None
  74. let rec list_from i = function
  75. | [] when i > 0 -> raise (Invalid_argument "l")
  76. | [] -> [] (* make the compiler happy *)
  77. | l when i = 0 -> l
  78. | _ :: tl -> list_from (i - 1) tl
  79. let is_width = function
  80. | Ident ("thin" | "thick" | "medium")
  81. | Number _ -> true
  82. | _ -> false
  83. let rec unfold = function
  84. | [] -> []
  85. (* do not unfold "<shorthand>: inherit;" *)
  86. | (("background" | "border" | "font" | "list-style" | "outline" | "margin" |
  87. "padding"), Ident "inherit", _) as orig :: tl ->
  88. orig :: unfold tl
  89. (* background: [color] [image] [repeat] [attachment] [position] *)
  90. | ("background", Concat values, imp) :: tl ->
  91. let make sub value = ("background-" ^ sub, value, imp) in
  92. let id_color = function
  93. | [] -> []
  94. | [color] when Color_names.is_color color -> [make "color" color]
  95. | tl -> raise (Box_error (Expr (Concat tl), "invalid background shortcut"))
  96. (*| _ -> failwith "invalid background shortcut"*)
  97. in
  98. let id_repeat = function
  99. | repeat :: (Uri _ as image) :: tl ->
  100. make "repeat" repeat :: make "image" image :: id_color tl
  101. | Uri _ as image :: tl ->
  102. make "image" image :: id_color tl
  103. | tl -> id_color tl
  104. in
  105. let id_attachment = function
  106. | Ident _ as attachment :: (Ident _ as repeat) :: tl ->
  107. make "attachment" attachment :: make "repeat" repeat :: id_repeat tl
  108. | Ident ("scroll" | "fixed") as attachment :: (Uri url :: _ as tl) ->
  109. make "attachment" attachment :: id_repeat tl
  110. | (_ :: Uri _ :: _) as tl
  111. | tl -> id_repeat tl
  112. in
  113. let id_pos = function
  114. | Number _ as posy :: (Number _ as posx) :: tl
  115. | (Ident ("top" | "center" | "bottom") as posy) ::
  116. (Ident ("left" | "center" | "right") as posx) :: tl ->
  117. make "position-y" posy :: make "position-x" posx :: id_attachment tl
  118. | tl -> id_attachment tl
  119. in
  120. List.rev (id_pos (List.rev values)) @ unfold tl
  121. | ("background", (Uri _ as image), imp) :: tl ->
  122. ("background-image", image, imp) :: unfold tl
  123. | ("background", color, imp) :: tl ->
  124. ("background-color", color, imp) :: unfold tl
  125. (* border: [width] style [color] *)
  126. | ("border", Concat [Ident _ as style], imp) :: tl ->
  127. ("border-style", style, imp) :: unfold tl
  128. | ("border", Concat [width; Ident _ as style; color], imp) :: tl ->
  129. ("border-width", width, imp) ::
  130. ("border-style", style, imp) ::
  131. ("border-color", color, imp) :: unfold tl
  132. | ("border", Concat [Number _ as width; Ident _ as style], imp) :: tl ->
  133. ("border-width", width, imp) ::
  134. ("border-style", style, imp) :: unfold tl
  135. | ("border", Concat [Ident _ as style; color], imp) :: tl ->
  136. ("border-style", style, imp) ::
  137. ("border-color", color, imp) :: unfold tl
  138. (* font: [style] [variant] [weight] size[/line-height] family *)
  139. | ("font", Concat values, imp) as orig :: tl ->
  140. let replacement =
  141. let make sub value = ("font-" ^ sub, value, imp) in
  142. let identify options =
  143. let return sub = assert (List.mem sub options); sub in
  144. function
  145. | Ident "normal" -> List.hd options
  146. | Ident ("italic" | "oblique") -> return "style"
  147. | Ident "small-caps" -> return "variant"
  148. | _ -> return "weight"
  149. in
  150. match values with
  151. | [size; family] ->
  152. [make "size" size; make "family" family]
  153. | [first; size; family] ->
  154. [make (identify ["weight"; "variant"; "style"] first) first;
  155. make "size" size; make "family" family]
  156. | [first; second; size; family] ->
  157. [make (identify ["variant"; "style"] first) first;
  158. make (identify ["weight"; "variant"] second) second;
  159. make "size" size; make "family" family]
  160. | [style; variant; weight; size; family] ->
  161. [make "style" style; make "variant" variant; make "weight" weight;
  162. make "size" size; make "family" family]
  163. | _ -> [orig]
  164. in
  165. let rec split_size = function
  166. | [] -> []
  167. | ("font-size", Nary ("/", [size; line_height]), _) :: tl ->
  168. ("font-size", size, imp) ::
  169. ("line-height", line_height, imp) :: tl
  170. | hd :: tl -> hd :: split_size tl
  171. in
  172. split_size replacement @ unfold tl
  173. (* list-style: [type] [position] [image] *)
  174. | ("list-style", Concat [ltype; pos; image], imp) :: tl ->
  175. ("list-style-type", ltype, imp) ::
  176. ("list-style-position", pos, imp) ::
  177. ("list-style-image", image, imp) :: unfold tl
  178. | ("list-style", Concat [Ident _ as pos; Uri _ as image], imp) :: tl ->
  179. ("list-style-position", pos, imp) ::
  180. ("list-style-image", image, imp) :: unfold tl
  181. | ("list-style", Concat [ltype; Ident _ as pos], imp) :: tl ->
  182. ("list-style-type", ltype, imp) ::
  183. ("list-style-position", pos, imp) :: unfold tl
  184. (* margin: top right bottom left
  185. * | top right-left bottom
  186. * | top-bottom right-left
  187. * | top right bottom left
  188. * | all
  189. *)
  190. | (("margin"| "padding") as base, value, imp) :: tl ->
  191. let (top, right, bottom, left) =
  192. match value with
  193. | Concat [top; right; bottom; left] ->
  194. (top, right, bottom, left)
  195. | Concat [top; right; bottom] ->
  196. (top, right, bottom, right)
  197. | Concat [top; right] ->
  198. (top, right, top, right)
  199. | _ ->
  200. (value, value, value, value)
  201. in
  202. let make dir value = (base ^ "-" ^ dir, value, imp) in
  203. make "top" top :: make "right" right :: make "bottom" bottom ::
  204. make "left" left :: unfold tl
  205. | hd :: tl ->
  206. hd :: unfold tl
  207. let make_shorthands decls =
  208. (* unfold currently existing shorthands into separate properties for merging
  209. * with override properties that are defined later on *)
  210. (*let decls = unfold decls in
  211. XXX: done by main function for correct pruning of duplicate declarations*)
  212. (* find shorthand names for which properties are present *)
  213. let rec find_props = function
  214. | [] -> SS.empty
  215. | (name, value, false) :: tl when Str.string_match pattern name 0 ->
  216. let base = Str.matched_group 1 name in
  217. let sub = Str.matched_group 2 name in
  218. if List.mem sub (order base)
  219. then SS.add base (find_props tl)
  220. else find_props tl
  221. | _ :: tl -> find_props tl
  222. in
  223. let try_shorthands = find_props decls in
  224. (* try to generate shorthands for the matched base properties *)
  225. let rec replace base tl =
  226. match shorten decls base with
  227. | None -> tl
  228. | Some short_value -> (base, short_value, false) :: tl
  229. in
  230. let shorthands = SS.fold replace try_shorthands [] in
  231. (* filter out the original, partial properties, and append the shorthands *)
  232. let keep_prop = function
  233. | ("line-height", _, _) ->
  234. not (decls_mem "font" shorthands)
  235. | (name, _, _) ->
  236. not (Str.string_match pattern name 0) ||
  237. let base = Str.matched_group 1 name in
  238. let sub = Str.matched_group 2 name in
  239. not (List.mem sub (order base)) || not (decls_mem base shorthands)
  240. in
  241. List.filter keep_prop decls @ shorthands
  242. let transform = function
  243. | Statement (Ruleset (selectors, decls)) ->
  244. Statement (Ruleset (selectors, make_shorthands decls))
  245. | v -> v
  246. let compress = Util.transform_stylesheet transform
  247. let transform_unfold = function
  248. | Statement (Ruleset (selectors, decls)) ->
  249. Statement (Ruleset (selectors, unfold decls))
  250. | v -> v
  251. let unfold_stylesheet = Util.transform_stylesheet transform_unfold