From 9a5076944a29537bd6013df67e897eb4c7ff7f35 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 20 Jan 2019 15:51:48 +0000 Subject: [PATCH] feat(hdom-canvas): add ellipse() / ellipticArc(), update readme --- packages/hdom-canvas/README.md | 133 +++++++++++++++++++++--------- packages/hdom-canvas/src/index.ts | 35 ++++++-- 2 files changed, 126 insertions(+), 42 deletions(-) diff --git a/packages/hdom-canvas/README.md b/packages/hdom-canvas/README.md index fdc8a39d57..71429dbae6 100644 --- a/packages/hdom-canvas/README.md +++ b/packages/hdom-canvas/README.md @@ -7,41 +7,42 @@ This project is part of the [@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. - - -- [@thi.ng/hdom-canvas](#thinghdom-canvas) - - [About](#about) - - [Status](#status) - - [Installation](#installation) - - [Dependencies](#dependencies) - - [Usage examples](#usage-examples) - - [How it works](#how-it-works) - - [Restrictions & behavior controls](#restrictions--behavior-controls) - - [HDPI support](#hdpi-support) - - [SVG conversion](#svg-conversion) - - [Supported shape types](#supported-shape-types) - - [Group](#group) - - [Definition group](#definition-group) - - [Circle](#circle) - - [Rect](#rect) - - [Arc](#arc) - - [Line](#line) - - [Horizontal Line](#horizontal-line) - - [Vertical Line](#vertical-line) - - [Polyline / Polygon](#polyline--polygon) - - [Path](#path) - - [Points](#points) - - [Text](#text) - - [Image](#image) - - [Gradients](#gradients) - - [Attributes](#attributes) - - [Coordinate transformations](#coordinate-transformations) - - [Transform matrix](#transform-matrix) - - [Translation](#translation) - - [Scaling](#scaling) - - [Rotation](#rotation) - - [Authors](#authors) - - [License](#license) + + +- [About](#about) + - [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) +- [How it works](#how-it-works) + - [Restrictions & behavior controls](#restrictions--behavior-controls) + - [HDPI support](#hdpi-support) +- [SVG conversion](#svg-conversion) +- [Supported shape types](#supported-shape-types) + - [Group](#group) + - [Definition group](#definition-group) + - [Circle](#circle) + - [Ellipse](#ellipse) + - [Rect](#rect) + - [Arc](#arc) + - [Line](#line) + - [Horizontal Line](#horizontal-line) + - [Vertical Line](#vertical-line) + - [Polyline / Polygon](#polyline--polygon) + - [Path](#path) + - [SVG paths with arc segments](#svg-paths-with-arc-segments) + - [Points](#points) + - [Text](#text) + - [Image](#image) + - [Gradients](#gradients) +- [Attributes](#attributes) +- [Coordinate transformations](#coordinate-transformations) + - [Transform matrix](#transform-matrix) + - [Translation](#translation) + - [Scaling](#scaling) + - [Rotation](#rotation) +- [Authors](#authors) +- [License](#license) @@ -94,6 +95,31 @@ start(() => { }); ``` +Usage with +[@thi.ng/geom3](https://github.com/thi-ng/umbrella/tree/master/packages/geom3) +shape primitives: + +```ts +import { start } from "@thi.ng/hdom"; +import { canvas } from "@thi.ng/hdom-canvas"; +import * as g from "@thi.ng/geom3"; + +start(() => { + const t = Date.now() * 0.001; + return [canvas, { width: 100, height: 100 }, + g.group( + { translate: [50,50], fill: "none" }, + g.withAttribs( + g.asPolygon(g.circle(50), 6), + { rotate: (Date.now() * 0.01) % (Math.PI * 2), stroke: "red" } + ), + g.star(25 + 25 * Math.sin(t), 6, [0.5, 1], { stroke: "blue" }), + ) + ]; +}); + +``` + ## How it works The package provides a `canvas` component which uses the branch-local @@ -105,8 +131,11 @@ component, but are then translated into canvas API draw commands during the hdom update process. Any embedded shape component functions receive the user context object as first arg, just like normal hdom components. -Shape components are expressed in standard hiccup syntax, however with -the following... +Shape components are expressed in standard hiccup syntax (or as objects +implementing the `IToHiccup()` interface, like the shape types provided +by +[@thi.ng/geom3](https://github.com/thi-ng/umbrella/tree/master/packages/geom3)), +and with the following... ### Restrictions & behavior controls @@ -236,6 +265,12 @@ used, should always come first in a scene tree. ["circle", attribs, [x, y], radius] ``` +### Ellipse + +```ts +["ellipse", attribs, [x, y], [rx,ry], axisTheta?, start?, end?, ccw?] +``` + ### Rect ```ts @@ -251,6 +286,9 @@ clamped to `Math.min(w, h)/2`. ["arc", attribs, [x, y], radius, startAngle, endAngle, anticlockwise?] ``` +Only circular arcs are supported in this format. Please see [note about +differences to SVG](#svg-paths-with-arc-segments). + ### Line ```ts @@ -305,6 +343,27 @@ relative to the end point of the previous segment. | `["A", [x1,y1], [x2, y2], r]` | Arc | | `["Z"]` | Close (sub)path | +#### SVG paths with arc segments + +**IMPORTANT:** Due to differences between SVG and canvas API arc +handling, SVG paths containing arc segments are **NOT** compatible with +the above format. To draw such paths reliably, these should first be +converted to use cubics. E.g. here using +[@thi.ng/geom3](https://github.com/thi-ng/umbrella/tree/master/packages/geom3): + +```ts +import { normalizedPath, pathFromSVG } from "@thi.ng/geom3"; + +// path w/ ac segments +const a = pathFromSvg("M0,0H80A20,20,0,0,1,100,20V30A20,20,0,0,1,80,50")[0]; + +// normalized to only use cubic curves +const b = normalizedPath(a); + +asSvg(b); +// +``` + ### Points ```ts diff --git a/packages/hdom-canvas/src/index.ts b/packages/hdom-canvas/src/index.ts index 3be1838b54..bfb4f6e495 100644 --- a/packages/hdom-canvas/src/index.ts +++ b/packages/hdom-canvas/src/index.ts @@ -275,6 +275,9 @@ const walk = case "circle": circularArc(ctx, attribs, shape[2], shape[3]); break; + case "ellipse": + ellipticArc(ctx, attribs, shape[2], shape[3], shape[4], shape[5], shape[6]); + break; case "arc": circularArc(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); break; @@ -505,29 +508,31 @@ const path = ( ctx.lineTo(b[0], b[1]); a = b; break; - // horizontal line + // horizontal line rel case "h": b = [a[0] + b, a[1]]; ctx.lineTo(b[0], b[1]); a = b; break; + // horizontal line abs case "H": b = [b, a[1]]; ctx.lineTo(b[0], b[1]); a = b; break; - // vertical line + // vertical line rel case "v": b = [a[0], a[1] + b]; ctx.lineTo(b[0], b[1]); a = b; break; + // vertical line abs case "V": b = [a[0], b]; ctx.lineTo(b[0], b[1]); a = b; break; - // cubic / bezier curve to + // cubic curve rel case "c": c = s[2]; d = s[3]; @@ -539,6 +544,7 @@ const path = ( ); a = d; break; + // cubic curve abs case "C": c = s[2]; d = s[3]; @@ -549,7 +555,7 @@ const path = ( ); a = d; break; - // quadratic curve to + // quadratic curve rel case "q": c = s[2]; c = [a[0] + c[0], a[1] + c[1]]; @@ -559,6 +565,7 @@ const path = ( ); a = c; break; + // quadratic curve abs case "Q": c = s[2]; ctx.quadraticCurveTo( @@ -567,7 +574,8 @@ const path = ( ); a = c; break; - // arc to + // circular arc rel + // Note: NOT compatible w/ SVG arc segments case "a": c = s[2]; c = [a[0] + c[0], a[1] + c[1]]; @@ -578,6 +586,8 @@ const path = ( ); a = c; break; + // circular arc abs + // Note: NOT compatible w/ SVG arc segments case "A": c = s[2]; ctx.arcTo( @@ -611,6 +621,21 @@ const circularArc = ( endShape(ctx, attribs); }; +const ellipticArc = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + r: ReadonlyVec, + axis = 0, + start = 0, + end = TAU, + ccw = false +) => { + ctx.beginPath(); + ctx.ellipse(pos[0], pos[1], r[0], r[1], axis, start, end, ccw); + endShape(ctx, attribs); +}; + const rect = (ctx: CanvasRenderingContext2D, attribs: IObjectOf, pos: ReadonlyVec,