diff --git a/.merlin b/.merlin index 89ea845..ebd3dcd 100644 --- a/.merlin +++ b/.merlin @@ -1,3 +1,3 @@ EXT js -PKG gg js_of_ocaml +PKG gg js_of_ocaml cairo2 B _build/** diff --git a/README.md b/README.md index 0173fda..1034bc7 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,20 @@ images are values that denote functions mapping points of the cartesian plane to colors. The module provides combinators to define and compose these values. -Renderers for PDF, SVG and the HTML canvas are distributed with the +Renderers for PDF, SVG, Cairo and the HTML canvas are distributed with the module. An API allows to implement new renderers. Vg depends only on [Gg][1]. The SVG renderer has no dependency, the PDF renderer depends on [Uutf][2] and [Otfm][3], the HTML canvas -renderer depends on [js_of_ocaml][4]. Vg and its renderers are -distributed under the BSD3 license. +renderer depends on [js_of_ocaml][4], the Cairo renderer depends on +[cairo2][5]. Vg and its renderers are distributed under the BSD3 +license. [1]: http://erratique.ch/software/gg [2]: http://erratique.ch/software/uutf [3]: http://erratique.ch/software/otfm [4]: http://ocsigen.org/js_of_ocaml/ +[5]: https://forge.ocamlcore.org/projects/cairo/ Home page: http://erratique.ch/software/vg Contact: Daniel Bünzli `` @@ -28,8 +30,8 @@ Contact: Daniel Bünzli `` Vg can be installed with `opam`: - opam install vg # SVG renderer only - opam install uutf otfm js_of_ocaml vg # all renderers + opam install vg # SVG renderer only + opam install uutf otfm js_of_ocaml cairo2 vg # all renderers If you don't use `opam` consult the [`opam`](opam) file for build instructions and a complete specification of the dependencies. @@ -64,6 +66,8 @@ The resulting binaries are in `_build/test` : - `min_htmlc.byte`, minimal example to render with the HTML canvas. - `rsvg.native`, renders images of the Vg image database to SVG files. - `rpdf.native`, renders images of the Vg image database to PDF files. +- `rcairo.native`, renders images of the Vg image database with Cairo + to PDF, PNG, PS or SVG files. - `rhtmlc.html` and `rhtmlc.byte` can be processed with `js_of_ocaml`, the resulting webapp renders images of the Vg image database with the HTML canvas, PDF and SVG renderers. diff --git a/_tags b/_tags index fe7919b..2501d1b 100644 --- a/_tags +++ b/_tags @@ -12,6 +12,8 @@ : package(gg), package(uutf), package(otfm) +: package(gg), package(cairo2) + : package(gg) : package(uutf) @@ -25,6 +27,9 @@ : package(gg), package(uutf), package(unix) + : package(gg), package(uutf), \ + package(unix), package(cairo2) + : package(gg), package(otfm), package(uutf), \ package(js_of_ocaml), \ package(js_of_ocaml.syntax), syntax(camlp4o) @@ -33,6 +38,8 @@ : package(gg) + : package(gg), package(cairo2) + : package(gg), package(js_of_ocaml), \ package(js_of_ocaml.syntax), syntax(camlp4o) @@ -42,4 +49,4 @@ : package(gg) : package(gg), package(uutf), package(otfm) - : package(gg), package(uutf), package(otfm) \ No newline at end of file + : package(gg), package(uutf), package(otfm) diff --git a/build b/build index 0469bf6..96da7cc 100755 --- a/build +++ b/build @@ -11,7 +11,7 @@ OCAMLBUILD=${OCAMLBUILD:="ocamlbuild -tag debug -classic-display \ action () { case $1 in - default) $OCAMLBUILD vg.cmx vgr_pdf.cmx vgr_svg.cmx vgr_htmlc.cmx ;; + default) $OCAMLBUILD vg.cmx vgr_pdf.cmx vgr_svg.cmx vgr_htmlc.cmx vgr_cairo.cmx ;; tests) $OCAMLBUILD rpdf.native rsvg.native; action rhtmlc ;; rhtmlc) shift; pkg/db-locs diff --git a/doc/api.odocl b/doc/api.odocl index 2e908f5..47c48f0 100644 --- a/doc/api.odocl +++ b/doc/api.odocl @@ -2,3 +2,4 @@ Vg Vgr_htmlc Vgr_pdf Vgr_svg +Vgr_cairo diff --git a/doc/dev-api.odocl b/doc/dev-api.odocl index eb3c4aa..2389c59 100644 --- a/doc/dev-api.odocl +++ b/doc/dev-api.odocl @@ -2,5 +2,6 @@ Vg Vgr_htmlc Vgr_pdf Vgr_svg +Vgr_cairo Mui -Db \ No newline at end of file +Db diff --git a/opam b/opam index 681c3a3..baecc68 100644 --- a/opam +++ b/opam @@ -9,7 +9,7 @@ tags: [ "pdf" "svg" "html-canvas" "declarative" "graphics" "org:erratique" ] license: "BSD3" ocaml-version: [>= "4.01.0"] depends: [ "ocamlfind" "gg" {>= "0.9.0"} ] -depopts: [ "uutf" "otfm" "js_of_ocaml" ] +depopts: [ "uutf" "otfm" "js_of_ocaml" "cairo2" ] build: [ [ "ocaml" "pkg/git.ml" ] @@ -17,5 +17,6 @@ build: "native-dynlink=%{ocaml-native-dynlink}%" "uutf=%{uutf:installed}%" "otfm=%{otfm:installed}%" + "cairo2=%{cairo2:installed}%" "jsoo=%{js_of_ocaml:installed}%" ] ] diff --git a/pkg/META b/pkg/META index 1f9d554..fff9212 100644 --- a/pkg/META +++ b/pkg/META @@ -39,3 +39,14 @@ package "htmlc" ( archive(native, plugin) = "vgr_htmlc.cmxs" exists_if = "vgr_htmlc.cma" ) + +package "cairo" ( + version = "%%VERSION%%" + description = "Vg's Cairo renderer" + requires = "vg cairo2" + archive(byte) = "vgr_cairo.cma" + archive(byte, plugin) = "vgr_cairo.cma" + archive(native) = "vgr_cairo.cmxa" + archive(native, plugin) = "vgr_cairo.cmxs" + exists_if = "vgr_cairo.cma" +) diff --git a/pkg/build.ml b/pkg/build.ml index 3fd87e8..d02cd13 100755 --- a/pkg/build.ml +++ b/pkg/build.ml @@ -6,6 +6,7 @@ let uutf = Env.bool "uutf" let otfm = Env.bool "otfm" let jsoo = Env.bool "jsoo" let vgr_pdf = uutf && otfm +let cairo2 = Env.bool "cairo2" let () = Pkg.describe "vg" ~builder:`OCamlbuild [ Pkg.lib "pkg/META"; @@ -14,11 +15,13 @@ let () = Pkg.describe "vg" ~builder:`OCamlbuild [ Pkg.lib ~cond:vgr_pdf ~exts:Exts.module_library "src/vgr_pdf"; Pkg.bin ~cond:vgr_pdf ~auto:true "test/vecho"; Pkg.lib ~cond:jsoo ~exts:Exts.module_library "src/vgr_htmlc"; + Pkg.lib ~cond:cairo2 ~exts:Exts.module_library "src/vgr_cairo"; Pkg.doc "README.md"; Pkg.doc "CHANGES.md"; Pkg.doc "test/min_htmlc.html"; Pkg.doc "test/min_htmlc.ml"; Pkg.doc "test/min_pdf.ml"; Pkg.doc "test/min_svg.ml"; + Pkg.doc "test/min_cairo.ml"; Pkg.doc "test/fglyphs.ml"; ] diff --git a/src/vgr_cairo.ml b/src/vgr_cairo.ml new file mode 100644 index 0000000..260660b --- /dev/null +++ b/src/vgr_cairo.ml @@ -0,0 +1,437 @@ +(*--------------------------------------------------------------------------- + Copyright 2014 Arthur Wendling, Daniel C. Bünzli. All rights reserved. + Distributed under the BSD3 license, see license at the end of the file. + %%NAME%% release %%VERSION%% + ---------------------------------------------------------------------------*) + +(* Based on the Vgr_htmlc implementation by Daniel C. Bünzli. *) + +open Gg +open Vg +open Vgr.Private.Data + +let err_zero_size () = invalid_arg "Cairo surface has a size of zero" + +let default_resolution = + let m = 72000. /. 25.4 in Size2.v m m + +type cairo_font = Font : 'a Cairo.Font_face.t -> cairo_font +type cairo_primitive = Pattern : 'a Cairo.Pattern.t -> cairo_primitive + +let dumb_prim = Pattern (Cairo.Pattern.create_rgba 0.0 0.0 0.0 0.0) + +type gstate = (* subset of the graphics state saved by a Cairo.save ctx *) + { mutable g_tr : M3.t; (* current transform without view_tr. *) + mutable g_outline : P.outline; (* current outline stroke. *) + mutable g_stroke : cairo_primitive; (* current stroke color. *) + mutable g_fill : cairo_primitive; } (* current fill color. *) + +let init_gstate = + { g_tr = M3.id; g_outline = P.o; g_stroke = dumb_prim; g_fill = dumb_prim } + +type cairo_backend = [ `Surface | `PDF | `PNG | `PS | `SVG ] + +type cmd = Set of gstate | Draw of Vgr.Private.Data.image +type state = + { r : Vgr.Private.renderer; (* corresponding renderer. *) + resolution : Gg.v2; (* resolution of the surface. *) + backend : cairo_backend; (* final format target. *) + mutable size : Size2.t; (* surface dimensions. *) + surface : Cairo.Surface.t; (* surface rendered to. *) + ctx : Cairo.context; (* context of [surface]. *) + mutable cost : int; (* cost counter for limit. *) + mutable view : Gg.box2; (* current renderable view rectangle. *) + mutable view_tr : M3.t; (* view to canvas transform. *) + mutable todo : cmd list; (* commands to perform. *) + fonts : (Vg.font, cairo_font) Hashtbl.t; (* cached fonts. *) + prims : (* cached primitives. *) + (Vgr.Private.Data.primitive, cairo_primitive) Hashtbl.t; + mutable gstate : gstate; } (* current graphic state. *) + +let save_gstate s = Set { s.gstate with g_tr = s.gstate.g_tr } +let set_gstate s g = s.gstate <- g + +let partial = Vgr.Private.partial +let limit s = Vgr.Private.limit s.r +let warn s w = Vgr.Private.warn s.r w +let image i = Vgr.Private.I.of_data i + +let view_rect s = (* image view rect in current coordinate system. *) + let tr = M3.inv s.gstate.g_tr in + Vgr.Private.Data.of_path (P.empty >> P.rect (Box2.tr tr s.view)) + +let cairo_matrix xx yx xy yy x0 y0 = + { Cairo.xx; yx; xy; yy; x0; y0 } + +let cairo_matrix_of_m3 m = + M3.(cairo_matrix (e00 m) (e10 m) (e01 m) (e11 m) (e02 m) (e12 m)) + +let cairo_cap = function + | `Butt -> Cairo.BUTT + | `Round -> Cairo.ROUND + | `Square -> Cairo.SQUARE + +let cairo_join = function + | `Bevel -> Cairo.JOIN_BEVEL + | `Round -> Cairo.JOIN_ROUND + | `Miter -> Cairo.JOIN_MITER + +let cairo_fill_rule = function + | `Anz -> Cairo.WINDING + | `Aeo -> Cairo.EVEN_ODD + | `O _ -> assert false + +let set_dashes s = function + | None -> Cairo.set_dash s.ctx [||] + | Some (offset, dashes) -> + let dashes = Array.of_list dashes in + Cairo.set_dash s.ctx ~ofs:offset dashes + +let init_ctx s = + let o = s.gstate.g_outline in + let m = s.view_tr in + Cairo.restore s.ctx; + Cairo.save s.ctx; + Cairo.transform s.ctx (cairo_matrix_of_m3 m); + Cairo.set_line_width s.ctx o.P.width; + Cairo.set_line_cap s.ctx (cairo_cap o.P.cap); + Cairo.set_line_join s.ctx (cairo_join o.P.join); + Cairo.set_miter_limit s.ctx (Vgr.Private.P.miter_limit o); + set_dashes s o.P.dashes; + Cairo.set_operator s.ctx Cairo.CLEAR; + let w = float (Cairo.Image.get_width s.surface) in + let h = float (Cairo.Image.get_height s.surface) in + Cairo.rectangle s.ctx 0. 0. w h; + Cairo.fill s.ctx; + Cairo.set_operator s.ctx Cairo.OVER + + +let push_transform s tr = + let m = match tr with + | Move v -> Cairo.translate s.ctx (V2.x v) (V2.y v); M3.move2 v + | Rot a -> Cairo.rotate s.ctx a; M3.rot2 a + | Scale sv -> Cairo.scale s.ctx (V2.x sv) (V2.y sv); M3.scale2 sv + | Matrix m -> Cairo.transform s.ctx (cairo_matrix_of_m3 m); m + in + s.gstate.g_tr <- M3.mul s.gstate.g_tr m + +let set_outline s o = + if s.gstate.g_outline == o then () else + let old = s.gstate.g_outline in + s.gstate.g_outline <- o; + if old.P.width <> o.P.width then + (Cairo.set_line_width s.ctx o.P.width); + if old.P.cap <> o.P.cap then + (Cairo.set_line_cap s.ctx (cairo_cap o.P.cap)); + if old.P.join <> o.P.join then + (Cairo.set_line_join s.ctx (cairo_join o.P.join)); + if old.P.miter_angle <> o.P.miter_angle then + (Cairo.set_miter_limit s.ctx (Vgr.Private.P.miter_limit o)); + if old.P.dashes <> o.P.dashes then set_dashes s o.P.dashes; + () + +let get_primitive s p = try Hashtbl.find s.prims p with +| Not_found -> + let add_stop g (t, c) = + let c = Color.to_srgb c in + Cairo.Pattern.add_color_stop_rgba g ~ofs:t + (V4.x c) (V4.y c) (V4.z c) (V4.w c) in + let create = function + | Const c -> + let c = Color.to_srgb c in + Pattern V4.(Cairo.Pattern.create_rgba (x c) (y c) (z c) (w c)) + | Axial (stops, pt, pt') -> + let g = V2.(Cairo.Pattern.create_linear (x pt) (y pt) + (x pt') (y pt')) in + List.iter (add_stop g) stops; Pattern g + | Radial (stops, f, c, r) -> + let g = V2.(Cairo.Pattern.create_radial + (x f) (y f) 0.0 (x c) (y c) r) in + List.iter (add_stop g) stops; Pattern g + | Raster _ -> assert false + in + let prim = create p in + Hashtbl.add s.prims p prim; prim + +let get_font s font = try Hashtbl.find s.fonts font with +| Not_found -> + let cairo_font = + let slant = match font.Font.slant with + | `Italic -> Cairo.Italic + | `Normal -> Cairo.Upright + | `Oblique -> Cairo.Oblique in + let weight = match font.Font.weight with + | `W700 | `W800 | `W900 -> Cairo.Bold + | _ -> Cairo.Normal in + Font (Cairo.Font_face.create ~family:font.Font.name slant weight) + in + Hashtbl.add s.fonts font cairo_font; cairo_font + +let set_source s p = + let (Pattern g) as p = get_primitive s p in + Cairo.set_source s.ctx g; + p + +let set_stroke s p = s.gstate.g_stroke <- set_source s p + +let set_fill s p = s.gstate.g_fill <- set_source s p + +let set_font s font size = + let Font f = get_font s font in + Cairo.Font_face.set s.ctx f; + Cairo.set_font_size s.ctx size + +let set_path s p = + let rec loop last = function + | [] -> () + | seg :: segs -> + match seg with + | `Sub pt -> P2.(Cairo.move_to s.ctx (x pt) (y pt)); loop pt segs + | `Line pt -> P2.(Cairo.line_to s.ctx (x pt) (y pt)); loop pt segs + | `Qcurve (q, pt) -> + let x,y = Cairo.Path.get_current_point s.ctx in + let p0 = V2.v x y in + let c = V2.((q + 2. * p0) / 3.) in + let c' = V2.((pt + 2. * q) / 3.) in + P2.(Cairo.curve_to s.ctx (x c) (y c) (x c') (y c') (x pt) (y pt)); + loop pt segs + | `Ccurve (c, c', pt) -> + P2.(Cairo.curve_to s.ctx (x c) (y c) (x c') (y c') (x pt) (y pt)); + loop pt segs + | `Earc (large, cw, r, a, pt) -> + begin match Vgr.Private.P.earc_params last large cw r a pt with + | None -> P2.(Cairo.line_to s.ctx (x pt) (y pt)); loop pt segs + | Some (c, m, a, a') -> + Cairo.save s.ctx; + let c = V2.ltr (M2.inv m) c in + M2.(Cairo.transform s.ctx (cairo_matrix (e00 m) (e10 m) + (e01 m) (e11 m) + 0. 0.)); + let arc = if cw then Cairo.arc_negative else Cairo.arc in + P2.(arc s.ctx ~x:(x c) ~y:(y c) ~r:1.0 ~a1:a ~a2:a'); + Cairo.restore s.ctx; + loop pt segs + end + | `Close -> Cairo.Path.close s.ctx; loop last (* we don't care *) segs + in + Cairo.Path.clear s.ctx; + loop P2.o (List.rev p) + +let rec r_cut s a = function +| Primitive (Raster _) -> assert false +| Primitive p -> + begin match a with + | `O o -> set_outline s o; set_stroke s p; Cairo.stroke s.ctx + | `Aeo | `Anz -> + set_fill s p; + Cairo.set_fill_rule s.ctx (cairo_fill_rule a); + Cairo.fill s.ctx + end +| Tr (tr, i) -> + Cairo.save s.ctx; + s.todo <- (save_gstate s) :: s.todo; + push_transform s tr; + r_cut s a i +| Blend _ | Cut _ | Cut_glyphs _ as i -> + let a = match a with + | `O _ -> warn s (`Unsupported_cut (a, image i)); `Anz + | a -> a + in + Cairo.save s.ctx; + Cairo.set_fill_rule s.ctx (cairo_fill_rule a); + Cairo.clip s.ctx; + s.todo <- (Draw i) :: (save_gstate s) :: s.todo + +let rec r_cut_glyphs s a run i = match run.text with +| None -> warn s (`Textless_glyph_cut (image (Cut_glyphs (a, run, i)))) +| Some text -> + Cairo.save s.ctx; + s.todo <- (save_gstate s) :: s.todo; + let font_size = run.font.Font.size in + set_font s run.font font_size; + Cairo.Path.clear s.ctx; + M3.(Cairo.transform s.ctx (cairo_matrix 1.0 0.0 + 0.0 (-1.0) + 0.0 0.0)); + Cairo.move_to s.ctx 0. 0.; + Cairo.Path.text s.ctx text; + begin match a with + | `O o -> + set_outline s o; + begin match i with + | Primitive p -> + set_stroke s p; + Cairo.stroke s.ctx + | _ -> + warn s (`Unsupported_glyph_cut (a, image i)) + end + | `Aeo | `Anz -> + Cairo.clip s.ctx; + M3.(Cairo.transform s.ctx (cairo_matrix 1.0 0.0 + 0.0 (-1.0) + 0.0 0.0)); + s.todo <- Draw i :: s.todo + end + + +let rec r_image s k r = + if s.cost > limit s then (s.cost <- 0; partial (r_image s k) r) else + match s.todo with + | [] -> k r + | Set gs :: todo -> + Cairo.restore s.ctx; + set_gstate s gs; + s.todo <- todo; + r_image s k r + | Draw i :: todo -> + s.cost <- s.cost + 1; + match i with + | Primitive _ as i -> (* Uncut primitive, just cut to view. *) + let p = view_rect s in + s.todo <- (Draw (Cut (`Anz, p, i))) :: todo; + r_image s k r + | Cut (a, p, i) -> + s.todo <- todo; + set_path s p; + r_cut s a i; + r_image s k r + | Cut_glyphs (a, run, i) -> + s.todo <- todo; + r_cut_glyphs s a run i; + r_image s k r + | Blend (_, _, i, i') -> + s.todo <- (Draw i') :: (Draw i) :: todo; + r_image s k r + | Tr (tr, i) -> + Cairo.save s.ctx; + s.todo <- (Draw i) :: (save_gstate s) :: todo; + push_transform s tr; + r_image s k r + +let vgr_output r str = + let k' _ = `Ok in + ignore (Vgr.Private.writes str 0 (String.length str) k' r) + +let render s v k r = match v with +| `End -> + if s.backend = `PNG then + Cairo.PNG.write_to_stream s.surface (vgr_output r); + if s.backend = `Surface + then k r + else (Cairo.Surface.finish s.surface; Vgr.Private.flush k r) +| `Image (size, view, i) -> + let cw = (Size2.w size /. 1000.) *. (V2.x s.resolution) in + let ch = (Size2.h size /. 1000.) *. (V2.y s.resolution) in + if cw = 0.0 || ch = 0.0 then err_zero_size () ; + (* Map view rect (bot-left coords) to surface (top-left coords) *) + let sx = cw /. Box2.w view in + let sy = ch /. Box2.h view in + let dx = -. Box2.ox view *. sx in + let dy = ch +. Box2.oy view *. sy in + let view_tr = M3.v sx 0. dx + 0. (-. sy) dy + 0. 0. 1. + in + s.cost <- 0; + s.view <- view; + s.view_tr <- view_tr; + s.todo <- [ Draw i ]; + s.gstate <- { init_gstate with g_tr = init_gstate.g_tr }; (* copy *) + init_ctx s; + r_image s k r + +let format_render resolution backend = + let s = ref None in + fun v k r -> + match !s, v with + | Some s, _ -> render s v k r + | None, `End -> k r + | None, `Image (size, view, i) -> + let w = (Size2.w size /. 1000.) *. (V2.x resolution) in + let h = (Size2.h size /. 1000.) *. (V2.y resolution) in + let surface = match backend with + | `PNG -> + Cairo.Image.(create ARGB32 (int_of_float w) (int_of_float h)) + | `PDF -> Cairo.PDF.create_for_stream (vgr_output r) w h + | `PS -> Cairo.PS.create_for_stream (vgr_output r) w h + | `SVG -> Cairo.SVG.create_for_stream (vgr_output r) w h + in + let ctx = Cairo.create surface in + Cairo.save ctx; + let state = + { r; surface; ctx; resolution; size; + backend = (backend :> cairo_backend); + cost = 0; + view = Box2.empty; + view_tr = M3.id; + todo = []; + fonts = Hashtbl.create 20; + prims = Hashtbl.create 231; + gstate = init_gstate; } in + s := Some state; + render state v k r + +let target ?(resolution = default_resolution) backend = + let target _ _ = false, format_render resolution backend in + Vgr.Private.create_target target + +let target_surface ?size surface = + let target r _ = + let sw = Cairo.Image.get_width surface in + let sh = Cairo.Image.get_height surface in + let size = + if sw > 0 && sh > 0 + then Size2.v (float sw) (float sh) + else match size with + | None -> err_zero_size () + | Some s -> + if Size2.w s > 0.0 && Size2.h s > 0.0 + then s + else err_zero_size () in + let ctx = Cairo.create surface in + Cairo.save ctx; + true, render { r; surface; ctx; size; + resolution = Size2.v 1.0 1.0; + backend = `Surface; + cost = 0; + view = Box2.empty; + view_tr = M3.id; + todo = []; + fonts = Hashtbl.create 20; + prims = Hashtbl.create 231; + gstate = init_gstate; } + in + Vgr.Private.create_target target + +(*--------------------------------------------------------------------------- + Copyright 2014 Arthur Wendling, Daniel C. Bünzli. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. Neither the name of Daniel C. Bünzli nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------------*) diff --git a/src/vgr_cairo.mli b/src/vgr_cairo.mli new file mode 100644 index 0000000..2d7db43 --- /dev/null +++ b/src/vgr_cairo.mli @@ -0,0 +1,117 @@ +(*--------------------------------------------------------------------------- + Copyright 2014 Arthur Wendling, Daniel C. Bünzli. All rights reserved. + Distributed under the BSD3 license, see license at the end of the file. + %%NAME%% release %%VERSION%% + ---------------------------------------------------------------------------*) + +(* Based on the Vgr_htmlc documentation by Daniel C. Bünzli. *) + +(** Vg Cairo renderer. + + {b Dependencies:} + {ul {- {e {{:http://forge.ocamlcore.org/projects/cairo/}Cairo2 bindings + for OCaml}}} + {- {e {{:http://cairographics.org/}Cairo Graphics library}}}} + + {e Release %%VERSION%% — %%MAINTAINER%% } *) + +(** {1:target Cairo render targets} *) + +val target : ?resolution:Gg.V2.t -> [< `PDF | `PNG | `PS | `SVG ] -> + Vg.Vgr.dst_stored Vg.Vgr.target +(** [target resolution fmt] is a render target for rendering to the stored + destination given to {!Vg.Vgr.create} in the chosen format [fmt]. + {ul + {- [resolution], specifies the rendering resolution in samples per + meters. The PDF, PS and SVG formats are measured in points by Cairo, + while the PNG format is in pixels. If unspecified, the default + conversion to points is used.}} + + {b Multiple images.} Multiple images render on the target are not + supported. [Invalid_argument] is raised by {!Vg.Vgr.render} if multiple + images are rendered. *) + +val target_surface : ?size:Gg.size2 -> Cairo.Surface.t -> + [`Other] Vg.Vgr.target +(** [target_surface size s] is a render target for rendering to the Cairo + surface [s]. + {ul + {- The physical size of {{!Vg.Vgr.renderable}renderables} is ignored and + the view rectangle is mapped on the surface size.} + {- [size], Surfaces created with [Cairo.Surface] have a valid size, while + file based surfaces have a size of zero by default: If the size of the + surface can not be determined, the optional argument [size] is used + instead. [Invalid_argument] is raised if the size is invalid.}} + + {b Multiple images.} Multiple images render on the target is supported. + Each new render clears the surface. However, the results are dependent on + Cairo internals: file based surfaces for PDF, PS and SVG do not clear the + view and blend the different images instead. *) + +(** {1:text Text rendering} + + {b Warning.} The following is subject to change in the future. + + Currently text rendering uses Cairo's font selection mechanism + and doesn't support the glyph API. + + Given a glyph cut: + +{!Vg.I.cut_glyphs}[ ~text ~blocks ~advances font glyphs] + + The [blocks], [advances] and [glyphs] parameters are ignored. + [text] must be provided and is used to define the text to render. + [font] is used to select the font family. + + The weight is limited to Normal ([< `W700]) and Bold ([>= `W700]). *) + +(** {1:limits Render warnings and limitations} + + The following render warnings are reported. + {ul + {- [`Unsupported_cut (`O o, i)], outline area cuts can be performed + only on (possibly transformed) {!Vg.I.const}, {!Vg.I.axial} and + {!Vg.I.radial} images.} + {- [`Unsupported_glyph_cut (`O o, i)], outline glyph cuts can be + performed only on (untransformed) {!Vg.I.const}, {!Vg.I.axial} + and {!Vg.I.radial} images.} + {- [`Textless_glyph_cut i] if no [text] argument is specified in a + glyph cut.}} + + The following limitations should be taken into account. + {ul + {- In Cairo, the gradient color interpolation is performed + in (non-linear) sRGB space. This doesn't respect Vg's semantics.}} *) + +(*--------------------------------------------------------------------------- + Copyright 2014 Arthur Wendling, Daniel C. Bünzli. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. Neither the name of Daniel C. Bünzli nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------------*) diff --git a/src/vgr_cairo.mllib b/src/vgr_cairo.mllib new file mode 100644 index 0000000..db30c28 --- /dev/null +++ b/src/vgr_cairo.mllib @@ -0,0 +1 @@ +Vgr_cairo diff --git a/test/min_cairo.ml b/test/min_cairo.ml new file mode 100644 index 0000000..1fe4e2c --- /dev/null +++ b/test/min_cairo.ml @@ -0,0 +1,36 @@ +(* This code is in the public domain. + + Minimal Vgr_cairo examples. Compile with: + + ocamlfind ocamlc \ + -package cairo2 \ + -package gg,vg,vg.cairo \ + -linkpkg -o min_cairo.byte min_cairo.ml +*) + +open Gg +open Vg + +(* 1. Define your image *) + +let aspect = 1.618 +let size = Size2.v (aspect *. 100.) 100. (* mm *) +let view = Box2.v P2.o (Size2.v aspect 1.) +let image = I.const (Color.v_srgb 0.314 0.784 0.471) + +(* 2. Render *) + +let () = + let warn w = Vgr.pp_warning Format.err_formatter w in + let r = Vgr.create ~warn (Vgr_cairo.target `PS) (`Channel stdout) in + ignore (Vgr.render r (`Image (size, view, image))); + ignore (Vgr.render r `End) + +(* 3. Render with a manually created surface *) + +let () = + let surface = Cairo.Image.(create ARGB32) 400 400 in + let warn w = Vgr.pp_warning Format.err_formatter w in + let r = Vgr.create ~warn (Vgr_cairo.target_surface surface) `Other in + ignore (Vgr.render r (`Image (size, view, image))); + ignore (Vgr.render r `End) diff --git a/test/rcairo.ml b/test/rcairo.ml new file mode 100644 index 0000000..0b69933 --- /dev/null +++ b/test/rcairo.ml @@ -0,0 +1,58 @@ +(*--------------------------------------------------------------------------- + Copyright 2014 Arthur Wendling. All rights reserved. + Distributed under the BSD3 license, see license at the end of the file. + %%NAME%% release %%VERSION%% + ---------------------------------------------------------------------------*) + +open Gg +open Vg + +include Db_contents + +let formats = [ + "png", `PNG; + "pdf", `PDF; + "ps", `PS; + "svg", `SVG; + ] + +let renderer fmt dst _ = + let cairo_fmt = List.assoc fmt formats in + Vgr.create (Vgr_cairo.target cairo_fmt) dst + +let ftypes = List.map fst formats +let () = + Rstored.main_multiformats ~no_pack:true "PNG, PDF, PS or SVG" ftypes renderer + +(*--------------------------------------------------------------------------- + Copyright 2014 Arthur Wendling. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. Neither the name of Daniel C. Bünzli nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------------*) diff --git a/test/rstored.ml b/test/rstored.ml index df467fd..4550b5a 100644 --- a/test/rstored.ml +++ b/test/rstored.ml @@ -157,13 +157,14 @@ let pp_image_info ppf i = (* Command line *) -let main ?(no_pack = false) rname ftype renderer = +let main_multiformats ?(no_pack = false) rname ftypes renderer = let usage = Printf.sprintf "Usage: %s [OPTION]... [ID1] [ID2]...\n\ \ Renders images of the Vg image database to %s files.\n\ \ Without any selector and ID specified renders all images.\n\ Options:" exec rname in + let ftype = ref (List.hd ftypes) in let cmd = ref `Image_render in let set_cmd v () = cmd := v in let list () = let l = ref [] in (l, fun v -> l := v :: !l) in @@ -176,7 +177,11 @@ let main ?(no_pack = false) rname ftype renderer = let use_unix = ref false in let usize = ref unix_buffer_size in let nat s r v = if v > 0 then r := v else log "%s must be > 0, ignored\n" s in - let options = [ + let options = + (match ftypes with [] | [_] -> [] | _ -> [ + "-format", Arg.Symbol (ftypes, ( := ) ftype), + Printf.sprintf "Selects the image format (default: %s)" !ftype + ]) @ [ "-dump", Arg.Unit (set_cmd `Image_dump), (str " Output a textual internal representation"); "-p", Arg.String add_prefix, @@ -209,11 +214,12 @@ let main ?(no_pack = false) rname ftype renderer = in match !cmd with | `Image_render -> - let render = render !sout !use_unix !usize !dir ftype !pack renderer in + let renderer = renderer !ftype in + let render = render !sout !use_unix !usize !dir !ftype !pack renderer in let dur = duration render imgs in log "Wrote %d images in %a.@." (List.length imgs) pp_dur dur | `Image_dump -> - let dur = duration (List.iter (dump !dir ftype)) imgs in + let dur = duration (List.iter (dump !dir !ftype)) imgs in log "Wrote %d images in %a.@." (List.length imgs) pp_dur dur | `Image_info -> pp Format.std_formatter "@[%a@]@." (pp_list pp_image_info) imgs @@ -225,6 +231,9 @@ let main ?(no_pack = false) rname ftype renderer = let tags = List.fold_left add_tags [] imgs in List.iter print_endline (List.sort compare tags) +let main ?no_pack rname ftype renderer = + main_multiformats ?no_pack rname [ftype] (fun _ -> renderer) + (*--------------------------------------------------------------------------- Copyright 2013 Daniel C. Bünzli. All rights reserved. diff --git a/test/tests.itarget b/test/tests.itarget index ed9c47a..a930031 100644 --- a/test/tests.itarget +++ b/test/tests.itarget @@ -1,10 +1,12 @@ min_svg.native min_pdf.native +min_cairo.native min_htmlc.byte rsvg.native rpdf.native +rcairo.native rhtmlc.byte examples.native sqc.byte vecho.native -fglyphs.native \ No newline at end of file +fglyphs.native