JsOfOCairo is an OCaml (4.02.3+) library to reuse Cairo-based drawing code in web browsers. It's an adapter, implementing (a reasonable subset of) the interface of Cairo OCaml targeting HTML5 canvas elements as exposed to OCaml by js_of_ocaml (3.0.0+).
Here is DrawGrammar, a real-life aplication of JsOfOCairo.
There is no real documentation besides this README.rst file. See below what is implemented, what behaves differently from Cairo, and what is not implemented.
Questions? Remarks? Bugs? Want to contribute? Open an issue!
Install from OPAM:
$ opam install JsOfOCairo
Create a functor implementing your drawing code against the
module Make(C: JsOfOCairo.S) = struct let draw ctx = C.save ctx; C.arc ctx ~x:50. ~y:50. ~r:40. ~a1:0. ~a2:5.; C.stroke ctx; C.restore ctx end
Instantiate this functor with
Cairo to create a command-line program.
module Drawings = Drawings.Make(Cairo) let () = begin let image = Cairo.Image.create Cairo.Image.ARGB32 ~width:100 ~height:100 in Drawings.draw (Cairo.create image); Cairo.PNG.write image "draw_on_command_line.png"; end
Instantiate the same functor with
module Drawings = Drawings.Make(JsOfOCairo) let () = Js.export "draw" (fun canvas -> Drawings.draw (JsOfOCairo.create canvas) )
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>JsOfOCairo demo</title> </head> <body> <h1>PNG image from command-line</h1> <img src="draw_on_command_line.png" /> <h1>HTML5 canvas</h1> <canvas id="drawings" width="100" height="100"></canvas> <script src="_build/default/draw_in_browser.bc.js"></script> <script> draw(document.getElementById("drawings")); </script> </body> </html>
As a bonus, JsOfOCairo comes with
CairoMock, which implements the
JsOfOCairo.S signature and simply records the
calls made on the context object. You can use it to automate some tests on your drawing code:
module Drawings = Drawings.Make(CairoMock) let () = begin let ctx = CairoMock.create () in Drawings.draw ctx; assert (CairoMock.calls ctx = ["save"; "arc ~x:50.00 ~y:50.00 ~r:40.00 ~a1:0.00 ~a2:5.00"; "stroke"; "restore"]) end
CairoMock itself is split into CairoMock.Mock, an actual mock implementation of
JsOfOCairo.S that does nothing, and CairoMock.Decorate, that can be used to record calls made on any implementation of
JsOfOCairo.S. So, you can draw and record calls at the same time.
What is implemented
How to avoid pitfalls
There are limitations however: text-related functions, arcs, re-use of the same canvas... Details of the limitations identified so far are available with the tests. We believe they are small enough for the library to be useful anyway.
Here is a set of rules to follow to stay on the safe side of using JsOfOCairo:
- Always call
savejust after creating a context, and
restorejust before stopping using it.
- Never create two contexts from the same canvas at the same time: wait until you have
restore-d a context before creating another.
- Never draw arcs of more than one full turn.
- Use only the
width``returned by ``text_extents.
- Use only the
What is not implemented
Contributions in this area are welcome. Please start a discussion before doing anything to avoid wasting time.
Everything involving a
Surface.t has been dismissed.
This doesn't make much sense in an HTML5 context.
An attempt has been made to implement
set_source_for_image using a hidden canvas but it's been unsuccessful.
A few other functions commented out at the beginning of S.incl.ml have been dismissed as well.
There are three sets of tests:
- universal tests
- They are run on
Cairoto check their validity, and then on
CairoMockto actually test the library. They verify that getters return the value that was last set, that the current point is updated, and that all this is saved and restored consistently.
- drawing tests
- They are run on
Cairoto generate reference bitmaps, and then on
JsOfOCairoto verify that both libraries produce very similar drawings.
- decoration tests
- They verify the strings generated by
All these tests are run automatically as OCaml bytecode and in Node.js (through js_of_ocaml) and are available in web browsers.