diff --git a/examples/SVGExample.re b/examples/SVGExample.re index e350596a9..0eb4461c8 100644 --- a/examples/SVGExample.re +++ b/examples/SVGExample.re @@ -3,94 +3,6 @@ open Revery.UI; open Revery.UI.Components; let examples = [ - ( - "Kitchen Sink", - {| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |}, - ), ( "", {| @@ -326,11 +238,6 @@ let examples = [ ( " - quadratic bezier 2", {| - - - - Example quad01 - quadratic Bézier commands in path data @@ -483,48 +390,45 @@ module SVGExample = { ]; let buttons = [width(200), color(Colors.white), marginLeft(10)]; - - let example = [width(350), height(350)]; }; - let loadSVG = data => - switch (SVG.fromString(data)) { - | Some(svg) => Ok(svg) - | None => Error("Parse error: empty string") - | exception (Failure(msg)) => Error("Parse error: " ++ msg) - }; - let%component make = () => { let%hook (currentExample, setExample) = { - let initialState = - switch (examples) { - | [(_, data), ..._] => loadSVG(data) - | [] => Error("No examples!") - }; - Hooks.state(initialState); + let (_, data) = List.hd(examples); + Hooks.state(data); }; + let%hook (width, setWidth) = Hooks.state(300.); + let%hook (height, setHeight) = Hooks.state(300.); let buttons = List.map( ((text, value)) => RadioButtonsString.{text, value}, examples, ); - let onChange = data => setExample(_ => loadSVG(data)); - - let example = - switch (currentExample) { - | Ok(svg) => - | Error(message) => - }; + let onChange = data => setExample(_ => data); - - example + + + setWidth(_ => w)} + maximumValue=300. + minimumValue=50. + initialValue=width + /> + setHeight(_ => h)} + maximumValue=500. + minimumValue=50. + initialValue=height + /> + + ; }; }; diff --git a/src/Revery.re b/src/Revery.re index 0f879bf32..c08e624f7 100644 --- a/src/Revery.re +++ b/src/Revery.re @@ -34,5 +34,3 @@ module Debug = { module Utility = { include Revery_Utility; }; - -module SVG = Revery_SVG; diff --git a/src/SVG/Model.re b/src/SVG/Model.re deleted file mode 100644 index 40a4659d6..000000000 --- a/src/SVG/Model.re +++ /dev/null @@ -1,132 +0,0 @@ -type point = { - x: float, - y: float, -}; - -type length = [ - | `zero - | `user(float) - | `em(float) - | `ex(float) - | `px(float) - | `in_(float) - | `cm(float) - | `mm(float) - | `pt(float) - | `pc(float) -]; -type percentage = [ | `percentage(float)]; - -type simplePaint = [ | `none | `currentColor | `color(Skia.Color.t)]; -type paint = [ simplePaint | `inherit_ | `funciri(string, simplePaint)]; - -module Attribute = { - type presentation = [ - | `fill(paint) - | `stroke(paint) - | `strokeWidth(length) - ]; - - type t = [ presentation]; -}; - -module Geometry = { - type pathCommand = [ - // MoveTo - | `M(float, float) // x, y - | `m(float, float) // dx, dy - // LineTo - | `L(float, float) // x, y - | `l(float, float) // dx, dy - | `H(float) // x - | `h(float) // dx - | `V(float) // y - | `v(float) // dy - // CubicBezierCurve - | `C(float, float, float, float, float, float) // x1, y1, x2, y2, x, y - | `c(float, float, float, float, float, float) // dx1, dy1, dx2, dy2, dx, dy - | `S(float, float, float, float) // x2, y2, x, y - | `s(float, float, float, float) // dx2, dy2, dx, dy - // QuadraticBezierCurve - | `Q(float, float, float, float) // x1, y1, x, y - | `q(float, float, float, float) // dx1, dy1, dx, dy - | `T(float, float) // x, y - | `t(float, float) // dx, dy - // EllipticalArcCurve - | `A( - float, - float, - float, - [ | `large | `small], - [ | `cw | `ccw], - float, - float, - ) // rx, ry, angle, large-arc-flag, sweep-flag, x, y - | `a( - float, - float, - float, - [ | `large | `small], - [ | `cw | `ccw], - float, - float, - ) // rx, ry, angle, large-arc-flag, sweep-flag, dx, dy - // ClosePath - | `Z - | `z - ]; - - type kind = - | Circle({ - cx: [ length | percentage], - cy: [ length | percentage], - r: length, - }) - | Ellipse({ - cx: [ length | percentage], - cy: [ length | percentage], - rx: [ | `auto | length | percentage], - ry: [ | `auto | length | percentage], - }) - | Line({ - x1: [ length | percentage], - y1: [ length | percentage], - x2: [ length | percentage], - y2: [ length | percentage], - }) - | Path({d: list(pathCommand)}) - | Polygon({points: list(point)}) - | Polyline({points: list(point)}) - | Rect({ - x: [ length | percentage], - y: [ length | percentage], - width: [ | `auto | length | percentage], - height: [ | `auto | length | percentage], - rx: [ | `auto | length | percentage], - ry: [ | `auto | length | percentage], - }); - - type t = { - attributes: list(Attribute.t), - kind, - }; -}; - -type element = - | Group(list(element)) - | Geometry(Geometry.t) - | Text(string); - -type definition = {id: string}; - -type viewport = { - origin: point, - width: float, - height: float, -}; - -type t = { - defs: list(definition), - elements: list(element), - viewport: option(viewport), -}; diff --git a/src/SVG/Parser.re b/src/SVG/Parser.re deleted file mode 100644 index 254b87442..000000000 --- a/src/SVG/Parser.re +++ /dev/null @@ -1,498 +0,0 @@ -open Revery_Core; -open SimpleXml; -open Model; - -let parseFloat = str => - try(float_of_string(str)) { - | _ => failwith("invalid float: " ++ str) - }; - -let length = str => `user(parseFloat(str)); // TODO -let length_percentage = str => `user(parseFloat(str)); // TODO -let length_percentage_auto = str => `user(parseFloat(str)); // TODO - -let paint = - fun - | "none" => Some(`none) - | "currentColor" => Some(`currentColor) - | value when value.[0] == '#' => - switch (`color(Color.hex(value) |> Color.toSkia)) { - | value => Some(value) - | exception _ => None - } - // TODO: functions - | value => - Colors.fromString(value) - |> Option.map(color => `color(Color.toSkia(color))); - -let attribute = - fun - | ("fill", value) => paint(value) |> Option.map(v => `fill(v)) - | ("stroke", value) => paint(value) |> Option.map(v => `stroke(v)) - | ("stroke-width", value) => Some(`strokeWidth(length(value))) - | _ => None; - -let coordinates = str => { - let buffer = Buffer.create(10); - let acc = ref([]); - - let accept = () => - if (Buffer.length(buffer) > 0) { - let coord = Buffer.contents(buffer) |> parseFloat; - acc := [coord, ...acc^]; - Buffer.clear(buffer); - }; - - String.iter( - fun - | ('+' | '-') as ch => { - accept(); - Buffer.add_char(buffer, ch); - } - | ('0' .. '9' | '.') as ch => Buffer.add_char(buffer, ch) - | '\t' - | ' ' - | '\n' - | '\012' - | '\r' - | ',' => accept() - | ch => failwith("invalid character in path data: " ++ Char.escaped(ch)), - str, - ); - - accept(); - - List.rev(acc^); -}; - -let pathCommands = str => { - let tokens = { - let buffer = Buffer.create(10); - - let arg = str => `arg(parseFloat(str)); - let cmd = str => `cmd(str.[0]); - - let push = i => Buffer.add_char(buffer, str.[i]); - - let accept = (f, acc) => { - let str = Buffer.contents(buffer); - Buffer.clear(buffer); - str != "" ? [f(str), ...acc] : acc; - }; - - let eof = acc => accept(arg, acc) |> List.rev; - - // Lexer states - let rec initial = (acc, i) => - switch (str.[i]) { - | 'M' - | 'm' - | 'L' - | 'l' - | 'H' - | 'h' - | 'V' - | 'v' - | 'C' - | 'c' - | 'S' - | 's' - | 'Q' - | 'q' - | 'T' - | 't' - | 'A' - | 'a' - | 'Z' - | 'z' => - let acc = accept(arg, acc); - push(i); - let acc = accept(cmd, acc); - initial(acc, i + 1); - - | '+' - | '-' => - let acc = accept(arg, acc); - push(i); - number_preDot(acc, i + 1); - - | '.' => - let acc = accept(arg, acc); - number_postDot(acc, i + 1); - - | '0' .. '9' => - push(i); - number_preDot(acc, i + 1); - - // white-space or comma - | '\t' - | ' ' - | '\n' - | '\012' - | '\r' - | ',' => - let acc = accept(arg, acc); - initial(acc, i + 1); - - | ch => - failwith("invalid character in path data: " ++ Char.escaped(ch)) - - | exception (Invalid_argument(_)) => eof(acc) - } - - and number_preDot = (acc, i) => - switch (str.[i]) { - | '.' => - push(i); - number_postDot(acc, i + 1); - - | '0' .. '9' => - push(i); - number_preDot(acc, i + 1); - - | 'e' => - push(i); - exponent_initial(acc, i + 1); - - | _ => initial(acc, i) - - | exception (Invalid_argument(_)) => eof(acc) - } - - and number_postDot = (acc, i) => - switch (str.[i]) { - | '0' .. '9' => - push(i); - number_postDot(acc, i + 1); - - | 'e' => - push(i); - exponent_initial(acc, i + 1); - - | _ => initial(acc, i) - - | exception (Invalid_argument(_)) => eof(acc) - } - - and exponent_initial = (acc, i) => - switch (str.[i]) { - | '+' - | '-' => - push(i); - exponent(acc, i + 1); - - | _ => exponent(acc, i) - - | exception (Invalid_argument(_)) => eof(acc) - } - - and exponent = (acc, i) => - switch (str.[i]) { - | '0' .. '9' => - push(i); - exponent(acc, i + 1); - - | _ => initial(acc, i) - - | exception (Invalid_argument(_)) => eof(acc) - }; - - initial([], 0); - }; - - let parse = tokens => { - let rec start = acc => - fun - | [] => List.rev(acc) - | [`cmd('M'), ...rest] => cmd_M(acc, rest) - | [`cmd('m'), ...rest] => cmd_m(acc, rest) - - | [`cmd('L'), ...rest] => cmd_L(acc, rest) - | [`cmd('l'), ...rest] => cmd_l(acc, rest) - | [`cmd('H'), ...rest] => cmd_H(acc, rest) - | [`cmd('h'), ...rest] => cmd_h(acc, rest) - | [`cmd('V'), ...rest] => cmd_V(acc, rest) - | [`cmd('v'), ...rest] => cmd_v(acc, rest) - - | [`cmd('C'), ...rest] => cmd_C(acc, rest) - | [`cmd('c'), ...rest] => cmd_c(acc, rest) - | [`cmd('S'), ...rest] => cmd_S(acc, rest) - | [`cmd('s'), ...rest] => cmd_s(acc, rest) - - | [`cmd('Q'), ...rest] => cmd_Q(acc, rest) - | [`cmd('q'), ...rest] => cmd_q(acc, rest) - | [`cmd('T'), ...rest] => cmd_T(acc, rest) - | [`cmd('t'), ...rest] => cmd_t(acc, rest) - - | [`cmd('A'), ...rest] => cmd_A(acc, rest) - | [`cmd('a'), ...rest] => cmd_a(acc, rest) - - | [`cmd('Z'), ...rest] => start([`Z, ...acc], rest) - | [`cmd('z'), ...rest] => start([`z, ...acc], rest) - - | [`arg(_arg), ..._rest] => - // TODO: Handle gracefully - failwith("expected path command, got argument") - | [`cmd(ch), ..._rest] => - // TODO: Handle gracefully - failwith("invalid path command: " ++ Char.escaped(ch)) - - and cmd_M = acc => - fun - | [`arg(x), `arg(y), ...rest] => cmd_M([`M((x, y)), ...acc], rest) - | rest => start(acc, rest) - and cmd_m = acc => - fun - | [`arg(dx), `arg(dy), ...rest] => - cmd_m([`m((dx, dy)), ...acc], rest) - | rest => start(acc, rest) - - and cmd_L = acc => - fun - | [`arg(x), `arg(y), ...rest] => cmd_L([`L((x, y)), ...acc], rest) - | rest => start(acc, rest) - and cmd_l = acc => - fun - | [`arg(dx), `arg(dy), ...rest] => - cmd_l([`l((dx, dy)), ...acc], rest) - | rest => start(acc, rest) - and cmd_H = acc => - fun - | [`arg(x), ...rest] => cmd_H([`H(x), ...acc], rest) - | rest => start(acc, rest) - and cmd_h = acc => - fun - | [`arg(dx), ...rest] => cmd_h([`h(dx), ...acc], rest) - | rest => start(acc, rest) - and cmd_V = acc => - fun - | [`arg(y), ...rest] => cmd_V([`V(y), ...acc], rest) - | rest => start(acc, rest) - and cmd_v = acc => - fun - | [`arg(dy), ...rest] => cmd_v([`v(dy), ...acc], rest) - | rest => start(acc, rest) - - and cmd_C = acc => - fun - | [`arg(x1), `arg(y1), `arg(x2), `arg(y2), `arg(x), `arg(y), ...rest] => - cmd_C([`C((x1, y1, x2, y2, x, y)), ...acc], rest) - | rest => start(acc, rest) - and cmd_c = acc => - fun - | [ - `arg(dx1), - `arg(dy1), - `arg(dx2), - `arg(dy2), - `arg(dx), - `arg(dy), - ...rest, - ] => - cmd_c([`c((dx1, dy1, dx2, dy2, dx, dy)), ...acc], rest) - | rest => start(acc, rest) - and cmd_S = acc => - fun - | [`arg(x2), `arg(y2), `arg(x), `arg(y), ...rest] => - cmd_S([`S((x2, y2, x, y)), ...acc], rest) - | rest => start(acc, rest) - and cmd_s = acc => - fun - | [`arg(dx2), `arg(dy2), `arg(dx), `arg(dy), ...rest] => - cmd_s([`s((dx2, dy2, dx, dy)), ...acc], rest) - | rest => start(acc, rest) - - and cmd_Q = acc => - fun - | [`arg(x1), `arg(y1), `arg(x), `arg(y), ...rest] => - cmd_Q([`Q((x1, y1, x, y)), ...acc], rest) - | rest => start(acc, rest) - and cmd_q = acc => - fun - | [`arg(dx1), `arg(dy1), `arg(dx), `arg(dy), ...rest] => - cmd_q([`q((dx1, dy1, dx, dy)), ...acc], rest) - | rest => start(acc, rest) - - and cmd_T = acc => - fun - | [`arg(x), `arg(y), ...rest] => cmd_T([`T((x, y)), ...acc], rest) - | rest => start(acc, rest) - and cmd_t = acc => - fun - | [`arg(dx), `arg(dy), ...rest] => - cmd_t([`t((dx, dy)), ...acc], rest) - | rest => start(acc, rest) - - and cmd_A = acc => - fun - | [ - `arg(rx), - `arg(ry), - `arg(angle), - `arg(size), - `arg(sweep), - `arg(x), - `arg(y), - ...rest, - ] => { - let size = size == 0. ? `large : `small; - let sweep = sweep == 0. ? `ccw : `cw; - cmd_A([`A((rx, ry, angle, size, sweep, x, y)), ...acc], rest); - } - | rest => start(acc, rest) - and cmd_a = acc => - fun - | [ - `arg(rx), - `arg(ry), - `arg(angle), - `arg(size), - `arg(sweep), - `arg(x), - `arg(y), - ...rest, - ] => { - let size = size == 0. ? `large : `small; - let sweep = sweep == 0. ? `ccw : `cw; - cmd_a([`a((rx, ry, angle, size, sweep, x, y)), ...acc], rest); - } - | rest => start(acc, rest); - - start([], tokens); - }; - - parse(tokens); -}; - -let attr_length = (key, ~default, attrs) => - List.assoc_opt(key, attrs) |> Option.map(length) |> Option.value(~default); - -let attr_length_percentage = (key, ~default, attrs) => - List.assoc_opt(key, attrs) - |> Option.map(length_percentage) - |> Option.value(~default); - -let attr_length_percentage_auto = (key, ~default, attrs) => - List.assoc_opt(key, attrs) - |> Option.map(length_percentage_auto) - |> Option.value(~default); - -let attr_pathCommands = (key, ~default, attrs) => - List.assoc_opt(key, attrs) - |> Option.map(pathCommands) - |> Option.value(~default); - -let attr_viewBox = attrs => - Option.bind(List.assoc_opt("viewBox", attrs), str => - switch (coordinates(str)) { - | [x, y, width, height] => Some({ - origin: { - x, - y, - }, - width, - height, - }) - | _ => None - } - ); - -let attr_points = (key, ~default, attrs) => { - let rec coordsToPoints = acc => - fun - | [] - | [_] => List.rev(acc) - | [x, y, ...rest] => coordsToPoints([{x, y}, ...acc], rest); - - List.assoc_opt(key, attrs) - |> Option.map(coordinates) - |> Option.map(coordsToPoints([])) - |> Option.value(~default); -}; - -let geometry = (kind, attrs) => - Geometry.{kind, attributes: List.filter_map(attribute, attrs)}; - -let circle = attrs => - geometry( - Circle({ - cx: attr_length_percentage("cx", ~default=`zero, attrs), - cy: attr_length_percentage("cy", ~default=`zero, attrs), - r: attr_length("r", ~default=`zero, attrs), - }), - attrs, - ); - -let ellipse = attrs => - geometry( - Ellipse({ - cx: attr_length_percentage("cx", ~default=`zero, attrs), - cy: attr_length_percentage("cy", ~default=`zero, attrs), - rx: attr_length("rx", ~default=`auto, attrs), - ry: attr_length("ry", ~default=`auto, attrs), - }), - attrs, - ); - -let line = attrs => - geometry( - Line({ - x1: attr_length_percentage("x1", ~default=`zero, attrs), - y1: attr_length_percentage("y1", ~default=`zero, attrs), - x2: attr_length_percentage("x2", ~default=`zero, attrs), - y2: attr_length_percentage("y2", ~default=`zero, attrs), - }), - attrs, - ); - -let path = attrs => - geometry(Path({d: attr_pathCommands("d", ~default=[], attrs)}), attrs); - -let polygon = attrs => - geometry( - Polygon({points: attr_points("points", ~default=[], attrs)}), - attrs, - ); - -let polyline = attrs => - geometry( - Polyline({points: attr_points("points", ~default=[], attrs)}), - attrs, - ); - -let rect = attrs => - geometry( - Rect({ - x: attr_length_percentage("x", ~default=`zero, attrs), - y: attr_length_percentage("y", ~default=`zero, attrs), - width: attr_length_percentage_auto("width", ~default=`auto, attrs), - height: attr_length_percentage_auto("height", ~default=`auto, attrs), - rx: attr_length_percentage_auto("rx", ~default=`auto, attrs), - ry: attr_length_percentage_auto("ry", ~default=`auto, attrs), - }), - attrs, - ); - -let rec element = - fun - //| Element("defs", _, _) => - | Element("g", _, children) => - Some(Group(List.filter_map(element, children))) - | Element("circle", attrs, _children) => Some(Geometry(circle(attrs))) - | Element("ellipse", attrs, _children) => Some(Geometry(ellipse(attrs))) - | Element("line", attrs, _children) => Some(Geometry(line(attrs))) - | Element("path", attrs, _children) => Some(Geometry(path(attrs))) - | Element("polygon", attrs, _children) => Some(Geometry(polygon(attrs))) - | Element("polyline", attrs, _children) => - Some(Geometry(polyline(attrs))) - | Element("rect", attrs, _children) => Some(Geometry(rect(attrs))) - | _ => None; - -let svg = - fun - | Element("svg", attrs, children) => { - defs: [], - elements: List.filter_map(element, children), - viewport: attr_viewBox(attrs), - } - | _ => failwith("svg root element expected"); diff --git a/src/SVG/RenderContext.re b/src/SVG/RenderContext.re deleted file mode 100644 index 4f1ea7d88..000000000 --- a/src/SVG/RenderContext.re +++ /dev/null @@ -1,16 +0,0 @@ -open Revery_Draw; - -type font = { - family: string, - size: float, - xHeight: float, // '1 ex', x-height of the element’s font - chWidth: float // '1 ch', width of the "0" (ZERO, U+0030) glyph in the element’s font -}; - -type context = { - defs: list(Model.definition), - font, - viewport: Model.viewport, - rootFontSize: float, // '1 rem', font size of the root element - canvas: CanvasContext.t, -}; diff --git a/src/SVG/Revery_SVG.re b/src/SVG/Revery_SVG.re deleted file mode 100644 index 20778c782..000000000 --- a/src/SVG/Revery_SVG.re +++ /dev/null @@ -1,7 +0,0 @@ -module Model = Model; - -let fromFile = path => SimpleXml.fromFile(path) |> Option.map(Parser.svg); - -let fromString = str => SimpleXml.fromString(str) |> Option.map(Parser.svg); - -let render = SkiaRenderer.render; diff --git a/src/SVG/SimpleXml.re b/src/SVG/SimpleXml.re deleted file mode 100644 index d358f8424..000000000 --- a/src/SVG/SimpleXml.re +++ /dev/null @@ -1,30 +0,0 @@ -[@deriving show({with_path: false})] -type t = - | Element(string, list((string, string)), list(t)) - | Text(string); - -let trim = - Markup.filter( - fun - | `Text(strs) => strs |> String.concat("") |> String.trim != "" - | _ => true, - ); - -let simplify = stream => - stream - |> trim - |> Markup.tree( - ~text=strings => Text(strings |> String.concat("")), - ~element= - ((_ns, name), attrs, children) => { - let attrs = - attrs |> List.map((((_ns, name), value)) => (name, value)); - Element(name, attrs, children); - }, - ); - -let fromFile = path => - Markup.file(path) |> fst |> Markup.parse_xml |> Markup.signals |> simplify; - -let fromString = str => - Markup.string(str) |> Markup.parse_xml |> Markup.signals |> simplify; diff --git a/src/SVG/SkiaRenderer.re b/src/SVG/SkiaRenderer.re deleted file mode 100644 index 111d90ea6..000000000 --- a/src/SVG/SkiaRenderer.re +++ /dev/null @@ -1,359 +0,0 @@ -// TDDO: 4.10 polyfill. Remove when safe to do so -module List = { - include Stdlib.List; - - let rec find_map = f => - fun - | [] => None - | [head, ...tail] => - switch (f(head)) { - | Some(_) as result => result - | None => find_map(f, tail) - }; -}; - -open Revery_Core; -open Revery_Draw; -open Model; -open RenderContext; - -let userCoord = - fun - | `zero => 0. - | `user(n) => n - | `em(_) - | `ex(_) - | `px(_) - | `in_(_) - | `cm(_) - | `mm(_) - | `pt(_) - | `pc(_) => failwith("TODO - userCoord") - | `percentage(_) => failwith("TODO - userCoord percentage") - | `auto => failwith("TODO - userCoord auto"); - -let paint = - fun - | `none => None - | `currentColor => failwith("TODO - currentColor") - | `color(color) => { - let paint = Skia.Paint.make(); - Skia.Paint.setAntiAlias(paint, true); - Skia.Paint.setColor(paint, color); - Some(paint); - } - | `funciri(_) => failwith("TODO - funciri") - | `inherit_ => failwith("TODO - inherit"); - -let blackPaint = paint(`color(Colors.black |> Color.toSkia)) |> Option.get; - -let circle = (~cx, ~cy, ~r, ~paint, ~context) => { - CanvasContext.drawCircle(~x=cx, ~y=cy, ~radius=r, ~paint, context.canvas); -}; - -let ellipse = (~cx, ~cy, ~rx, ~ry, ~paint, ~context) => { - CanvasContext.drawOval( - ~rect=Skia.Rect.makeLtrb(cx -. rx, cy -. ry, cx +. rx, cy +. ry), - ~paint, - context.canvas, - ); -}; - -let line = (~x1, ~y1, ~x2, ~y2, ~paint, ~context) => { - let path = Skia.Path.make(); - Skia.Path.moveTo(path, x1, y1); - Skia.Path.lineTo(path, x2, y2); - CanvasContext.drawPath(~path, ~paint, context.canvas); -}; - -let path = (~d, ~paint, ~context) => { - let path = Skia.Path.make(); - - List.iter( - { - let previous = ref(None); - - command => { - switch (command) { - | `M(x, y) => Skia.Path.moveTo(path, x, y) - | `m(dx, dy) => Skia.Path.rMoveTo(path, dx, dy) - - | `L(x, y) => Skia.Path.lineTo(path, x, y) - | `l(dx, dy) => Skia.Path.rLineTo(path, dx, dy) - - | `H(x) => - let last = Skia.Point.make(0., 0.); - if (Skia.Path.getLastPoint(path, last)) { - Skia.Path.lineTo(path, x, Skia.Point.getY(last)); - }; - | `h(dx) => Skia.Path.rLineTo(path, dx, 0.) - - | `V(y) => - let last = Skia.Point.make(0., 0.); - if (Skia.Path.getLastPoint(path, last)) { - Skia.Path.lineTo(path, Skia.Point.getX(last), y); - }; - | `v(dy) => Skia.Path.rLineTo(path, 0., dy) - - | `C(x1, y1, x2, y2, x, y) => - Skia.Path.cubicTo(path, x1, y1, x2, y2, x, y) - | `c(dx1, dy1, dx2, dy2, dx, dy) => - Skia.Path.rCubicTo(path, dx1, dy1, dx2, dy2, dx, dy) - - | `S(x2, y2, x, y) => - let (x1, y1) = - switch (previous^) { - // TODO: Implement for c, s - | Some(`C(_, _, px2, py2, px, py) | `S(px2, py2, px, py)) => ( - px -. (px2 -. px), - py -. (py2 -. py), - ) - | _ => (x, y) - }; - Skia.Path.cubicTo(path, x1, y1, x2, y2, x, y); - | `s(dx2, dy2, dx, dy) => - let (dx1, dy1) = - switch (previous^) { - // TODO: Implement for C, S - | Some( - `c(_, _, pdx2, pdy2, pdx, pdy) | `s(pdx2, pdy2, pdx, pdy), - ) => ( - pdx -. pdx2, - pdy -. pdy2, - ) - | _ => (0., 0.) - }; - Skia.Path.rCubicTo(path, dx1, dy1, dx2, dy2, dx, dy); - - | `Q(x1, y1, x, y) => Skia.Path.quadTo(path, x1, y1, x, y) - | `q(dx1, dy1, dx, dy) => Skia.Path.rQuadTo(path, dx1, dy1, dx, dy) - - | `T(x, y) => - let (x1, y1) = - switch (previous^) { - // TODO: Implement for q, T, t - | Some(`Q(px1, py1, px, py)) => ( - px -. (px1 -. px), - py -. (py1 -. py), - ) - | _ => (x, y) - }; - Skia.Path.quadTo(path, x1, y1, x, y); - | `t(_) => failwith("TODO - path t") - - | `A(rx, ry, angle, size, sweep, x, y) => - Skia.Path.arcTo(path, rx, ry, angle, size, sweep, x, y) - | `a(rx, ry, angle, size, sweep, dx, dy) => - Skia.Path.rArcTo(path, rx, ry, angle, size, sweep, dx, dy) - - | `Z - | `z => Skia.Path.close(path) - }; - previous := Some(command); - }; - }, - d, - ); - - CanvasContext.drawPath(~path, ~paint, context.canvas); -}; - -let polygon = (~points, ~paint, ~context) => { - switch (points) { - | [] => () - | [first, ...rest] => - let path = Skia.Path.make(); - - Skia.Path.moveTo(path, first.x, first.y); - List.iter(({x, y}) => Skia.Path.lineTo(path, x, y), rest); - Skia.Path.close(path); - - CanvasContext.drawPath(~path, ~paint, context.canvas); - }; -}; - -let polyline = (~points, ~paint, ~context) => { - switch (points) { - | [] => () - | [first, ...rest] => - let path = Skia.Path.make(); - - Skia.Path.moveTo(path, first.x, first.y); - List.iter(({x, y}) => Skia.Path.lineTo(path, x, y), rest); - - CanvasContext.drawPath(~path, ~paint, context.canvas); - }; -}; - -let rect = (~x, ~y, ~width, ~height, ~rx, ~ry, ~paint, ~context) => { - CanvasContext.drawRoundRect( - ~rect=Skia.Rect.makeLtrb(x, y, x +. width, y +. height), - ~rx, - ~ry, - ~paint, - context.canvas, - ); -}; - -let geometry = (context, shape: Geometry.t) => { - let fill = - List.find_map( - fun - | `fill(spec) => Some(paint(spec)) - | _ => None, - shape.attributes, - ) - |> Option.value(~default=Some(blackPaint)); - - let stroke = { - let make = spec => { - switch (paint(spec)) { - | Some(paint) => - Skia.Paint.setStyle(paint, Stroke); - Skia.Paint.setStrokeWidth(paint, 1.); - - List.iter( - fun - | `strokeWidth(width) => - Skia.Paint.setStrokeWidth(paint, userCoord(width)) - | _ => (), - shape.attributes, - ); - - Some(paint); - | None => None - }; - }; - - List.find_map( - fun - | `stroke(spec) => make(spec) - | _ => None, - shape.attributes, - ); - }; - - switch (shape.kind) { - | Circle({cx, cy, r}) => - let cx = userCoord(cx); - let cy = userCoord(cy); - let r = userCoord(r); - let draw = paint => circle(~cx, ~cy, ~r, ~paint, ~context); - Option.iter(draw, fill); - Option.iter(draw, stroke); - - | Ellipse({cx, cy, rx, ry}) => - let cx = userCoord(cx); - let cy = userCoord(cy); - let rx = userCoord(rx); - let ry = userCoord(ry); - let draw = paint => ellipse(~cx, ~cy, ~rx, ~ry, ~paint, ~context); - Option.iter(draw, fill); - Option.iter(draw, stroke); - - | Line({x1, y1, x2, y2}) => - let x1 = userCoord(x1); - let y1 = userCoord(y1); - let x2 = userCoord(x2); - let y2 = userCoord(y2); - let draw = paint => line(~x1, ~y1, ~x2, ~y2, ~paint, ~context); - Option.iter(draw, stroke); - - | Path({d}) => - let draw = paint => path(~d, ~paint, ~context); - Option.iter(draw, fill); - Option.iter(draw, stroke); - - | Polygon({points}) => - let draw = paint => polygon(~points, ~paint, ~context); - Option.iter(draw, fill); - Option.iter(draw, stroke); - - | Polyline({points}) => - let draw = paint => polyline(~points, ~paint, ~context); - Option.iter(draw, fill); - Option.iter(draw, stroke); - - | Rect({x, y, width, height, rx, ry}) => - let x = userCoord(x); - let y = userCoord(y); - let width = userCoord(width); - let height = userCoord(height); - let (rx, ry) = - switch (rx, ry) { - | (`auto, `auto) => (0., 0.) - | (`auto, r) - | (r, `auto) => - let r = userCoord(r); - (r, r); - | (rx, ry) => (userCoord(rx), userCoord(ry)) - }; - let draw = paint => - rect(~x, ~y, ~width, ~height, ~rx, ~ry, ~paint, ~context); - Option.iter(draw, fill); - Option.iter(draw, stroke); - }; -}; - -let rec element = context => - fun - | Group(elements) => elements |> List.iter(element(context)) - | Geometry(shape) => geometry(context, shape) - | Text(_) => failwith("TODO - text"); - -let applyViewportTransform = - (~width, ~height, viewport: Model.viewport, canvas) => { - let scaleX = float(width) /. viewport.width; - let scaleY = float(height) /. viewport.height; - let translateX = viewport.origin.x *. scaleX; - let translateY = viewport.origin.y *. scaleY; - - let transform = Skia.Matrix.make(); - Skia.Matrix.setAll( - transform, - scaleX, - 0., - translateX, - 0., - scaleY, - translateY, - 0., - 0., - 1., - ); - CanvasContext.concat(transform, canvas); -}; - -let render = (~width, ~height, document: Model.t, canvas) => { - let viewport = - switch (document.viewport) { - | Some(viewport) => - applyViewportTransform(~width, ~height, viewport, canvas); - viewport; - - | None => { - origin: { - x: 0., - y: 0., - }, - width: float(width), - height: float(height), - } - }; - - let context = - RenderContext.{ - defs: document.defs, - font: { - family: "", - size: 10., - xHeight: 10., - chWidth: 10., - }, - viewport, - rootFontSize: 10., - canvas, - }; - - element(context, Group(document.elements)); -}; diff --git a/src/SVG/dune b/src/SVG/dune deleted file mode 100644 index d2fc5d089..000000000 --- a/src/SVG/dune +++ /dev/null @@ -1,6 +0,0 @@ -(library - (name Revery_SVG) - (public_name Revery.SVG) - (preprocess - (pps brisk-reconciler.ppx)) - (libraries markup Revery_Draw)) diff --git a/src/UI_Components/Revery_UI_Components.re b/src/UI_Components/Revery_UI_Components.re index 29adc9d62..e9282776e 100644 --- a/src/UI_Components/Revery_UI_Components.re +++ b/src/UI_Components/Revery_UI_Components.re @@ -28,7 +28,6 @@ module Row = Row; module ScrollView = ScrollView; module Slider = Slider; module Stack = Stack; -module SVGString = SVGString; module RadioButtons = RadioButtons; module RadioButtonsInt = RadioButtons.Make({ @@ -38,6 +37,7 @@ module RadioButtonsString = RadioButtons.Make({ type t = string; }); +module SVG = SVG; module ClickableText = ClickableText; module Ticker = Ticker; module Tree = Tree; diff --git a/src/UI_Components/SVG.re b/src/UI_Components/SVG.re new file mode 100644 index 000000000..769a4b85f --- /dev/null +++ b/src/UI_Components/SVG.re @@ -0,0 +1,91 @@ +open Revery_UI; +open Revery_UI_Primitives; +open Revery_Draw; +open Skia; + +module Defaults = { + let svgWidth = 300.; + let svgHeight = 150.; +}; +let make = + ( + ~src: [ | `Str(string) | `File(string)], + ~scaleMode: [ | `Fit | `Fill]=`Fit, + ~width as maybeWidth: option(float)=?, + ~height as maybeHeight: option(float)=?, + (), + ) => { + let maybeStream = + switch (src) { + | `Str(svgStr) => + Some(Stream.makeMemoryStreamFromString(svgStr, String.length(svgStr))) + | `File(filePath) => Stream.makeFileStream(filePath) + }; + let maybeSVG = + Option.bind(maybeStream, stream => SVG.makeFromStream(stream)); + + switch (maybeSVG) { + | Some(svg) => + let intrinsicWidth = SVG.getContainerWidth(svg); + let intrinsicHeight = SVG.getContainerHeight(svg); + + let adjustedWidth = + if (intrinsicWidth != 0.) { + intrinsicWidth; + } else { + Defaults.svgWidth; + }; + let adjustedHeight = + if (intrinsicHeight != 0.) { + intrinsicHeight; + } else { + Defaults.svgHeight; + }; + + let canvasWidth = Option.value(maybeWidth, ~default=adjustedWidth); + let canvasHeight = Option.value(maybeHeight, ~default=adjustedHeight); + + let (xScale, yScale) = + switch (maybeWidth, maybeHeight, scaleMode) { + | (Some(width), Some(height), `Fill) + when intrinsicWidth != 0. && intrinsicHeight != 0. => ( + width /. intrinsicWidth, + height /. intrinsicHeight, + ) + | (Some(width), Some(height), `Fit) + when intrinsicWidth != 0. && intrinsicHeight != 0. => + let scale = + if (width < height) { + width /. intrinsicWidth; + } else { + height /. intrinsicHeight; + }; + (scale, scale); + | _ => (1., 1.) + }; + + if (intrinsicHeight == 0. && intrinsicWidth == 0.) { + SVG.setContainerSize(svg, canvasWidth, canvasHeight); + }; + + let canvasStyle = + Style.[ + width(canvasWidth +. 0.5 |> int_of_float), + height(canvasHeight +. 0.5 |> int_of_float), + ]; + + { + let transform = Matrix.make(); + Matrix.setIdentity(transform); + Matrix.setScaleX(transform, xScale); + Matrix.setScaleY(transform, yScale); + CanvasContext.concat(transform, canvasContext); + + SVG.render(svg, canvasContext.canvas); + }} + />; + | None => + }; +}; diff --git a/src/UI_Components/SVGString.re b/src/UI_Components/SVGString.re deleted file mode 100644 index b345e347d..000000000 --- a/src/UI_Components/SVGString.re +++ /dev/null @@ -1,14 +0,0 @@ -open Revery_Core; -open Revery_UI; -open Revery_UI_Primitives; - -module Log = (val Log.withNamespace("Revery.UI.Components.SVG")); - -let make = (~style=?, ~contents, ()) => { - let render = (canvas, Dimensions.{width, height, _}) => - try(Revery_SVG.render(~width, ~height, contents, canvas)) { - | Failure(error) => Log.error("Render error: " ++ error) - }; - - ; -}; diff --git a/src/UI_Components/dune b/src/UI_Components/dune index 99ce98009..87cb60a01 100644 --- a/src/UI_Components/dune +++ b/src/UI_Components/dune @@ -4,4 +4,4 @@ (pps brisk-reconciler.ppx lwt_ppx)) (public_name Revery.UI_Components) (libraries lwt lwt.unix sdl2 flex omd Revery_Core Revery_Math Revery_UI - Revery_UI_Primitives Revery_UI_Hooks Revery_SVG)) + Revery_UI_Primitives Revery_UI_Hooks)) diff --git a/src/dune b/src/dune index 32e5ade36..6da260515 100644 --- a/src/dune +++ b/src/dune @@ -5,7 +5,7 @@ (pps brisk-reconciler.ppx lwt_ppx)) (libraries lwt lwt.unix sdl2 Revery_Core Revery_Font Revery_Draw Revery_Math Revery_UI Revery_UI_Components Revery_Native Revery_UI_Primitives - Revery_UI_Hooks Revery_Utility Revery_SVG)) + Revery_UI_Hooks Revery_Utility)) (documentation (package Revery)