Source file Figure.ml
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
type point = float * float
type box = { x0 : float; y0 : float; x1 : float; y1 : float }
let box (ax, ay) (bx, by) = { x0 = Float.min ax bx; y0 = Float.min ay by; x1 = Float.max ax bx; y1 = Float.max ay by }
type style = { fill : float option; pen : float }
type t =
| Line of point * point * style
| Rect of box * style
| Oval of box * style
| Text of box * string * float
| Group of t list
let union a b = { x0 = Float.min a.x0 b.x0; y0 = Float.min a.y0 b.y0; x1 = Float.max a.x1 b.x1; y1 = Float.max a.y1 b.y1 }
let rec bounds = function
| Line (a, b, _) -> box a b
| Rect (b, _) | Oval (b, _) | Text (b, _, _) -> b
| Group [] -> { x0 = 0.; y0 = 0.; x1 = 0.; y1 = 0. }
| Group (f :: rest) -> List.fold_left (fun acc f -> union acc (bounds f)) (bounds f) rest
let distance_to_segment (px, py) (ax, ay) (bx, by) =
let dx = bx -. ax and dy = by -. ay in
let len2 = (dx *. dx) +. (dy *. dy) in
let t = if len2 = 0. then 0. else Float.max 0. (Float.min 1. ((((px -. ax) *. dx) +. ((py -. ay) *. dy)) /. len2)) in
let nx = ax +. (t *. dx) and ny = ay +. (t *. dy) in
Float.sqrt (((px -. nx) ** 2.) +. ((py -. ny) ** 2.))
let inside b (x, y) = x >= b.x0 && x <= b.x1 && y >= b.y0 && y <= b.y1
let rec hit ~tolerance f ((x, y) as p) =
match f with
| Line (a, b, s) -> distance_to_segment p a b <= tolerance +. (s.pen /. 2.)
| Text (b, _, _) -> inside b p
| Rect (b, s) -> (
match s.fill with
| Some _ -> inside b p
| None ->
let corners = [ (b.x0, b.y0); (b.x1, b.y0); (b.x1, b.y1); (b.x0, b.y1) ] in
let sides = List.combine corners (List.tl corners @ [ List.hd corners ]) in
List.exists (fun (a, c) -> distance_to_segment p a c <= tolerance +. (s.pen /. 2.)) sides)
| Oval (b, s) -> (
let a = (b.x1 -. b.x0) /. 2. and r = (b.y1 -. b.y0) /. 2. in
if a <= 0. || r <= 0. then false
else
let u = (x -. (b.x0 +. a)) /. a and v = (y -. (b.y0 +. r)) /. r in
let k = Float.sqrt ((u *. u) +. (v *. v)) in
match s.fill with
| Some _ -> k <= 1.
| None -> Float.abs (k -. 1.) *. Float.min a r <= tolerance +. (s.pen /. 2.))
| Group fs -> List.exists (fun f -> hit ~tolerance f p) fs
let rec map_points m = function
| Line (a, b, s) -> Line (m a, m b, s)
| Rect (b, s) -> Rect (box (m (b.x0, b.y0)) (m (b.x1, b.y1)), s)
| Oval (b, s) -> Oval (box (m (b.x0, b.y0)) (m (b.x1, b.y1)), s)
| Text (b, text, size) ->
let x, y = m (b.x0, b.y1) in
Text ({ x0 = x; y1 = y; x1 = x +. (b.x1 -. b.x0); y0 = y -. (b.y1 -. b.y0) }, text, size)
| Group fs -> Group (List.map (map_points m) fs)
let translate dx dy = map_points (fun (x, y) -> (x +. dx, y +. dy))
let fit nb f =
let b = bounds f in
let scale lo hi nlo nhi v = if hi = lo then nlo else nlo +. ((v -. lo) *. (nhi -. nlo) /. (hi -. lo)) in
map_points (fun (x, y) -> (scale b.x0 b.x1 nb.x0 nb.x1 x, scale b.y0 b.y1 nb.y0 nb.y1 y)) f
let handles = function
| Line (a, b, _) -> [ a; b ]
| f ->
let b = bounds f in
let mx = (b.x0 +. b.x1) /. 2. and my = (b.y0 +. b.y1) /. 2. in
[ (b.x0, b.y1); (b.x1, b.y1); (b.x1, b.y0); (b.x0, b.y0); (mx, b.y1); (b.x1, my); (mx, b.y0); (b.x0, my) ]
let drag_handle f i (x, y) =
match f with
| Line (a, b, s) -> if i = 0 then Line ((x, y), b, s) else Line (a, (x, y), s)
| f ->
let b = bounds f in
let l, r, t, bo =
match i with
| 0 -> (Some x, None, Some y, None)
| 1 -> (None, Some x, Some y, None)
| 2 -> (None, Some x, None, Some y)
| 3 -> (Some x, None, None, Some y)
| 4 -> (None, None, Some y, None)
| 5 -> (None, Some x, None, None)
| 6 -> (None, None, None, Some y)
| _ -> (Some x, None, None, None)
in
let v o d = Option.value o ~default:d in
fit (box (v l b.x0, v bo b.y0) (v r b.x1, v t b.y1)) f
let rec restyle g = function
| Line (a, b, s) -> Line (a, b, g s)
| Rect (b, s) -> Rect (b, g s)
| Oval (b, s) -> Oval (b, g s)
| Text _ as t -> t
| Group fs -> Group (List.map (restyle g) fs)