diff --git a/examples/axes.ml b/examples/axes.ml
index 1ff41ee..21ad375 100644
--- a/examples/axes.ml
+++ b/examples/axes.ml
@@ -1,11 +1,12 @@
(* simple example demonstrating drawing x and y axes for debugging or plots *)
-open Joy
+
+open Joy.Svg
let _ =
(* intialize rendering context with the axes flag set to true *)
init ~axes:true ();
(* set background to opaque white *)
let c = circle 50 in
- render c;
+ show [ c ];
(* Write to PNG! *)
write ~filename:"axes.png" ()
diff --git a/examples/canvas.ml b/examples/canvas.ml
new file mode 100644
index 0000000..b1c6771
--- /dev/null
+++ b/examples/canvas.ml
@@ -0,0 +1,7 @@
+open Joy.Canvas
+
+let () =
+ init ~size:(400, 400) ();
+ let c = circle 100 in
+ let r = rectangle 200 150 in
+ show [ c; r ]
diff --git a/examples/circle.ml b/examples/circle.ml
index e3e911b..23222a5 100644
--- a/examples/circle.ml
+++ b/examples/circle.ml
@@ -1,7 +1,7 @@
-open Joy
+open Joy.Svg
let () =
init ();
let c = circle 50 in
- render c;
+ show [ c ];
write ~filename:"circle.png" ()
diff --git a/examples/circle_packing.ml b/examples/circle_packing.ml
index e94d733..4ef1e90 100644
--- a/examples/circle_packing.ml
+++ b/examples/circle_packing.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
(* global constants // RNG initialization *)
let w, h = (900., 900.)
@@ -94,9 +94,7 @@ let () =
let circles =
List.map
(fun ((x, y), radius) ->
- circle
- ~c:{x; y}
- (int_of_float radius)
+ circle ~c:{ x; y } (int_of_float radius)
|> with_stroke (rand_nth palette))
concentric
in
diff --git a/examples/circle_row_joy.ml b/examples/circle_row_joy.ml
index e4c419b..6e25a3c 100644
--- a/examples/circle_row_joy.ml
+++ b/examples/circle_row_joy.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let () =
init ();
diff --git a/examples/color.ml b/examples/color.ml
index bad55cb..13db095 100644
--- a/examples/color.ml
+++ b/examples/color.ml
@@ -1,7 +1,7 @@
-open Joy
+open Joy.Svg
let _ =
init ();
let c = circle 50 |> with_stroke red in
- render c;
+ show [ c ];
write ~filename:"color.png" ()
diff --git a/examples/complex.ml b/examples/complex.ml
index 615d15a..c80746e 100644
--- a/examples/complex.ml
+++ b/examples/complex.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
(*
Complex shapes can also be created from lists of shapes.
diff --git a/examples/concentric_circles.ml b/examples/concentric_circles.ml
index 4f9a21d..7a7c880 100644
--- a/examples/concentric_circles.ml
+++ b/examples/concentric_circles.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let () =
init ();
@@ -12,5 +12,5 @@ let () =
| _, _ -> arr
in
let circles = complex (make_concentric [] 21) in
- render circles;
+ show [ circles ];
write ~filename:"concentric_circles.png" ()
diff --git a/examples/donut_with_scale.ml b/examples/donut_with_scale.ml
index 3cc0f03..5ce4ae4 100644
--- a/examples/donut_with_scale.ml
+++ b/examples/donut_with_scale.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let () =
init ();
diff --git a/examples/dune b/examples/dune
index c5d0153..d7ca53a 100644
--- a/examples/dune
+++ b/examples/dune
@@ -103,6 +103,12 @@
(modules donut_with_scale)
(libraries joy))
+(executable
+ (name canvas)
+ (modes js)
+ (modules canvas)
+ (libraries joy))
+
(executable
(name color)
(modules color)
diff --git a/examples/ellipse.ml b/examples/ellipse.ml
index 0898890..deae301 100644
--- a/examples/ellipse.ml
+++ b/examples/ellipse.ml
@@ -1,9 +1,9 @@
-open Joy
+open Joy.Svg
let () =
init ();
(* create an ellipse *)
let e = ellipse 100 75 in
(* render it *)
- render e;
+ show [ e ];
write ~filename:"ellipse.png" ()
diff --git a/examples/higher_transforms.ml b/examples/higher_transforms.ml
index d4921b0..a320d0d 100644
--- a/examples/higher_transforms.ml
+++ b/examples/higher_transforms.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
(* Higher order transformations can be composed with `comp`,
which applies its function args right-to-left.
@@ -10,5 +10,5 @@ let () =
init ();
let initial = rectangle ~c:(point (-250) (-250)) 100 100 in
let shapes = repeat 32 transform initial in
- render shapes;
+ show [ shapes ];
write ~filename:"higher_transforms.png" ()
diff --git a/examples/line.ml b/examples/line.ml
index 2c250c1..0004198 100644
--- a/examples/line.ml
+++ b/examples/line.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let size = 800
let interval = 16
diff --git a/examples/polygon.ml b/examples/polygon.ml
index 91c691c..603713c 100644
--- a/examples/polygon.ml
+++ b/examples/polygon.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let size = 100.
@@ -8,5 +8,5 @@ let () =
polygon
[ { x = -.size; y = 0. }; { x = 0.; y = size }; { x = size; y = 0. } ]
in
- render poly;
+ show [ poly ];
write ~filename:"polygon.png" ()
diff --git a/examples/rectangle.ml b/examples/rectangle.ml
index d95d3b1..d53b819 100644
--- a/examples/rectangle.ml
+++ b/examples/rectangle.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let () =
init ();
diff --git a/examples/rectangle_canvas.ml b/examples/rectangle_canvas.ml
index d172962..d067e05 100644
--- a/examples/rectangle_canvas.ml
+++ b/examples/rectangle_canvas.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let () =
init ~size:(500, 300) ();
diff --git a/examples/repeat.ml b/examples/repeat.ml
index 860fd72..7f34a22 100644
--- a/examples/repeat.ml
+++ b/examples/repeat.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
(*
demonstration of the repeat function
@@ -12,5 +12,5 @@ let () =
init ();
let circle = circle ~c:(point (-100) 0) 50 in
let shapes = repeat 10 (translate 10 0) circle in
- render shapes;
+ show [ shapes ];
write ~filename:"repeat.png" ()
diff --git a/examples/rotate.ml b/examples/rotate.ml
index 624756a..fb0321c 100644
--- a/examples/rotate.ml
+++ b/examples/rotate.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let max = 32.
let rec range a b = if a > b then [] else a :: range (a +. 1.) b
diff --git a/examples/square.ml b/examples/square.ml
index 542788c..257c4a2 100644
--- a/examples/square.ml
+++ b/examples/square.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let () =
init ();
diff --git a/examples/star.ml b/examples/star.ml
index 8164804..a778295 100644
--- a/examples/star.ml
+++ b/examples/star.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let outer_radius = 200.
let inner_radius = 80.
@@ -18,5 +18,5 @@ let () =
init ();
set_line_width 3;
let star = List.init points star_section |> List.flatten |> polygon in
- render star;
+ show [ star ];
write ~filename:"star.png" ()
diff --git a/examples/translate_circle.ml b/examples/translate_circle.ml
index c7d17c1..c07cc28 100644
--- a/examples/translate_circle.ml
+++ b/examples/translate_circle.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let () =
init ();
diff --git a/examples/translate_ellipse.ml b/examples/translate_ellipse.ml
index 20af612..2561a95 100644
--- a/examples/translate_ellipse.ml
+++ b/examples/translate_ellipse.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let () =
init ();
diff --git a/examples/translate_rectangle.ml b/examples/translate_rectangle.ml
index b3c6bb1..09ed148 100644
--- a/examples/translate_rectangle.ml
+++ b/examples/translate_rectangle.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let () =
init ();
diff --git a/examples/triangle.ml b/examples/triangle.ml
index ef0b8a7..e7b44ca 100644
--- a/examples/triangle.ml
+++ b/examples/triangle.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let size = 100.
@@ -8,5 +8,5 @@ let () =
polygon
[ { x = -.size; y = 0. }; { x = 0.; y = size }; { x = size; y = 0. } ]
in
- render triangle;
+ show [ triangle ];
write ~filename:"triangle.png" ()
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..3561cc1
--- /dev/null
+++ b/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Joy
+
+
+
+
+
diff --git a/lib/canvas.ml b/lib/canvas.ml
new file mode 100644
index 0000000..a4c1bbd
--- /dev/null
+++ b/lib/canvas.ml
@@ -0,0 +1,218 @@
+module Html = Js_of_ocaml.Dom_html
+module Dom = Js_of_ocaml.Dom
+module Js = Js_of_ocaml.Js
+
+(* JS type conversion helpers *)
+let str = Js.string
+let bl = Js.bool
+
+(* aliases for globals *)
+let doc = Html.document
+
+(* Needed for 'write'/image save*)
+let _window = Html.window
+
+module C : Modules.Impl = struct
+ type context = {
+ context : Html.canvasRenderingContext2D Js.t;
+ size : int * int;
+ axes : bool;
+ }
+
+ let context = ref None
+
+ exception Context of string
+
+ (* Not working, could use help fixing *)
+ let () =
+ Printexc.register_printer (fun e ->
+ match e with Context err -> Some ("Context: " ^ err) | _ -> None)
+
+ let fail () = raise (Context "not initialized")
+
+ let create_canvas size =
+ let w, h = size in
+ let canvas = Html.createCanvas doc in
+ canvas##.width := w;
+ canvas##.height := h;
+ canvas
+
+ let begin_path ctx = ctx##beginPath
+ let close_path ctx = ctx##closePath
+
+ (* sets background color *)
+ let background { context; size; _ } color =
+ let rgba (r, g, b, a) =
+ str (Printf.sprintf "rgba(%d, %d, %d, %d)" r g b a)
+ in
+ let col = rgba color in
+ let w, h = size in
+ begin_path context;
+ context##.fillStyle := col;
+ context##fillRect 0. 0. (float_of_int w) (float_of_int h);
+ close_path context
+
+ (** Returns 'axes' field of context *)
+ let axes ctx = ctx.axes
+
+ (** Returns 'size' field of context *)
+ let resolution () =
+ match !context with Some ctx -> ctx.size | None -> fail ()
+
+ let set_line_width lw =
+ match !context with
+ | Some { context; _ } -> context##.lineWidth := float_of_int lw
+ | None -> fail ()
+
+ (* TODO: writ this fn, blob API + coercion in JSOO is tough, not sure where
+ all the methods etc I need are *)
+ let write _ctx _filename =
+ let _save_image _filename _blob =
+ (* Create anchor element, bind download fn to anchor,
+ and then programmatically click anchor to download png *)
+ ()
+ in
+ (* Coerce canvas to blob, bind callback *)
+ ()
+
+ let save () =
+ match !context with Some { context; _ } -> context##save | None -> fail ()
+
+ let restore () =
+ match !context with
+ | Some { context; _ } -> context##restore
+ | None -> fail ()
+
+ let init_context background_color line_width ((w, h) as size) axes =
+ if Option.is_some !context then
+ raise (Context "context already initialized")
+ else
+ let canvas = create_canvas size in
+ Dom.appendChild doc##.body canvas;
+ let ctx = canvas##getContext Html._2d_ in
+ ctx##translate (w / 2 |> float_of_int) (h / 2 |> float_of_int);
+ ctx##.lineWidth := float_of_int line_width;
+ let temp = { context = ctx; size; axes } in
+ background temp background_color;
+ context := Some temp
+
+ open Shape
+
+ let stroke' ctx (r, g, b) =
+ let clr_str = Printf.sprintf "rgb(%d, %d, %d)" r g b |> str in
+ ctx##.strokeStyle := clr_str;
+ ctx##stroke
+
+ let fill' ctx (r, g, b) =
+ let clr_str = Printf.sprintf "rgb(%d, %d, %d)" r g b |> str in
+ ctx##.fillStyle := clr_str;
+ ctx##fill
+
+ let draw_circle ctx { c; radius; stroke; fill } =
+ let { x; y } = c in
+ begin_path ctx;
+ ctx##arc x y radius 0. (2. *. Float.pi) (bl false);
+ Option.iter (stroke' ctx) stroke;
+ Option.iter (fill' ctx) fill;
+ close_path ctx
+
+ let draw_line ctx { a = { x = x1; y = y1 }; b = { x = x2; y = y2 }; stroke } =
+ begin_path ctx;
+ ctx##moveTo x1 y1;
+ ctx##lineTo x2 y2;
+ stroke' ctx stroke;
+ ctx##moveTo 0. 0.;
+ close_path ctx
+
+ type curve = float * float * float * float * float * float
+
+ let create_control_points (c, rx, ry) : float point * curve * curve =
+ let { x; y } = c in
+ let half_height = ry /. 2. in
+ let width_two_thirds = rx *. (2. /. 3.) in
+ ( { x; y = y -. half_height },
+ ( x +. width_two_thirds,
+ y -. half_height,
+ x +. width_two_thirds,
+ y +. half_height,
+ x,
+ y +. half_height ),
+ ( x -. width_two_thirds,
+ y +. half_height,
+ x -. width_two_thirds,
+ y -. half_height,
+ x,
+ y -. half_height ) )
+
+ let draw_ellipse ctx { c; rx; ry; stroke; fill } =
+ begin_path ctx;
+ let start, curve_one, curve_two = create_control_points (c, rx, ry) in
+ ctx##moveTo start.x start.y;
+ let x1, y1, x2, y2, x3, y3 = curve_one in
+ ctx##bezierCurveTo x1 y1 x2 y2 x3 y3;
+ let x1, y1, x2, y2, x3, y3 = curve_two in
+ ctx##bezierCurveTo x1 y1 x2 y2 x3 y3;
+ Option.iter (stroke' ctx) stroke;
+ Option.iter (fill' ctx) fill;
+ close_path ctx
+
+ let rec split_at n lst =
+ match (n, lst) with
+ | 0, _ -> ([], lst)
+ | _, [] -> ([], [])
+ | n, x :: xs ->
+ let taken, rest = split_at (n - 1) xs in
+ (x :: taken, rest)
+
+ let rec partition n ?(step = 0) lst =
+ match lst with
+ | [] -> []
+ | lst ->
+ let taken, _ = split_at n lst in
+ if List.length taken = n then taken :: partition n ~step (List.tl lst)
+ else []
+
+ let draw_polygon ctx { vertices; stroke; fill } =
+ let points = partition 2 ~step:1 (vertices @ [ List.hd vertices ]) in
+ begin_path ctx;
+ List.iter
+ (fun pair ->
+ let { x = x1; y = y1 }, { x = x2; y = y2 } =
+ (List.nth pair 0, List.nth pair 1)
+ in
+ ctx##moveTo x1 y1;
+ ctx##lineTo x2 y2)
+ points;
+ Option.iter (stroke' ctx) stroke;
+ Option.iter (fill' ctx) fill;
+ close_path ctx
+
+ let render_axes () =
+ let tmap f (a, b) = (f a, f b) in
+ let render_axes' { context = ctx; size; _ } =
+ let fsize = tmap float_of_int size in
+ let hw, hh = tmap (fun n -> n /. 2.) fsize in
+ let w, h = fsize in
+ ctx##beginPath;
+ ctx##moveTo 0. hh;
+ ctx##lineTo w hh;
+ ctx##moveTo hw 0.;
+ ctx##lineTo hw h;
+ ctx##closePath
+ in
+ match !context with Some ctx -> render_axes' ctx | None -> fail ()
+
+ let show shapes =
+ let rec render' context = function
+ | Circle circle' -> draw_circle context circle'
+ | Ellipse ellipse' -> draw_ellipse context ellipse'
+ | Line line' -> draw_line context line'
+ | Polygon polygon' -> draw_polygon context polygon'
+ | Complex complex' -> List.iter (render' context) complex'
+ in
+ match !context with
+ | Some ctx -> List.iter (render' ctx.context) shapes
+ | None -> fail ()
+end
+
+module Backend = Modules.Make (C)
diff --git a/lib/canvas.mli b/lib/canvas.mli
new file mode 100644
index 0000000..a402467
--- /dev/null
+++ b/lib/canvas.mli
@@ -0,0 +1,11 @@
+module Html = Js_of_ocaml.Dom_html
+module Dom = Js_of_ocaml.Dom
+module Js = Js_of_ocaml.Js
+
+val str : string -> Js.js_string Js.t
+val bl : bool -> bool Js.t
+val doc : Html.document Js_of_ocaml.Js.t
+val _window : Html.window Js_of_ocaml.Js.t
+
+module C : Modules.Impl
+module Backend : Modules.Backend
diff --git a/lib/context.ml b/lib/context.ml
deleted file mode 100644
index 33ede8f..0000000
--- a/lib/context.ml
+++ /dev/null
@@ -1,72 +0,0 @@
-(* Global rendering context singleton definition and instantiation *)
-type context = {
- ctx : Cairo.context;
- surface : Cairo.Surface.t;
- size : int * int;
- axes : bool;
-}
-
-(* Renders context to PNG *)
-let write ctx filename =
- Cairo.PNG.write ctx.surface filename;
- Cairo.Surface.finish ctx.surface
-
-let context = ref None
-
-exception Context of string
-
-(* Not working, could use help fixing *)
-let () =
- Printexc.register_printer (fun e ->
- match e with Context err -> Some ("Context: " ^ err) | _ -> None)
-
-let fail () = raise (Context "not initialized")
-let resolution () = match !context with Some ctx -> ctx.size | None -> fail ()
-let tmap3 f (a, b, c) = (f a, f b, f c)
-let tmap4 f (a, b, c, d) = (f a, f b, f c, f d)
-let ( >> ) f g x = g (f x)
-let scale_channel n = n /. 255.
-let scale_color_channel = float_of_int >> scale_channel
-
-let set_color color =
- match !context with
- | Some ctx ->
- let r, g, b = tmap3 scale_color_channel color in
- Cairo.set_source_rgb ctx.ctx r g b
- | None -> fail ()
-
-(* sets background color *)
-let background color =
- match !context with
- | Some { ctx; _ } ->
- let r, g, b, alpha = tmap4 scale_color_channel color in
- Cairo.set_source_rgb ctx r g b;
- Cairo.paint ctx ~alpha;
- Cairo.fill ctx
- | None -> fail ()
-
-(** Sets the width of lines for both stroke of shapes and line primitives.
- Can be any positive integer, with larger numbers producing thicker lines.
- default is 2 *)
-let set_line_width line_width =
- match !context with
- | Some ctx -> Cairo.set_line_width ctx.ctx (float_of_int line_width)
- | None -> fail ()
-
-let save () =
- match !context with Some ctx -> Cairo.save ctx.ctx | None -> fail ()
-
-let restore () =
- match !context with Some ctx -> Cairo.restore ctx.ctx | None -> fail ()
-
-let init_context background_color line_width (w, h) axes =
- (* Fail if context has already been instantiated *)
- if Option.is_some !context then
- raise (Context "Cannot initialize context twice");
-
- let surface = Cairo.Image.create Cairo.Image.ARGB32 ~w ~h in
- let ctx = Cairo.create surface in
- Cairo.set_line_width ctx line_width;
- Cairo.translate ctx (w / 2 |> float_of_int) (h / 2 |> float_of_int);
- context := Some { ctx; surface; size = (w, h); axes };
- background background_color
diff --git a/lib/context.mli b/lib/context.mli
deleted file mode 100644
index d9f36a9..0000000
--- a/lib/context.mli
+++ /dev/null
@@ -1,20 +0,0 @@
-type context = {
- ctx : Cairo.context;
- surface : Cairo.Surface.t;
- size : int * int;
- axes : bool;
-}
-
-val context : context option ref
-val fail : unit -> unit
-
-exception Context of string
-
-val init_context : int * int * int * int -> float -> int * int -> bool -> unit
-val resolution : unit -> int * int
-val set_color : int * int * int -> unit
-val background : int * int * int * int -> unit
-val set_line_width : int -> unit
-val write : context -> string -> unit
-val save : unit -> unit
-val restore : unit -> unit
diff --git a/lib/dune b/lib/dune
index 5feb774..5cf1b27 100644
--- a/lib/dune
+++ b/lib/dune
@@ -1,5 +1,7 @@
(library
(name joy)
(public_name joy)
- (libraries cairo2)
+ (libraries cairo2 js_of_ocaml)
+ (preprocess
+ (pps js_of_ocaml-ppx))
(wrapped false))
diff --git a/lib/joy.ml b/lib/joy.ml
index b6eb36a..fdd5a8b 100644
--- a/lib/joy.ml
+++ b/lib/joy.ml
@@ -1,51 +1,2 @@
-let context = Context.context
-
-type 'a point = 'a Shape.point
-type shape = Shape.shape
-type shapes = Shape.shapes
-type transformation = Transform.transformation
-
-type color = Color.color
-(** Three-tuple representing a 24-bit RGB color *)
-
-let black = Color.black
-let white = Color.white
-let red = Color.red
-let green = Color.green
-let blue = Color.blue
-let yellow = Color.yellow
-let transparent = Color.transparent
-let opaque = Color.opaque
-let point = Shape.point
-let circle = Shape.circle
-let rectangle = Shape.rectangle
-let polygon = Shape.polygon
-let ellipse = Shape.ellipse
-let line = Shape.line
-let complex = Shape.complex
-let with_stroke = Shape.with_stroke
-let with_fill = Shape.with_fill
-let rotate = Transform.rotate
-let scale = Transform.scale
-let translate = Transform.translate
-let compose = Transform.compose
-let repeat = Transform.repeat
-let map_fill = Transform.map_fill
-let map_stroke = Transform.map_stroke
-let set_line_width = Context.set_line_width
-
-let init ?(background = Color.white) ?(line_width = 2) ?(size = (500, 500))
- ?(axes = false) () =
- Context.init_context (Color.opaque background)
- (float_of_int line_width)
- size axes
-
-let write ?(filename = "joy.png") () =
- match !Context.context with
- | Some ctx ->
- if ctx.axes then Render.render_axes ();
- Context.write ctx filename
- | None -> Context.fail ()
-
-let render shape = Render.render shape
-let show shapes = Render.show shapes
+module Svg = Svg.Backend
+module Canvas = Canvas.Backend
diff --git a/lib/joy.mli b/lib/joy.mli
index bfd4982..fdd5a8b 100644
--- a/lib/joy.mli
+++ b/lib/joy.mli
@@ -1,44 +1,2 @@
-type 'a point = 'a Shape.point
-type shape = Shape.shape
-type shapes = Shape.shapes
-type transformation = Transform.transformation
-type color = Color.color
-
-val point : int -> int -> float point
-val circle : ?c:float point -> int -> shape
-val rectangle : ?c:float point -> int -> int -> shape
-val ellipse : ?c:float point -> int -> int -> shape
-val line : ?a:float point -> float point -> shape
-val polygon : float point list -> shape
-val complex : shapes -> shape
-val with_stroke : color -> shape -> shape
-val with_fill : color -> shape -> shape
-val rotate : int -> transformation
-val translate : int -> int -> transformation
-val scale : float -> transformation
-val compose : transformation -> transformation -> transformation
-val repeat : int -> transformation -> transformation
-val map_stroke : (color -> color) -> shape -> shape
-val map_fill : (color -> color) -> shape -> shape
-val context : Context.context option ref
-val set_line_width : int -> unit
-val black : color
-val white : color
-val red : color
-val green : color
-val blue : color
-val yellow : color
-val transparent : int * int * int * int
-val opaque : color -> int * int * int * int
-
-val init :
- ?background:color ->
- ?line_width:int ->
- ?size:int * int ->
- ?axes:bool ->
- unit ->
- unit
-
-val render : shape -> unit
-val show : shapes -> unit
-val write : ?filename:string -> unit -> unit
+module Svg = Svg.Backend
+module Canvas = Canvas.Backend
diff --git a/lib/modules.ml b/lib/modules.ml
new file mode 100644
index 0000000..442f22c
--- /dev/null
+++ b/lib/modules.ml
@@ -0,0 +1,101 @@
+module type Impl = sig
+ type context
+
+ val context : context option ref
+
+ exception Context of string
+
+ val fail : unit -> unit
+ val init_context : int * int * int * int -> int -> int * int -> bool -> unit
+
+ (* Getters *)
+ val axes : context -> bool
+ val resolution : unit -> int * int
+
+ (* Setters *)
+ val set_line_width : int -> unit
+ val render_axes : unit -> unit
+ val save : unit -> unit
+ val restore : unit -> unit
+ val write : context -> string -> unit
+ val show : Shape.shapes -> unit
+end
+
+module type Backend = sig
+ type context
+
+ val context : context option ref
+
+ type 'a point = 'a Shape.point
+ type shape = Shape.shape
+ type shapes = Shape.shapes
+ type transformation = Transform.transformation
+ type color = Color.color
+
+ val point : int -> int -> float Shape.point
+ val circle : ?c:float Shape.point -> int -> Shape.shape
+ val rectangle : ?c:float Shape.point -> int -> int -> Shape.shape
+ val polygon : float Shape.point list -> Shape.shape
+ val ellipse : ?c:float Shape.point -> int -> int -> Shape.shape
+ val line : ?a:float Shape.point -> float Shape.point -> Shape.shape
+ val complex : Shape.shape list -> Shape.shape
+ val rotate : int -> Transform.transformation
+ val scale : float -> Transform.transformation
+ val translate : int -> int -> Transform.transformation
+ val with_stroke : color -> shape -> shape
+ val with_fill : color -> shape -> shape
+ val map_stroke : (color -> color) -> shape -> shape
+ val map_fill : (color -> color) -> shape -> shape
+ val white : color
+ val black : color
+ val red : color
+ val blue : color
+ val green : color
+ val yellow : color
+
+ val compose :
+ Transform.transformation ->
+ Transform.transformation ->
+ Transform.transformation
+
+ val repeat : int -> Transform.transformation -> Transform.transformation
+ val set_line_width : int -> unit
+ val resolution : unit -> int * int
+
+ val init :
+ ?background:int * int * int * int ->
+ ?line_width:int ->
+ ?size:int * int ->
+ ?axes:bool ->
+ unit ->
+ unit
+
+ val write : ?filename:string -> unit -> unit
+ val show : Shape.shapes -> unit
+end
+
+module Make (B : Impl) : Backend with type context = B.context = struct
+ type context = B.context
+
+ let context = B.context
+
+ include Shape
+ include Transform
+ include Color
+
+ let set_line_width = B.set_line_width
+ let resolution = B.resolution
+
+ let init ?(background = (255, 255, 255, 255)) ?(line_width = 2)
+ ?(size = (800, 800)) ?(axes = false) () =
+ B.init_context background line_width size axes
+
+ let write ?(filename = "joy.png") () =
+ match !B.context with
+ | Some ctx ->
+ if B.axes ctx then B.render_axes ();
+ B.write ctx filename
+ | None -> B.fail ()
+
+ let show shapes = B.show shapes
+end
diff --git a/lib/modules.mli b/lib/modules.mli
new file mode 100644
index 0000000..a060285
--- /dev/null
+++ b/lib/modules.mli
@@ -0,0 +1,73 @@
+module type Impl = sig
+ type context
+
+ val context : context option ref
+
+ exception Context of string
+
+ val fail : unit -> unit
+ val init_context : int * int * int * int -> int -> int * int -> bool -> unit
+ val axes : context -> bool
+ val resolution : unit -> int * int
+ val set_line_width : int -> unit
+ val render_axes : unit -> unit
+ val save : unit -> unit
+ val restore : unit -> unit
+ val write : context -> string -> unit
+ val show : Shape.shapes -> unit
+end
+
+module type Backend = sig
+ type context
+
+ val context : context option ref
+
+ type 'a point = 'a Shape.point
+ type shape = Shape.shape
+ type shapes = Shape.shapes
+ type transformation = Transform.transformation
+ type color = Color.color
+
+ val point : int -> int -> float Shape.point
+ val circle : ?c:float Shape.point -> int -> Shape.shape
+ val rectangle : ?c:float Shape.point -> int -> int -> Shape.shape
+ val polygon : float Shape.point list -> Shape.shape
+ val ellipse : ?c:float Shape.point -> int -> int -> Shape.shape
+ val line : ?a:float Shape.point -> float Shape.point -> Shape.shape
+ val complex : Shape.shape list -> Shape.shape
+ val rotate : int -> Transform.transformation
+ val scale : float -> Transform.transformation
+ val translate : int -> int -> Transform.transformation
+ val with_stroke : color -> shape -> shape
+ val with_fill : color -> shape -> shape
+ val map_stroke : (color -> color) -> shape -> shape
+ val map_fill : (color -> color) -> shape -> shape
+ val white : color
+ val black : color
+ val red : color
+ val blue : color
+ val green : color
+ val yellow : color
+
+ val compose :
+ Transform.transformation ->
+ Transform.transformation ->
+ Transform.transformation
+
+ val repeat : int -> Transform.transformation -> Transform.transformation
+ val set_line_width : int -> unit
+ val resolution : unit -> int * int
+
+ val init :
+ ?background:int * int * int * int ->
+ ?line_width:int ->
+ ?size:int * int ->
+ ?axes:bool ->
+ unit ->
+ unit
+
+ val write : ?filename:string -> unit -> unit
+ val show : Shape.shapes -> unit
+end
+
+module Make : functor (B : Impl) -> Backend with type context = B.context
diff --git a/lib/render.ml b/lib/render.ml
deleted file mode 100644
index eca4a77..0000000
--- a/lib/render.ml
+++ /dev/null
@@ -1,126 +0,0 @@
-open Shape
-open Context
-
-let tmap f (x, y) = (f x, f y)
-
-let draw_circle ctx ({ c; radius; stroke; fill } : circle) =
- let stroke_circle stroke =
- set_color stroke;
- Cairo.stroke_preserve ctx.ctx
- in
- let fill_circle fill =
- set_color fill;
- Cairo.fill_preserve ctx.ctx
- in
- Cairo.arc ctx.ctx c.x c.y ~r:radius ~a1:0. ~a2:(Float.pi *. 2.);
- Option.iter stroke_circle stroke;
- Option.iter fill_circle fill;
- Cairo.Path.clear ctx.ctx
-
-let create_control_points ({x; y}, rx, ry) =
- let half_height = ry /. 2. in
- let width_two_thirds = rx *. (2. /. 3.) *. 2. in
- ( { x; y = y -. half_height },
- ( x +. width_two_thirds,
- y -. half_height,
- x +. width_two_thirds,
- y +. half_height,
- x,
- y +. half_height ),
- ( x -. width_two_thirds,
- y +. half_height,
- x -. width_two_thirds,
- y -. half_height,
- x,
- y -. half_height ) )
-
-let draw_ellipse ctx { c; rx; ry; stroke; fill } =
- let stroke_ellipse stroke =
- set_color stroke;
- Cairo.stroke_preserve ctx.ctx
- in
- let fill_ellipse fill =
- set_color fill;
- Cairo.fill_preserve ctx.ctx
- in
- let start, curve_one, curve_two = create_control_points (c, rx, ry) in
- Cairo.move_to ctx.ctx start.x start.y;
- let x1, y1, x2, y2, x3, y3 = curve_one in
- Cairo.curve_to ctx.ctx x1 y1 x2 y2 x3 y3;
- let x1, y1, x2, y2, x3, y3 = curve_two in
- Cairo.curve_to ctx.ctx x1 y1 x2 y2 x3 y3;
- Option.iter stroke_ellipse stroke;
- Option.iter fill_ellipse fill;
- Cairo.Path.clear ctx.ctx
-
-let draw_line ctx { a; b; stroke } =
- set_color stroke;
- let { x; y } = a in
- Cairo.move_to ctx.ctx x y;
- let { x; y } = b in
- Cairo.line_to ctx.ctx x y;
- Cairo.stroke ctx.ctx
-
-let rec take n lst =
- match (n, lst) with
- | 0, _ -> ([], lst)
- | _, [] -> ([], [])
- | n, x :: xs ->
- let taken, rest = take (n - 1) xs in
- (x :: taken, rest)
-
-let rec partition n ?(step = 0) lst =
- match lst with
- | [] -> []
- | _ ->
- let taken, _ = take n lst in
- if List.length taken = n then
- taken
- ::
- partition n ~step (List.tl lst)
- else []
-
-let draw_polygon ctx { vertices = points; stroke; fill } =
- let stroke_rect stroke =
- set_color stroke;
- Cairo.stroke_preserve ctx.ctx
- in
- let fill_rect fill =
- set_color fill;
- Cairo.fill_preserve ctx.ctx
- in
- let points = partition 2 ~step:1 (points @ [ List.hd points ]) in
- List.iter
- (fun pair ->
- let { x = x1; y = y1 }, { x = x2; y = y2 } =
- (List.nth pair 0, List.nth pair 1)
- in
- Cairo.move_to ctx.ctx x1 y1;
- Cairo.line_to ctx.ctx x2 y2)
- points;
- Option.iter stroke_rect stroke;
- Option.iter fill_rect fill;
- Cairo.Path.clear ctx.ctx
-
-let rec render_shape ctx = function
- | Circle circle -> draw_circle ctx circle
- | Ellipse ellipse -> draw_ellipse ctx ellipse
- | Line line -> draw_line ctx line
- | Polygon polygon -> draw_polygon ctx polygon
- | Complex complex -> List.iter (render_shape ctx) complex
-
-(* Validates context before rendering *)
-let render shape =
- match !context with Some ctx -> render_shape ctx shape | None -> fail ()
-
-let show shapes =
- match !context with
- | Some ctx -> List.iter (render_shape ctx) shapes
- | None -> fail ()
-
-let render_axes () =
- let x, y = resolution () |> tmap float_of_int in
- let half_x, half_y = (x /. 2., y /. 2.) in
- let x_axis = line ~a:{ x = 0.; y = -.half_y } { x = 0.; y = half_y } in
- let y_axis = line ~a:{ x = -.half_x; y = 0. } { x = half_x; y = 0. } in
- show [ x_axis; y_axis ]
diff --git a/lib/render.mli b/lib/render.mli
deleted file mode 100644
index c4c7e03..0000000
--- a/lib/render.mli
+++ /dev/null
@@ -1,8 +0,0 @@
-val draw_circle : Context.context -> Shape.circle -> unit
-val draw_ellipse : Context.context -> Shape.ellipse -> unit
-val draw_line : Context.context -> Shape.line -> unit
-val draw_polygon : Context.context -> Shape.polygon -> unit
-val render_axes : unit -> unit
-val render_shape : Context.context -> Shape.shape -> unit
-val render : Shape.shape -> unit
-val show : Shape.shape list -> unit
diff --git a/lib/svg.ml b/lib/svg.ml
new file mode 100644
index 0000000..42a2590
--- /dev/null
+++ b/lib/svg.ml
@@ -0,0 +1,195 @@
+module S : Modules.Impl = struct
+ (* Global rendering context singleton definition and instantiation *)
+ type context = {
+ ctx : Cairo.context;
+ surface : Cairo.Surface.t;
+ size : int * int;
+ axes : bool;
+ }
+
+ (* Renders context to PNG *)
+ let write ctx filename =
+ Cairo.PNG.write ctx.surface filename;
+ Cairo.Surface.finish ctx.surface
+
+ let context = ref None
+
+ exception Context of string
+
+ (* Not working, could use help fixing *)
+ let () =
+ Printexc.register_printer (fun e ->
+ match e with Context err -> Some ("Context: " ^ err) | _ -> None)
+
+ let fail () = raise (Context "not initialized")
+ let axes ctx = ctx.axes
+
+ let resolution () =
+ match !context with Some ctx -> ctx.size | None -> fail ()
+
+ let tmap3 f (a, b, c) = (f a, f b, f c)
+ let tmap4 f (a, b, c, d) = (f a, f b, f c, f d)
+ let ( >> ) f g x = g (f x)
+ let scale_color_channel x = x /. 256.
+
+ let set_color color =
+ match !context with
+ | Some ctx ->
+ let r, g, b = tmap3 (float_of_int >> scale_color_channel) color in
+ Cairo.set_source_rgb ctx.ctx r g b
+ | None -> fail ()
+
+ (* sets background color *)
+ let background { ctx; _ } color =
+ let r, g, b, alpha = tmap4 (float_of_int >> scale_color_channel) color in
+ Cairo.set_source_rgb ctx r g b;
+ Cairo.paint ctx ~alpha;
+ Cairo.fill ctx
+
+ (** Sets the width of lines for both stroke of shapes and line primitives.
+ Can be any positive integer, with larger numbers producing thicker lines.
+ default is 2 *)
+ let set_line_width line_width =
+ match !context with
+ | Some ctx -> Cairo.set_line_width ctx.ctx (float_of_int line_width)
+ | None -> fail ()
+
+ let save () =
+ match !context with Some ctx -> Cairo.save ctx.ctx | None -> fail ()
+
+ let restore () =
+ match !context with Some ctx -> Cairo.restore ctx.ctx | None -> fail ()
+
+ let init_context background_color line_width (w, h) axes =
+ (* Fail if context has already been instantiated *)
+ if Option.is_some !context then
+ raise (Context "Cannot initialize context twice");
+
+ let surface = Cairo.Image.create Cairo.Image.ARGB32 ~w ~h in
+ let ctx = Cairo.create surface in
+ Cairo.set_line_width ctx (float_of_int line_width);
+ Cairo.translate ctx (w / 2 |> float_of_int) (h / 2 |> float_of_int);
+ let temp = { ctx; surface; size = (w, h); axes } in
+ context := Some temp;
+ background temp background_color
+
+ open Shape
+
+ let tmap f (x, y) = (f x, f y)
+
+ let draw_circle ctx ({ c; radius; stroke; fill } : circle) =
+ let stroke_circle stroke =
+ set_color stroke;
+ Cairo.stroke_preserve ctx.ctx
+ in
+ let fill_circle fill =
+ set_color fill;
+ Cairo.fill_preserve ctx.ctx
+ in
+ Cairo.arc ctx.ctx c.x c.y ~r:radius ~a1:0. ~a2:(Float.pi *. 2.);
+ Option.iter stroke_circle stroke;
+ Option.iter fill_circle fill;
+ Cairo.Path.clear ctx.ctx
+
+ let create_control_points ({ x; y }, rx, ry) =
+ let half_height = ry /. 2. in
+ let width_two_thirds = rx *. (2. /. 3.) *. 2. in
+ ( { x; y = y -. half_height },
+ ( x +. width_two_thirds,
+ y -. half_height,
+ x +. width_two_thirds,
+ y +. half_height,
+ x,
+ y +. half_height ),
+ ( x -. width_two_thirds,
+ y +. half_height,
+ x -. width_two_thirds,
+ y -. half_height,
+ x,
+ y -. half_height ) )
+
+ let draw_ellipse ctx { c; rx; ry; stroke; fill } =
+ let stroke_ellipse stroke =
+ set_color stroke;
+ Cairo.stroke_preserve ctx.ctx
+ in
+ let fill_ellipse fill =
+ set_color fill;
+ Cairo.fill_preserve ctx.ctx
+ in
+ let start, curve_one, curve_two = create_control_points (c, rx, ry) in
+ Cairo.move_to ctx.ctx start.x start.y;
+ let x1, y1, x2, y2, x3, y3 = curve_one in
+ Cairo.curve_to ctx.ctx x1 y1 x2 y2 x3 y3;
+ let x1, y1, x2, y2, x3, y3 = curve_two in
+ Cairo.curve_to ctx.ctx x1 y1 x2 y2 x3 y3;
+ Option.iter stroke_ellipse stroke;
+ Option.iter fill_ellipse fill;
+ Cairo.Path.clear ctx.ctx
+
+ let draw_line ctx { a; b; stroke } =
+ set_color stroke;
+ let { x; y } = a in
+ Cairo.move_to ctx.ctx x y;
+ let { x; y } = b in
+ Cairo.line_to ctx.ctx x y;
+ Cairo.stroke ctx.ctx
+
+ let rec take n lst =
+ match (n, lst) with
+ | 0, _ -> ([], lst)
+ | _, [] -> ([], [])
+ | n, x :: xs ->
+ let taken, rest = take (n - 1) xs in
+ (x :: taken, rest)
+
+ let rec partition n ?(step = 0) = function
+ | [] -> []
+ | lst ->
+ let taken, _ = take n lst in
+ if List.length taken = n then taken :: partition n ~step (List.tl lst)
+ else []
+
+ let draw_polygon ctx { vertices = points; stroke; fill } =
+ let stroke_rect stroke =
+ set_color stroke;
+ Cairo.stroke_preserve ctx.ctx
+ in
+ let fill_rect fill =
+ set_color fill;
+ Cairo.fill_preserve ctx.ctx
+ in
+ let points = partition 2 ~step:1 (points @ [ List.hd points ]) in
+ List.iter
+ (fun pair ->
+ let { x = x1; y = y1 }, { x = x2; y = y2 } =
+ (List.nth pair 0, List.nth pair 1)
+ in
+ Cairo.move_to ctx.ctx x1 y1;
+ Cairo.line_to ctx.ctx x2 y2)
+ points;
+ Option.iter stroke_rect stroke;
+ Option.iter fill_rect fill;
+ Cairo.Path.clear ctx.ctx
+
+ let show shapes =
+ let rec render ctx = function
+ | Circle circle -> draw_circle ctx circle
+ | Ellipse ellipse -> draw_ellipse ctx ellipse
+ | Line line -> draw_line ctx line
+ | Polygon polygon -> draw_polygon ctx polygon
+ | Complex complex -> List.iter (render ctx) complex
+ in
+ match !context with
+ | Some ctx -> List.iter (render ctx) shapes
+ | None -> fail ()
+
+ let render_axes () =
+ let x, y = resolution () |> tmap float_of_int in
+ let half_x, half_y = (x /. 2., y /. 2.) in
+ let x_axis = line ~a:{ x = 0.; y = -.half_y } { x = 0.; y = half_y } in
+ let y_axis = line ~a:{ x = -.half_x; y = 0. } { x = half_x; y = 0. } in
+ show [ x_axis; y_axis ]
+end
+
+module Backend = Modules.Make (S)
diff --git a/lib/svg.mli b/lib/svg.mli
new file mode 100644
index 0000000..b13a2fd
--- /dev/null
+++ b/lib/svg.mli
@@ -0,0 +1,2 @@
+module S : Modules.Impl
+module Backend : Modules.Backend
diff --git a/test/test_circle.ml b/test/test_circle.ml
index 6153bfd..a699cd0 100644
--- a/test/test_circle.ml
+++ b/test/test_circle.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let run () =
init ();
diff --git a/test/test_ellipse.ml b/test/test_ellipse.ml
index df8600c..78d626e 100644
--- a/test/test_ellipse.ml
+++ b/test/test_ellipse.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let run () =
init ();
diff --git a/test/test_rectangle.ml b/test/test_rectangle.ml
index 73734e5..7e8d12d 100644
--- a/test/test_rectangle.ml
+++ b/test/test_rectangle.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let run () =
init ();
diff --git a/test/test_scale_shape.ml b/test/test_scale_shape.ml
index 3b3f0df..a8b4451 100644
--- a/test/test_scale_shape.ml
+++ b/test/test_scale_shape.ml
@@ -1,4 +1,4 @@
-open Joy
+open Joy.Svg
let run () =
init ();