shorthand.ml 10 KB

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