From 8d85dd134ec26a7dbf7501ea8c9ae4a520b2d631 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Mon, 1 Jun 2020 11:16:52 +0100 Subject: [PATCH 01/25] docs(transducers): add AUTHORS.md, update readme --- packages/transducers/AUTHORS.md | 7 +++++++ packages/transducers/README.md | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 packages/transducers/AUTHORS.md diff --git a/packages/transducers/AUTHORS.md b/packages/transducers/AUTHORS.md new file mode 100644 index 0000000000..b6e68b2559 --- /dev/null +++ b/packages/transducers/AUTHORS.md @@ -0,0 +1,7 @@ +### Maintainer + +- Karsten Schmidt (@postspectacular) + +### Contributors + +- Gavin Cannizzaro (@gavinpc-mindgrub) diff --git a/packages/transducers/README.md b/packages/transducers/README.md index 338cbbb6d3..82494c2240 100644 --- a/packages/transducers/README.md +++ b/packages/transducers/README.md @@ -53,6 +53,8 @@ This project is part of the - [Generators / Iterators](#generators---iterators) - [Reducers](#reducers) - [Authors](#authors) + - [Maintainer](#maintainer) + - [Contributors](#contributors) - [License](#license) ## About @@ -948,7 +950,13 @@ and return a reduced result (as if it would be called via `reduce()`). ## Authors -Karsten Schmidt +### Maintainer + +- Karsten Schmidt ([@postspectacular](https://github.com/postspectacular)) + +### Contributors + +- Gavin Cannizzaro ([@gavinpc-mindgrub](https://github.com/gavinpc-mindgrub)) ## License From a8e8261b5a9b8fc98efaf3c6f7973d48348bd940 Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Mon, 1 Jun 2020 23:28:36 +0200 Subject: [PATCH 02/25] docs: fix typos --- CONTRIBUTING.md | 2 +- examples/async-effect/src/index.ts | 2 +- packages/dsp/README.md | 2 +- packages/dsp/tpl.readme.md | 2 +- packages/interceptors/README.md | 2 +- packages/interceptors/tpl.readme.md | 2 +- packages/iterators/README.md | 2 +- packages/iterators/tpl.readme.md | 2 +- packages/unionstruct/tpl.readme.md | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8bb9ce33ba..41eef4e6ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -182,7 +182,7 @@ git-flow feature start my-feature git commit -am 'feat(module): description' ``` -**Please do use the [Convential Commits +**Please do use the [Conventional Commits convention](https://conventionalcommits.org/) for your commit messages.** This format is used to generate changelogs and ensures consistency and better filtering. Since this is a mono repository the diff --git a/examples/async-effect/src/index.ts b/examples/async-effect/src/index.ts index 10a40b7b12..55f22c598d 100644 --- a/examples/async-effect/src/index.ts +++ b/examples/async-effect/src/index.ts @@ -52,7 +52,7 @@ const events: IObjectOf = { () => ({ [FX_DISPATCH_NOW]: [ EV_SET_STATUS, - ["success", "JSON succesfully loaded"] + ["success", "JSON successfully loaded"] ], [FX_DISPATCH_ASYNC]: [ FX_DELAY, diff --git a/packages/dsp/README.md b/packages/dsp/README.md index 8b57c31ffa..28e97af35e 100644 --- a/packages/dsp/README.md +++ b/packages/dsp/README.md @@ -246,7 +246,7 @@ Diagram of the FM/AM osc with some low pass filters applied: The second fundamental interface in this package, similar to `IGen` and used to implement processors & transformers of input values (e.g those -generated by the various `IGen`s availabe). `IProc` implementations have a +generated by the various `IGen`s available). `IProc` implementations have a `.next(x)` method, where `x` is the next input to be processed. The package also provides several approaches to compose multi-step diff --git a/packages/dsp/tpl.readme.md b/packages/dsp/tpl.readme.md index 2712e62a94..6df99a51d2 100644 --- a/packages/dsp/tpl.readme.md +++ b/packages/dsp/tpl.readme.md @@ -196,7 +196,7 @@ Diagram of the FM/AM osc with some low pass filters applied: The second fundamental interface in this package, similar to `IGen` and used to implement processors & transformers of input values (e.g those -generated by the various `IGen`s availabe). `IProc` implementations have a +generated by the various `IGen`s available). `IProc` implementations have a `.next(x)` method, where `x` is the next input to be processed. The package also provides several approaches to compose multi-step diff --git a/packages/interceptors/README.md b/packages/interceptors/README.md index 1b6116717f..0f44ec824f 100644 --- a/packages/interceptors/README.md +++ b/packages/interceptors/README.md @@ -104,7 +104,7 @@ actions any component can take. So assigning them to registered side effects enables better code reuse. Another use-case is debugging. With a break point set at the beginning of `processEffects()` (in [`event-bus.ts`](https://github.com/thi-ng/umbrella/blob/develop/packages/interceptors/src/event-bus.ts#L36)) -you can see exactly which side effects have occured at each frame. This +you can see exactly which side effects have occurred at each frame. This can be very helpful for debugging and avoid having to "keep everything in your head" or - as Rich Hickey would say - make your app "Easier to reason about". diff --git a/packages/interceptors/tpl.readme.md b/packages/interceptors/tpl.readme.md index e64aaf8fce..8a871bc6f9 100644 --- a/packages/interceptors/tpl.readme.md +++ b/packages/interceptors/tpl.readme.md @@ -88,7 +88,7 @@ actions any component can take. So assigning them to registered side effects enables better code reuse. Another use-case is debugging. With a break point set at the beginning of `processEffects()` (in [`event-bus.ts`](https://github.com/thi-ng/umbrella/blob/develop/packages/interceptors/src/event-bus.ts#L36)) -you can see exactly which side effects have occured at each frame. This +you can see exactly which side effects have occurred at each frame. This can be very helpful for debugging and avoid having to "keep everything in your head" or - as Rich Hickey would say - make your app "Easier to reason about". diff --git a/packages/iterators/README.md b/packages/iterators/README.md index 2ce7b19d8b..9e37c030be 100644 --- a/packages/iterators/README.md +++ b/packages/iterators/README.md @@ -615,7 +615,7 @@ inputs is exhausted. Signature: `interpose(x: any, input: Iterable) => IterableIterator` -Produces an iterator which injects `x` inbetween each item from input +Produces an iterator which injects `x` between each item from input `iter`. ```ts diff --git a/packages/iterators/tpl.readme.md b/packages/iterators/tpl.readme.md index 17b7368707..a134d0d002 100644 --- a/packages/iterators/tpl.readme.md +++ b/packages/iterators/tpl.readme.md @@ -548,7 +548,7 @@ inputs is exhausted. Signature: `interpose(x: any, input: Iterable) => IterableIterator` -Produces an iterator which injects `x` inbetween each item from input +Produces an iterator which injects `x` between each item from input `iter`. ```ts diff --git a/packages/unionstruct/tpl.readme.md b/packages/unionstruct/tpl.readme.md index 353a233dbe..022c49031f 100644 --- a/packages/unionstruct/tpl.readme.md +++ b/packages/unionstruct/tpl.readme.md @@ -20,7 +20,7 @@ Features: - Packed bitfields (signed / unsigned) - Auto-alignment of fields to respective word boundaries (can be disabled) -- Configurable endianess (bitfields currently assume network order / big +- Configurable endianness (bitfields currently assume network order / big endian) - No runtime dependencies, works in node & browser - Small: 2.35KB minified, 1.14KB gzipped From 902be699c9a9c9eed3a80c37993c640394d7eb2b Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 22:21:53 +0000 Subject: [PATCH 03/25] docs: update README.md [skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fc59088fac..bec56c68da 100644 --- a/README.md +++ b/README.md @@ -367,6 +367,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Nik Shevchenko

🐛 💻
Matei Adriel

💻 🐛 🤔 +
Pierre Grimaud

📖 From 9b1f32af6cfd0f42764dd4a2d91b474c80cfe7d5 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 22:21:54 +0000 Subject: [PATCH 04/25] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 2f6cd53282..657956c038 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -170,6 +170,15 @@ "bug", "ideas" ] + }, + { + "login": "pgrimaud", + "name": "Pierre Grimaud", + "avatar_url": "https://avatars1.githubusercontent.com/u/1866496?v=4", + "profile": "https://github.com/pgrimaud", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, From 8d869e6f53e41ac9658747d649fe14b556635579 Mon Sep 17 00:00:00 2001 From: Alberto Date: Thu, 4 Jun 2020 20:00:04 +0200 Subject: [PATCH 05/25] docs(hdom-canvas): fix example with group --- packages/hdom-canvas/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hdom-canvas/README.md b/packages/hdom-canvas/README.md index 17da3b1be6..ab0687f1cd 100644 --- a/packages/hdom-canvas/README.md +++ b/packages/hdom-canvas/README.md @@ -159,6 +159,7 @@ 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), @@ -166,7 +167,6 @@ start(() => { ), g.star(25 + 25 * Math.sin(t), 6, [0.5, 1], { stroke: "blue" }), ], - { translate: [50, 50], fill: "none" } ) ]; }); From 04117f3c610a7d142e86fc9bc778749c78c43ce0 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 10:26:40 +0100 Subject: [PATCH 06/25] feat(examples): add hdom-canvas-arcs demo --- examples/hdom-canvas-arcs/.gitignore | 8 +++ examples/hdom-canvas-arcs/README.md | 13 +++++ examples/hdom-canvas-arcs/index.html | 17 +++++++ examples/hdom-canvas-arcs/package.json | 39 ++++++++++++++ examples/hdom-canvas-arcs/src/index.ts | 56 +++++++++++++++++++++ examples/hdom-canvas-arcs/src/webpack.d.ts | 4 ++ examples/hdom-canvas-arcs/tsconfig.json | 11 ++++ examples/hdom-canvas-arcs/webpack.config.js | 23 +++++++++ 8 files changed, 171 insertions(+) create mode 100644 examples/hdom-canvas-arcs/.gitignore create mode 100644 examples/hdom-canvas-arcs/README.md create mode 100644 examples/hdom-canvas-arcs/index.html create mode 100644 examples/hdom-canvas-arcs/package.json create mode 100644 examples/hdom-canvas-arcs/src/index.ts create mode 100644 examples/hdom-canvas-arcs/src/webpack.d.ts create mode 100644 examples/hdom-canvas-arcs/tsconfig.json create mode 100644 examples/hdom-canvas-arcs/webpack.config.js diff --git a/examples/hdom-canvas-arcs/.gitignore b/examples/hdom-canvas-arcs/.gitignore new file mode 100644 index 0000000000..5d62218c54 --- /dev/null +++ b/examples/hdom-canvas-arcs/.gitignore @@ -0,0 +1,8 @@ +.cache +out +node_modules +yarn.lock +*.js +*.map +!src/*.d.ts +!*.config.js diff --git a/examples/hdom-canvas-arcs/README.md b/examples/hdom-canvas-arcs/README.md new file mode 100644 index 0000000000..96dd987e78 --- /dev/null +++ b/examples/hdom-canvas-arcs/README.md @@ -0,0 +1,13 @@ +# hdom-canvas-arcs + +[Live demo](http://demo.thi.ng/umbrella/hdom-canvas-arcs/) + +Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. + +## Authors + +- Karsten Schmidt + +## License + +© 2020 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/hdom-canvas-arcs/index.html b/examples/hdom-canvas-arcs/index.html new file mode 100644 index 0000000000..e7f224f76d --- /dev/null +++ b/examples/hdom-canvas-arcs/index.html @@ -0,0 +1,17 @@ + + + + + + + hdom-canvas-arcs + + + + +
+ + + + diff --git a/examples/hdom-canvas-arcs/package.json b/examples/hdom-canvas-arcs/package.json new file mode 100644 index 0000000000..38499682dd --- /dev/null +++ b/examples/hdom-canvas-arcs/package.json @@ -0,0 +1,39 @@ +{ + "name": "hdom-canvas-arcs", + "version": "0.0.1", + "description": "Animated arcs & drawing using hdom-canvas without hdom", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "clean": "rm -rf .cache build out", + "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report --experimental-scope-hoisting", + "build:webpack": "../../node_modules/.bin/webpack --mode production", + "start": "parcel index.html -p 8080 --open" + }, + "devDependencies": { + "parcel-bundler": "^1.12.4", + "terser": "^4.6.3", + "typescript": "^3.7.5" + }, + "dependencies": { + "@thi.ng/geom": "latest", + "@thi.ng/hdom": "latest", + "@thi.ng/hdom-canvas": "latest", + "@thi.ng/math": "latest", + "@thi.ng/rstream": "latest", + "@thi.ng/transducers": "latest" + }, + "browserslist": [ + "last 3 Chrome versions" + ], + "browser": { + "process": false + }, + "thi.ng": { + "readme": [ + "geom", + "hdom-canvas" + ] + } +} diff --git a/examples/hdom-canvas-arcs/src/index.ts b/examples/hdom-canvas-arcs/src/index.ts new file mode 100644 index 0000000000..9dae03f5ad --- /dev/null +++ b/examples/hdom-canvas-arcs/src/index.ts @@ -0,0 +1,56 @@ +import { arc, asCubic, group, pathFromCubics } from "@thi.ng/geom"; +import { createElement } from "@thi.ng/hdom"; +import { walk } from "@thi.ng/hdom-canvas"; +import { fit01, TAU } from "@thi.ng/math"; +import { fromRAF } from "@thi.ng/rstream"; +import { map, normRange } from "@thi.ng/transducers"; + +const canvas = createElement(document.body, "canvas", { + width: 600, + height: 600, +}); +const ctx = canvas.getContext("2d")!; + +// generate random arc configurations +const arcs = [ + ...map( + (i) => ({ + r: fit01(i, 50, 200), + theta: Math.random() * TAU, + spread: Math.random() * TAU, + speed: (Math.random() * 2 - 1) * 0.1, + }), + normRange(10) + ), +]; + +fromRAF().subscribe({ + next() { + // update arc start angles + arcs.forEach((a) => (a.theta += a.speed)); + ctx.clearRect(0, 0, canvas.width, canvas.height); + // build arcs, convert them to cubic paths + // then group and convert to hiccup tree for drawing + // with hdom-canvas' standalone tree traversal & rendering feature + + // NOTE: this would be MUCH easier, if the HTML Canvas API would actually + // support elliptic arcs and not just circular ones. IMHO it's a huge + // oversight in terms of web standards that the canvas API is not more + // compatible with SVG conventions/features... + walk( + ctx, + group( + { stroke: "red" }, + arcs.map(({ r, theta, spread }) => + pathFromCubics( + asCubic(arc([300, 300], r, 0, theta, theta + spread)) + ) + ) + ).toHiccup(), + { + attribs: {}, + edits: [], + } + ); + }, +}); diff --git a/examples/hdom-canvas-arcs/src/webpack.d.ts b/examples/hdom-canvas-arcs/src/webpack.d.ts new file mode 100644 index 0000000000..2966449833 --- /dev/null +++ b/examples/hdom-canvas-arcs/src/webpack.d.ts @@ -0,0 +1,4 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; +declare module "*.json"; diff --git a/examples/hdom-canvas-arcs/tsconfig.json b/examples/hdom-canvas-arcs/tsconfig.json new file mode 100644 index 0000000000..bbf112cc18 --- /dev/null +++ b/examples/hdom-canvas-arcs/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "target": "es6", + "sourceMap": true + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/examples/hdom-canvas-arcs/webpack.config.js b/examples/hdom-canvas-arcs/webpack.config.js new file mode 100644 index 0000000000..bf16021356 --- /dev/null +++ b/examples/hdom-canvas-arcs/webpack.config.js @@ -0,0 +1,23 @@ +module.exports = { + entry: "./src/index.ts", + output: { + filename: "bundle.[hash].js", + path: __dirname + "/out" + }, + resolve: { + extensions: [".ts", ".js"] + }, + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: "file-loader", + options: { name: "[path][hash].[ext]" } + }, + { test: /\.ts$/, use: "ts-loader" } + ] + }, + node: { + process: false + } +}; From 51c47c5e0590644f3ad112829508e3955c45f6b9 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 11:12:05 +0100 Subject: [PATCH 07/25] feat(examples): add shape picking (arcs demo) --- examples/hdom-canvas-arcs/index.html | 36 +++++++---- examples/hdom-canvas-arcs/src/index.ts | 88 +++++++++++++++++++------- 2 files changed, 86 insertions(+), 38 deletions(-) diff --git a/examples/hdom-canvas-arcs/index.html b/examples/hdom-canvas-arcs/index.html index e7f224f76d..2eaadf190c 100644 --- a/examples/hdom-canvas-arcs/index.html +++ b/examples/hdom-canvas-arcs/index.html @@ -1,17 +1,25 @@ - - - - - hdom-canvas-arcs - - - - -
- - - + + + + + hdom-canvas-arcs + + + + +
+ + + diff --git a/examples/hdom-canvas-arcs/src/index.ts b/examples/hdom-canvas-arcs/src/index.ts index 9dae03f5ad..b08ef0a1b9 100644 --- a/examples/hdom-canvas-arcs/src/index.ts +++ b/examples/hdom-canvas-arcs/src/index.ts @@ -1,51 +1,91 @@ -import { arc, asCubic, group, pathFromCubics } from "@thi.ng/geom"; +import { + arc, + asCubic, + group, + pathFromCubics, + closestPoint, +} from "@thi.ng/geom"; import { createElement } from "@thi.ng/hdom"; import { walk } from "@thi.ng/hdom-canvas"; +import { hsla } from "@thi.ng/color"; +import { SYSTEM } from "@thi.ng/random"; import { fit01, TAU } from "@thi.ng/math"; -import { fromRAF } from "@thi.ng/rstream"; +import { fromRAF, fromDOMEvent } from "@thi.ng/rstream"; import { map, normRange } from "@thi.ng/transducers"; +import { dist } from "@thi.ng/vectors"; + +const W = 600; +const ORIGIN = [W / 2, W / 2]; + +const PICK_DIST = 10; +const PICK_COL = "cyan"; const canvas = createElement(document.body, "canvas", { - width: 600, - height: 600, + width: W, + height: W, }); + const ctx = canvas.getContext("2d")!; // generate random arc configurations const arcs = [ ...map( (i) => ({ - r: fit01(i, 50, 200), - theta: Math.random() * TAU, - spread: Math.random() * TAU, - speed: (Math.random() * 2 - 1) * 0.1, + // radius + r: fit01(i, 50, W * 0.4), + // stroke width + w: SYSTEM.minmax(1, 5), + // randomized HSLA color + col: hsla([SYSTEM.norm(0.1), SYSTEM.minmax(0.5, 1), 0.5]), + // start angle + theta: SYSTEM.float(TAU), + // angle spread + spread: SYSTEM.float(TAU), + // rotation speed + speed: SYSTEM.norm(0.02), }), - normRange(10) + normRange(20) ), ]; +// mouse position stream +const mouse = fromDOMEvent(canvas, "mousemove").transform( + map((e) => { + const b = canvas.getBoundingClientRect(); + return [e.clientX - b.left, e.clientY - b.top]; + }) +); + +// 60Hz update loop fromRAF().subscribe({ next() { - // update arc start angles + // update rotations arcs.forEach((a) => (a.theta += a.speed)); + // clear viewport ctx.clearRect(0, 0, canvas.width, canvas.height); - // build arcs, convert them to cubic paths - // then group and convert to hiccup tree for drawing - // with hdom-canvas' standalone tree traversal & rendering feature - - // NOTE: this would be MUCH easier, if the HTML Canvas API would actually - // support elliptic arcs and not just circular ones. IMHO it's a huge - // oversight in terms of web standards that the canvas API is not more - // compatible with SVG conventions/features... + // get mouse pos + const m = mouse.deref(); walk( ctx, + // group arcs and convert to hiccup tree required by `walk()` + // (see hdom-canvas readme for details) group( - { stroke: "red" }, - arcs.map(({ r, theta, spread }) => - pathFromCubics( - asCubic(arc([300, 300], r, 0, theta, theta + spread)) - ) - ) + {}, + arcs.map(({ r, w, col, theta, spread }) => { + // build (elliptic) arc from config + const a = arc(ORIGIN, r, 0, theta, theta + spread); + // convert to cubic path due to HTML Canvas API limitations + // (doesn't support elliptic arcs, so we need to convert them...) + // also perform shape picking by computing distance to + // closest point on arc to mouse pos. adjust color based on result + return pathFromCubics(asCubic(a), { + weight: w, + stroke: + m && dist(m, closestPoint(a, m)!) < PICK_DIST + ? PICK_COL + : col, + }); + }) ).toHiccup(), { attribs: {}, From 4b3c516573dc9cb247dedc211210151575709925 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 13:18:36 +0100 Subject: [PATCH 08/25] feat(hiccup-canvas): extract as new package BREAKING CHANGE: extract as new package from former @thi.ng/hdom-canvas --- packages/hiccup-canvas/LICENSE | 201 ++++++++++++++++++++ packages/hiccup-canvas/api-extractor.json | 3 + packages/hiccup-canvas/package.json | 76 ++++++++ packages/hiccup-canvas/src/api.ts | 8 + packages/hiccup-canvas/src/arc.ts | 33 ++++ packages/hiccup-canvas/src/color.ts | 36 ++++ packages/hiccup-canvas/src/draw.ts | 123 ++++++++++++ packages/hiccup-canvas/src/end-shape.ts | 18 ++ packages/hiccup-canvas/src/image.ts | 28 +++ packages/hiccup-canvas/src/index.ts | 15 ++ packages/hiccup-canvas/src/line.ts | 15 ++ packages/hiccup-canvas/src/packed-points.ts | 49 +++++ packages/hiccup-canvas/src/path.ts | 113 +++++++++++ packages/hiccup-canvas/src/points.ts | 40 ++++ packages/hiccup-canvas/src/polygon.ts | 33 ++++ packages/hiccup-canvas/src/polyline.ts | 13 ++ packages/hiccup-canvas/src/rect.ts | 36 ++++ packages/hiccup-canvas/src/state.ts | 164 ++++++++++++++++ packages/hiccup-canvas/src/text.ts | 18 ++ packages/hiccup-canvas/test/index.ts | 6 + packages/hiccup-canvas/test/tsconfig.json | 11 ++ packages/hiccup-canvas/tsconfig.json | 11 ++ 22 files changed, 1050 insertions(+) create mode 100644 packages/hiccup-canvas/LICENSE create mode 100644 packages/hiccup-canvas/api-extractor.json create mode 100644 packages/hiccup-canvas/package.json create mode 100644 packages/hiccup-canvas/src/api.ts create mode 100644 packages/hiccup-canvas/src/arc.ts create mode 100644 packages/hiccup-canvas/src/color.ts create mode 100644 packages/hiccup-canvas/src/draw.ts create mode 100644 packages/hiccup-canvas/src/end-shape.ts create mode 100644 packages/hiccup-canvas/src/image.ts create mode 100644 packages/hiccup-canvas/src/index.ts create mode 100644 packages/hiccup-canvas/src/line.ts create mode 100644 packages/hiccup-canvas/src/packed-points.ts create mode 100644 packages/hiccup-canvas/src/path.ts create mode 100644 packages/hiccup-canvas/src/points.ts create mode 100644 packages/hiccup-canvas/src/polygon.ts create mode 100644 packages/hiccup-canvas/src/polyline.ts create mode 100644 packages/hiccup-canvas/src/rect.ts create mode 100644 packages/hiccup-canvas/src/state.ts create mode 100644 packages/hiccup-canvas/src/text.ts create mode 100644 packages/hiccup-canvas/test/index.ts create mode 100644 packages/hiccup-canvas/test/tsconfig.json create mode 100644 packages/hiccup-canvas/tsconfig.json diff --git a/packages/hiccup-canvas/LICENSE b/packages/hiccup-canvas/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/hiccup-canvas/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/hiccup-canvas/api-extractor.json b/packages/hiccup-canvas/api-extractor.json new file mode 100644 index 0000000000..94972e6bed --- /dev/null +++ b/packages/hiccup-canvas/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "../../api-extractor.json" +} diff --git a/packages/hiccup-canvas/package.json b/packages/hiccup-canvas/package.json new file mode 100644 index 0000000000..465a2bedf9 --- /dev/null +++ b/packages/hiccup-canvas/package.json @@ -0,0 +1,76 @@ +{ + "name": "@thi.ng/hiccup-canvas", + "version": "0.0.1", + "description": "Hiccup shape tree renderer for vanilla Canvas 2D contexts", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/hiccup-canvas#readme", + "funding": { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + }, + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module", + "build:release": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module all", + "build:es6": "tsc --declaration", + "build:test": "rimraf build && tsc -p test/tsconfig.json", + "test": "mocha test", + "cover": "nyc mocha test && nyc report --reporter=lcov", + "clean": "rimraf *.js *.d.ts *.map .nyc_output build coverage doc lib", + "doc:readme": "ts-node -P ../../tools/tsconfig.json ../../tools/src/readme.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && node_modules/.bin/api-extractor run --local --verbose", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn build:release && yarn publish --access public" + }, + "devDependencies": { + "@istanbuljs/nyc-config-typescript": "^1.0.1", + "@microsoft/api-extractor": "^7.8.0", + "@types/mocha": "^7.0.2", + "@types/node": "^14.0.1", + "mocha": "^7.1.2", + "nyc": "^15.0.1", + "ts-node": "^8.10.1", + "typedoc": "^0.17.6", + "typescript": "^3.9.2" + }, + "dependencies": { + "@thi.ng/api": "^6.11.0", + "@thi.ng/checks": "^2.7.0", + "@thi.ng/color": "^1.2.2", + "@thi.ng/math": "^1.7.10", + "@thi.ng/vectors": "^4.4.3" + }, + "files": [ + "*.js", + "*.d.ts", + "lib" + ], + "keywords": [ + "2d", + "canvas", + "declarative", + "draw", + "ES6", + "hiccup", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false, + "thi.ng": { + "related": [ + "hdom-canvas", + "hiccup" + ], + "year": 2018 + } +} diff --git a/packages/hiccup-canvas/src/api.ts b/packages/hiccup-canvas/src/api.ts new file mode 100644 index 0000000000..cd4e797bff --- /dev/null +++ b/packages/hiccup-canvas/src/api.ts @@ -0,0 +1,8 @@ +import type { IObjectOf } from "@thi.ng/api"; + +export interface DrawState { + attribs: IObjectOf; + grads?: IObjectOf; + edits: string[]; + restore?: boolean; +} diff --git a/packages/hiccup-canvas/src/arc.ts b/packages/hiccup-canvas/src/arc.ts new file mode 100644 index 0000000000..8bbd94f52a --- /dev/null +++ b/packages/hiccup-canvas/src/arc.ts @@ -0,0 +1,33 @@ +import type { IObjectOf } from "@thi.ng/api"; +import { TAU } from "@thi.ng/math"; +import type { ReadonlyVec } from "@thi.ng/vectors"; +import { endShape } from "./end-shape"; + +export const circularArc = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + r: number, + start = 0, + end = TAU, + antiCCW = false +) => { + ctx.beginPath(); + ctx.arc(pos[0], pos[1], r, start, end, antiCCW); + endShape(ctx, attribs); +}; + +export 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); +}; diff --git a/packages/hiccup-canvas/src/color.ts b/packages/hiccup-canvas/src/color.ts new file mode 100644 index 0000000000..118b75f655 --- /dev/null +++ b/packages/hiccup-canvas/src/color.ts @@ -0,0 +1,36 @@ +import { isString } from "@thi.ng/checks"; +import { resolveAsCSS } from "@thi.ng/color"; +import type { DrawState } from "./api"; + +export const resolveColor = (v: any) => (isString(v) ? v : resolveAsCSS(v)); + +export const resolveGradientOrColor = (state: DrawState, v: any) => + isString(v) + ? v[0] === "$" + ? state.grads![v.substr(1)] + : v + : resolveAsCSS(v); + +export const defLinearGradient = ( + ctx: CanvasRenderingContext2D, + { from, to }: any, + stops: any[][] +) => { + const g = ctx.createLinearGradient(from[0], from[1], to[0], to[1]); + for (let s of stops) { + g.addColorStop(s[0], resolveColor(s[1])); + } + return g; +}; + +export const defRadialGradient = ( + ctx: CanvasRenderingContext2D, + { from, to, r1, r2 }: any, + stops: any[][] +) => { + const g = ctx.createRadialGradient(from[0], from[1], r1, to[0], to[1], r2); + for (let s of stops) { + g.addColorStop(s[0], resolveColor(s[1])); + } + return g; +}; diff --git a/packages/hiccup-canvas/src/draw.ts b/packages/hiccup-canvas/src/draw.ts new file mode 100644 index 0000000000..53747f4ca6 --- /dev/null +++ b/packages/hiccup-canvas/src/draw.ts @@ -0,0 +1,123 @@ +import { isArray } from "@thi.ng/checks"; +import type { DrawState } from "./api"; +import { circularArc, ellipticArc } from "./arc"; +import { defLinearGradient, defRadialGradient } from "./color"; +import { image } from "./image"; +import { line } from "./line"; +import { packedPoints } from "./packed-points"; +import { path } from "./path"; +import { points } from "./points"; +import { polygon } from "./polygon"; +import { polyline } from "./polyline"; +import { rect } from "./rect"; +import { mergeState, registerGradient, restoreState } from "./state"; +import { text } from "./text"; + +export const draw = ( + ctx: CanvasRenderingContext2D, + shape: any[], + pstate: DrawState = { attribs: {}, edits: [] } +) => { + if (!shape) return; + if (isArray(shape[0])) { + for (let s of shape) { + draw(ctx, s, pstate); + } + return; + } + const state = mergeState(ctx, pstate, shape[1]); + const attribs = state ? state.attribs : pstate.attribs; + if (attribs.__skip) return; + switch (shape[0]) { + case "g": + case "defs": + defs(ctx, state, pstate, shape); + break; + case "linearGradient": + registerGradient( + pstate, + shape[1].id, + defLinearGradient(ctx, shape[1], shape[2]) + ); + break; + case "radialGradient": + registerGradient( + pstate, + shape[1].id, + defRadialGradient(ctx, shape[1], shape[2]) + ); + break; + case "points": + points(ctx, attribs, shape[1], shape[2]); + break; + case "packedPoints": + packedPoints(ctx, attribs, shape[1], shape[2]); + break; + case "line": + line(ctx, attribs, shape[2], shape[3]); + break; + case "hline": + line(ctx, attribs, [-1e6, shape[2]], [1e6, shape[2]]); + break; + case "vline": + line(ctx, attribs, [shape[2], -1e6], [shape[2], 1e6]); + break; + case "polyline": + polyline(ctx, attribs, shape[2]); + break; + case "polygon": + polygon(ctx, attribs, shape[2]); + break; + case "path": + path(ctx, attribs, shape[2]); + break; + case "rect": + rect(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); + break; + 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; + case "text": + text(ctx, attribs, shape[2], shape[3], shape[4]); + break; + case "img": + image( + ctx, + attribs, + shape[1], + shape[2], + shape[3], + shape[4], + shape[5] + ); + default: + } + state && restoreState(ctx, pstate, state); +}; + +const defs = ( + ctx: CanvasRenderingContext2D, + state: DrawState | undefined, + pstate: DrawState, + shape: any[] +) => { + const n = shape.length; + const __state = shape[0] === "g" ? state || pstate : pstate; + for (let i = 2; i < n; i++) { + draw(ctx, shape[i], __state); + } +}; diff --git a/packages/hiccup-canvas/src/end-shape.ts b/packages/hiccup-canvas/src/end-shape.ts new file mode 100644 index 0000000000..ee3ec128e0 --- /dev/null +++ b/packages/hiccup-canvas/src/end-shape.ts @@ -0,0 +1,18 @@ +import type { IObjectOf } from "@thi.ng/api"; + +/** @internal */ +export const endShape = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf +) => { + let v: any; + if ((v = attribs.fill) && v !== "none") { + ctx.fill(); + } + if ((v = attribs.stroke) && v !== "none") { + ctx.stroke(); + } + if ((v = attribs.clip)) { + ctx.clip(v === true ? "nonzero" : v); + } +}; diff --git a/packages/hiccup-canvas/src/image.ts b/packages/hiccup-canvas/src/image.ts new file mode 100644 index 0000000000..53fbf8c8fc --- /dev/null +++ b/packages/hiccup-canvas/src/image.ts @@ -0,0 +1,28 @@ +import type { IObjectOf } from "@thi.ng/api"; +import type { ReadonlyVec } from "@thi.ng/vectors"; + +export const image = ( + ctx: CanvasRenderingContext2D, + _: IObjectOf, + { width, height }: IObjectOf, + img: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, + dpos: ReadonlyVec, + spos?: ReadonlyVec, + ssize?: ReadonlyVec +) => { + width = width || img.width; + height = height || img.height; + spos + ? ctx.drawImage( + img, + spos[0], + spos[1], + ssize ? ssize[0] : width, + ssize ? ssize[1] : height, + dpos[0], + dpos[1], + width, + height + ) + : ctx.drawImage(img, dpos[0], dpos[1], width, height); +}; diff --git a/packages/hiccup-canvas/src/index.ts b/packages/hiccup-canvas/src/index.ts new file mode 100644 index 0000000000..ed923bd5de --- /dev/null +++ b/packages/hiccup-canvas/src/index.ts @@ -0,0 +1,15 @@ +export * from "./api"; +export * from "./arc"; +export * from "./color"; +export * from "./draw"; +export * from "./end-shape"; +export * from "./image"; +export * from "./line"; +export * from "./packed-points"; +export * from "./path"; +export * from "./points"; +export * from "./polygon"; +export * from "./polyline"; +export * from "./rect"; +export * from "./state"; +export * from "./text"; diff --git a/packages/hiccup-canvas/src/line.ts b/packages/hiccup-canvas/src/line.ts new file mode 100644 index 0000000000..acaf349f8b --- /dev/null +++ b/packages/hiccup-canvas/src/line.ts @@ -0,0 +1,15 @@ +import type { IObjectOf } from "@thi.ng/api"; +import type { ReadonlyVec } from "@thi.ng/vectors"; + +export const line = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + a: ReadonlyVec, + b: ReadonlyVec +) => { + if (attribs.stroke === "none") return; + ctx.beginPath(); + ctx.moveTo(a[0], a[1]); + ctx.lineTo(b[0], b[1]); + ctx.stroke(); +}; diff --git a/packages/hiccup-canvas/src/packed-points.ts b/packages/hiccup-canvas/src/packed-points.ts new file mode 100644 index 0000000000..32cc8cf8a5 --- /dev/null +++ b/packages/hiccup-canvas/src/packed-points.ts @@ -0,0 +1,49 @@ +import type { IObjectOf } from "@thi.ng/api"; +import { TAU } from "@thi.ng/math"; + +export const packedPoints = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + opts: IObjectOf, + pts: ArrayLike +) => { + let v: any; + if ((v = attribs.fill) && v !== "none") { + __drawPoints(ctx, opts, pts, "fill", "fillRect"); + } + if ((v = attribs.stroke) && v !== "none") { + __drawPoints(ctx, opts, pts, "stroke", "strokeRect"); + } +}; + +const __drawPoints = ( + ctx: CanvasRenderingContext2D, + opts: IObjectOf, + pts: ArrayLike, + cmd: "fill" | "stroke", + cmdR: "fillRect" | "strokeRect" +) => { + const { start, cstride, estride, size } = { + start: 0, + cstride: 1, + estride: 2, + size: 1, + ...opts, + }; + let num = + opts && opts.num != null + ? opts.num + : ((pts.length - start) / estride) | 0; + if (opts.shape === "circle") { + for (let i = start; --num >= 0; i += estride) { + ctx.beginPath(); + ctx.arc(pts[i], pts[i + cstride], size, 0, TAU); + ctx[cmd](); + } + } else { + const r = size / 2; + for (let i = start; --num >= 0; i += estride) { + ctx[cmdR](pts[i] - r, pts[i + cstride] - r, size, size); + } + } +}; diff --git a/packages/hiccup-canvas/src/path.ts b/packages/hiccup-canvas/src/path.ts new file mode 100644 index 0000000000..1d9db72275 --- /dev/null +++ b/packages/hiccup-canvas/src/path.ts @@ -0,0 +1,113 @@ +import type { IObjectOf } from "@thi.ng/api"; +import type { ReadonlyVec } from "@thi.ng/vectors"; +import { endShape } from "./end-shape"; + +export const path = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + segments: any[] +) => { + ctx.beginPath(); + let a: ReadonlyVec = [0, 0]; + for (let i = 0, n = segments.length; i < n; i++) { + const s = segments[i]; + let b = s[1], + c, + d; + switch (s[0]) { + // move to + case "m": + b = [a[0] + b[0], a[1] + b[1]]; + case "M": + ctx.moveTo(b[0], b[1]); + a = b; + break; + // line to + case "l": + b = [a[0] + b[0], a[1] + b[1]]; + case "L": + ctx.lineTo(b[0], b[1]); + a = b; + break; + // 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 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 curve rel + case "c": + c = s[2]; + d = s[3]; + d = [a[0] + d[0], a[1] + d[1]]; + ctx.bezierCurveTo( + a[0] + b[0], + a[1] + b[1], + a[0] + c[0], + a[1] + c[1], + d[0], + d[1] + ); + a = d; + break; + // cubic curve abs + case "C": + c = s[2]; + d = s[3]; + ctx.bezierCurveTo(b[0], b[1], c[0], c[1], d[0], d[1]); + a = d; + break; + // quadratic curve rel + case "q": + c = s[2]; + c = [a[0] + c[0], a[1] + c[1]]; + ctx.quadraticCurveTo(a[0] + b[0], a[1] + b[1], c[0], c[1]); + a = c; + break; + // quadratic curve abs + case "Q": + c = s[2]; + ctx.quadraticCurveTo(b[0], b[1], c[0], c[1]); + a = c; + break; + // circular arc rel + // Note: NOT compatible w/ SVG arc segments + case "a": + c = s[2]; + c = [a[0] + c[0], a[1] + c[1]]; + ctx.arcTo(a[0] + b[0], a[1] + b[1], c[0], c[1], s[3]); + a = c; + break; + // circular arc abs + // Note: NOT compatible w/ SVG arc segments + case "A": + c = s[2]; + ctx.arcTo(b[0], b[1], c[0], c[1], s[3]); + a = c; + break; + // close path + case "z": + case "Z": + ctx.closePath(); + } + } + endShape(ctx, attribs); +}; diff --git a/packages/hiccup-canvas/src/points.ts b/packages/hiccup-canvas/src/points.ts new file mode 100644 index 0000000000..e42a5d308b --- /dev/null +++ b/packages/hiccup-canvas/src/points.ts @@ -0,0 +1,40 @@ +import type { IObjectOf } from "@thi.ng/api"; +import { TAU } from "@thi.ng/math"; +import type { ReadonlyVec } from "@thi.ng/vectors"; + +export const points = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + opts: IObjectOf, + pts: Iterable +) => { + let v: any; + if ((v = attribs.fill) && v !== "none") { + __drawPoints(ctx, opts, pts, "fill", "fillRect"); + } + if ((v = attribs.stroke) && v !== "none") { + __drawPoints(ctx, opts, pts, "stroke", "strokeRect"); + } +}; + +const __drawPoints = ( + ctx: CanvasRenderingContext2D, + opts: IObjectOf, + pts: Iterable, + cmd: "fill" | "stroke", + cmdR: "fillRect" | "strokeRect" +) => { + const s: number = (opts && opts.size) || 1; + if (opts.shape === "circle") { + for (let p of pts) { + ctx.beginPath(); + ctx.arc(p[0], p[1], s, 0, TAU); + ctx[cmd](); + } + } else { + const r = s / 2; + for (let p of pts) { + ctx[cmdR](p[0] - r, p[1] - r, s, s); + } + } +}; diff --git a/packages/hiccup-canvas/src/polygon.ts b/packages/hiccup-canvas/src/polygon.ts new file mode 100644 index 0000000000..11660701ec --- /dev/null +++ b/packages/hiccup-canvas/src/polygon.ts @@ -0,0 +1,33 @@ +import type { IObjectOf } from "@thi.ng/api"; +import type { ReadonlyVec } from "@thi.ng/vectors"; +import { endShape } from "./end-shape"; + +export const polygon = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pts: ReadonlyVec[] +) => { + if (pts.length < 2) return; + __drawPoly(ctx, pts); + ctx.closePath(); + endShape(ctx, attribs); +}; + +/** + * Shared internal helper for polygon & polyline fns. + * + * @param ctx - canvas context + * @param pts - poly vertices + */ +export const __drawPoly = ( + ctx: CanvasRenderingContext2D, + pts: ReadonlyVec[] +) => { + let p: ReadonlyVec = pts[0]; + ctx.beginPath(); + ctx.moveTo(p[0], p[1]); + for (let i = 1, n = pts.length; i < n; i++) { + p = pts[i]; + ctx.lineTo(p[0], p[1]); + } +}; diff --git a/packages/hiccup-canvas/src/polyline.ts b/packages/hiccup-canvas/src/polyline.ts new file mode 100644 index 0000000000..557b409163 --- /dev/null +++ b/packages/hiccup-canvas/src/polyline.ts @@ -0,0 +1,13 @@ +import type { IObjectOf } from "@thi.ng/api"; +import type { ReadonlyVec } from "@thi.ng/vectors"; +import { __drawPoly } from "./polygon"; + +export const polyline = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pts: ReadonlyVec[] +) => { + if (pts.length < 2 || attribs.stroke == "none") return; + __drawPoly(ctx, pts); + ctx.stroke(); +}; diff --git a/packages/hiccup-canvas/src/rect.ts b/packages/hiccup-canvas/src/rect.ts new file mode 100644 index 0000000000..c1fbf893f4 --- /dev/null +++ b/packages/hiccup-canvas/src/rect.ts @@ -0,0 +1,36 @@ +import type { IObjectOf } from "@thi.ng/api"; +import type { ReadonlyVec } from "@thi.ng/vectors"; +import { path } from "./path"; + +export const rect = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + w: number, + h: number, + r = 0 +) => { + let v: any; + if (r > 0) { + r = Math.min(Math.min(w, h) / 2, r); + w -= 2 * r; + h -= 2 * r; + return path(ctx, attribs, [ + ["M", [pos[0] + r, pos[1]]], + ["h", w], + ["a", [r, 0], [r, r], r], + ["v", h], + ["a", [0, r], [-r, r], r], + ["h", -w], + ["a", [-r, 0], [-r, -r], r], + ["v", -h], + ["a", [0, -r], [r, -r], r], + ]); + } + if ((v = attribs.fill) && v !== "none") { + ctx.fillRect(pos[0], pos[1], w, h); + } + if ((v = attribs.stroke) && v !== "none") { + ctx.strokeRect(pos[0], pos[1], w, h); + } +}; diff --git a/packages/hiccup-canvas/src/state.ts b/packages/hiccup-canvas/src/state.ts new file mode 100644 index 0000000000..6372ccaddc --- /dev/null +++ b/packages/hiccup-canvas/src/state.ts @@ -0,0 +1,164 @@ +import type { IObjectOf } from "@thi.ng/api"; +import { isArrayLike } from "@thi.ng/checks"; +import type { DrawState } from "./api"; +import { resolveGradientOrColor } from "./color"; + +const DEFAULTS: any = { + align: "left", + alpha: 1, + baseline: "alphabetic", + compose: "source-over", + dash: [], + dashOffset: 0, + direction: "inherit", + fill: "#000", + filter: "none", + font: "10px sans-serif", + lineCap: "butt", + lineJoin: "miter", + miterLimit: 10, + shadowBlur: 0, + shadowColor: "rgba(0,0,0,0)", + shadowX: 0, + shadowY: 0, + smooth: true, + stroke: "#000", + weight: 1, +}; + +const CTX_ATTRIBS: IObjectOf = { + align: "textAlign", + alpha: "globalAlpha", + baseline: "textBaseline", + clip: "clip", + compose: "globalCompositeOperation", + dash: "setLineDash", + dashOffset: "lineDashOffset", + direction: "direction", + fill: "fillStyle", + filter: "filter", + font: "font", + lineCap: "lineCap", + lineJoin: "lineJoin", + miterLimit: "miterLimit", + shadowBlur: "shadowBlur", + shadowColor: "shadowColor", + shadowX: "shadowOffsetX", + shadowY: "shadowOffsetY", + smooth: "imageSmoothingEnabled", + stroke: "strokeStyle", + weight: "lineWidth", +}; + +const newState = (state: DrawState, restore = false) => ({ + attribs: { ...state.attribs }, + grads: { ...state.grads }, + edits: [], + restore, +}); + +/** @internal */ +export const mergeState = ( + ctx: CanvasRenderingContext2D, + state: DrawState, + attribs: IObjectOf +) => { + let res: DrawState | undefined; + if (!attribs) return; + if (applyTransform(ctx, attribs)) { + res = newState(state, true); + } + for (let id in attribs) { + const k = CTX_ATTRIBS[id]; + if (k) { + const v = attribs[id]; + if (v != null && state.attribs[id] !== v) { + !res && (res = newState(state)); + res.attribs[id] = v; + res.edits!.push(id); + setAttrib(ctx, state, id, k, v); + } + } + } + return res; +}; + +/** @internal */ +export const restoreState = ( + ctx: CanvasRenderingContext2D, + prev: DrawState, + curr: DrawState +) => { + if (curr.restore) { + ctx.restore(); + return; + } + const edits = curr.edits; + const attribs = prev.attribs; + for (let i = edits.length; --i >= 0; ) { + const id = edits[i]; + const v = attribs[id]; + setAttrib(ctx, prev, id, CTX_ATTRIBS[id], v != null ? v : DEFAULTS[id]); + } +}; + +/** @internal */ +export const registerGradient = ( + state: DrawState, + id: string, + g: CanvasGradient +) => { + !state.grads && (state.grads = {}); + state.grads[id] = g; +}; + +const setAttrib = ( + ctx: CanvasRenderingContext2D, + state: DrawState, + id: string, + k: string, + val: any +) => { + switch (id) { + case "fill": + case "stroke": + case "shadowColor": + (ctx)[k] = resolveGradientOrColor(state, val); + break; + case "dash": + (ctx)[k].call(ctx, val); + break; + case "clip": + break; + default: + (ctx)[k] = val; + } +}; + +const applyTransform = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf +) => { + let v: any; + if ( + (v = attribs.transform) || + attribs.setTransform || + attribs.translate || + attribs.scale || + attribs.rotate + ) { + ctx.save(); + if (v) { + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + } else if ((v = attribs.setTransform)) { + ctx.setTransform(v[0], v[1], v[2], v[3], v[4], v[5]); + } else { + (v = attribs.translate) && ctx.translate(v[0], v[1]); + (v = attribs.rotate) && ctx.rotate(v); + (v = attribs.scale) && + (isArrayLike(v) ? ctx.scale(v[0], v[1]) : ctx.scale(v, v)); + } + return true; + } + return false; +}; diff --git a/packages/hiccup-canvas/src/text.ts b/packages/hiccup-canvas/src/text.ts new file mode 100644 index 0000000000..f217a7d31a --- /dev/null +++ b/packages/hiccup-canvas/src/text.ts @@ -0,0 +1,18 @@ +import type { IObjectOf } from "@thi.ng/api"; +import type { ReadonlyVec } from "@thi.ng/vectors"; + +export const text = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + body: any, + maxWidth?: number +) => { + let v: any; + if ((v = attribs.fill) && v !== "none") { + ctx.fillText(body.toString(), pos[0], pos[1], maxWidth); + } + if ((v = attribs.stroke) && v !== "none") { + ctx.strokeText(body.toString(), pos[0], pos[1], maxWidth); + } +}; diff --git a/packages/hiccup-canvas/test/index.ts b/packages/hiccup-canvas/test/index.ts new file mode 100644 index 0000000000..18f1f75771 --- /dev/null +++ b/packages/hiccup-canvas/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import { } from "../src"; + +describe("hiccup-canvas", () => { + it("tests pending"); +}); diff --git a/packages/hiccup-canvas/test/tsconfig.json b/packages/hiccup-canvas/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/hiccup-canvas/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/hiccup-canvas/tsconfig.json b/packages/hiccup-canvas/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/hiccup-canvas/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +} From 41c8a9d696211b13bde358dae431f110ab7b4be5 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 13:21:38 +0100 Subject: [PATCH 09/25] feat(hdom-canvas): remove obsolete files BREAKING CHANGE: tree traversal & rendering parts extracted to new package @thi.ng/hiccup-canvas From now on, this package only contains the canvas component wrapper & hdom related interface implementations, allowing canvas rendering parts to be used separately. --- packages/hdom-canvas/package.json | 11 +- packages/hdom-canvas/src/api.ts | 8 - packages/hdom-canvas/src/draw/arc.ts | 33 --- packages/hdom-canvas/src/draw/color.ts | 36 ---- packages/hdom-canvas/src/draw/end-shape.ts | 18 -- packages/hdom-canvas/src/draw/image.ts | 28 --- packages/hdom-canvas/src/draw/line.ts | 15 -- .../hdom-canvas/src/draw/packed-points.ts | 49 ----- packages/hdom-canvas/src/draw/path.ts | 113 ---------- packages/hdom-canvas/src/draw/points.ts | 40 ---- packages/hdom-canvas/src/draw/polygon.ts | 33 --- packages/hdom-canvas/src/draw/polyline.ts | 13 -- packages/hdom-canvas/src/draw/rect.ts | 36 ---- packages/hdom-canvas/src/draw/text.ts | 18 -- packages/hdom-canvas/src/impl.ts | 186 ----------------- packages/hdom-canvas/src/index.ts | 196 ++++++++++++++++-- packages/hdom-canvas/src/state.ts | 164 --------------- packages/hdom-canvas/src/walk.ts | 124 ----------- 18 files changed, 184 insertions(+), 937 deletions(-) delete mode 100644 packages/hdom-canvas/src/api.ts delete mode 100644 packages/hdom-canvas/src/draw/arc.ts delete mode 100644 packages/hdom-canvas/src/draw/color.ts delete mode 100644 packages/hdom-canvas/src/draw/end-shape.ts delete mode 100644 packages/hdom-canvas/src/draw/image.ts delete mode 100644 packages/hdom-canvas/src/draw/line.ts delete mode 100644 packages/hdom-canvas/src/draw/packed-points.ts delete mode 100644 packages/hdom-canvas/src/draw/path.ts delete mode 100644 packages/hdom-canvas/src/draw/points.ts delete mode 100644 packages/hdom-canvas/src/draw/polygon.ts delete mode 100644 packages/hdom-canvas/src/draw/polyline.ts delete mode 100644 packages/hdom-canvas/src/draw/rect.ts delete mode 100644 packages/hdom-canvas/src/draw/text.ts delete mode 100644 packages/hdom-canvas/src/impl.ts delete mode 100644 packages/hdom-canvas/src/state.ts delete mode 100644 packages/hdom-canvas/src/walk.ts diff --git a/packages/hdom-canvas/package.json b/packages/hdom-canvas/package.json index 8aeee9fbd9..c5c02b93ea 100644 --- a/packages/hdom-canvas/package.json +++ b/packages/hdom-canvas/package.json @@ -1,7 +1,7 @@ { "name": "@thi.ng/hdom-canvas", "version": "2.4.26", - "description": "Declarative canvas scenegraph & visualization for @thi.ng/hdom", + "description": "@thi.ng/hdom component wrapper for declarative canvas scenegraphs", "module": "./index.js", "main": "./lib/index.js", "umd:main": "./lib/index.umd.js", @@ -45,18 +45,14 @@ "dependencies": { "@thi.ng/api": "^6.11.0", "@thi.ng/checks": "^2.7.0", - "@thi.ng/color": "^1.2.2", "@thi.ng/diff": "^3.2.22", "@thi.ng/hdom": "^8.0.27", - "@thi.ng/math": "^1.7.10", - "@thi.ng/vectors": "^4.4.3", - "tslib": "^1.12.0" + "@thi.ng/hiccup-canvas": "^0.0.1" }, "files": [ "*.js", "*.d.ts", - "lib", - "draw" + "lib" ], "keywords": [ "ES6", @@ -78,6 +74,7 @@ "related": [ "geom", "hdom", + "hiccup-canvas", "hiccup-svg" ], "year": 2018 diff --git a/packages/hdom-canvas/src/api.ts b/packages/hdom-canvas/src/api.ts deleted file mode 100644 index cd4e797bff..0000000000 --- a/packages/hdom-canvas/src/api.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { IObjectOf } from "@thi.ng/api"; - -export interface DrawState { - attribs: IObjectOf; - grads?: IObjectOf; - edits: string[]; - restore?: boolean; -} diff --git a/packages/hdom-canvas/src/draw/arc.ts b/packages/hdom-canvas/src/draw/arc.ts deleted file mode 100644 index 78a939a366..0000000000 --- a/packages/hdom-canvas/src/draw/arc.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { TAU } from "@thi.ng/math"; -import { endShape } from "./end-shape"; -import type { IObjectOf } from "@thi.ng/api"; -import type { ReadonlyVec } from "@thi.ng/vectors"; - -export const circularArc = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - r: number, - start = 0, - end = TAU, - antiCCW = false -) => { - ctx.beginPath(); - ctx.arc(pos[0], pos[1], r, start, end, antiCCW); - endShape(ctx, attribs); -}; - -export 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); -}; diff --git a/packages/hdom-canvas/src/draw/color.ts b/packages/hdom-canvas/src/draw/color.ts deleted file mode 100644 index 0fd893e638..0000000000 --- a/packages/hdom-canvas/src/draw/color.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { isString } from "@thi.ng/checks"; -import { resolveAsCSS } from "@thi.ng/color"; -import type { DrawState } from "../api"; - -export const resolveColor = (v: any) => (isString(v) ? v : resolveAsCSS(v)); - -export const resolveGradientOrColor = (state: DrawState, v: any) => - isString(v) - ? v[0] === "$" - ? state.grads![v.substr(1)] - : v - : resolveAsCSS(v); - -export const defLinearGradient = ( - ctx: CanvasRenderingContext2D, - { from, to }: any, - stops: any[][] -) => { - const g = ctx.createLinearGradient(from[0], from[1], to[0], to[1]); - for (let s of stops) { - g.addColorStop(s[0], resolveColor(s[1])); - } - return g; -}; - -export const defRadialGradient = ( - ctx: CanvasRenderingContext2D, - { from, to, r1, r2 }: any, - stops: any[][] -) => { - const g = ctx.createRadialGradient(from[0], from[1], r1, to[0], to[1], r2); - for (let s of stops) { - g.addColorStop(s[0], resolveColor(s[1])); - } - return g; -}; diff --git a/packages/hdom-canvas/src/draw/end-shape.ts b/packages/hdom-canvas/src/draw/end-shape.ts deleted file mode 100644 index ee3ec128e0..0000000000 --- a/packages/hdom-canvas/src/draw/end-shape.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { IObjectOf } from "@thi.ng/api"; - -/** @internal */ -export const endShape = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf -) => { - let v: any; - if ((v = attribs.fill) && v !== "none") { - ctx.fill(); - } - if ((v = attribs.stroke) && v !== "none") { - ctx.stroke(); - } - if ((v = attribs.clip)) { - ctx.clip(v === true ? "nonzero" : v); - } -}; diff --git a/packages/hdom-canvas/src/draw/image.ts b/packages/hdom-canvas/src/draw/image.ts deleted file mode 100644 index 53fbf8c8fc..0000000000 --- a/packages/hdom-canvas/src/draw/image.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { IObjectOf } from "@thi.ng/api"; -import type { ReadonlyVec } from "@thi.ng/vectors"; - -export const image = ( - ctx: CanvasRenderingContext2D, - _: IObjectOf, - { width, height }: IObjectOf, - img: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, - dpos: ReadonlyVec, - spos?: ReadonlyVec, - ssize?: ReadonlyVec -) => { - width = width || img.width; - height = height || img.height; - spos - ? ctx.drawImage( - img, - spos[0], - spos[1], - ssize ? ssize[0] : width, - ssize ? ssize[1] : height, - dpos[0], - dpos[1], - width, - height - ) - : ctx.drawImage(img, dpos[0], dpos[1], width, height); -}; diff --git a/packages/hdom-canvas/src/draw/line.ts b/packages/hdom-canvas/src/draw/line.ts deleted file mode 100644 index acaf349f8b..0000000000 --- a/packages/hdom-canvas/src/draw/line.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { IObjectOf } from "@thi.ng/api"; -import type { ReadonlyVec } from "@thi.ng/vectors"; - -export const line = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - a: ReadonlyVec, - b: ReadonlyVec -) => { - if (attribs.stroke === "none") return; - ctx.beginPath(); - ctx.moveTo(a[0], a[1]); - ctx.lineTo(b[0], b[1]); - ctx.stroke(); -}; diff --git a/packages/hdom-canvas/src/draw/packed-points.ts b/packages/hdom-canvas/src/draw/packed-points.ts deleted file mode 100644 index 34dce991b6..0000000000 --- a/packages/hdom-canvas/src/draw/packed-points.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { TAU } from "@thi.ng/math"; -import type { IObjectOf } from "@thi.ng/api"; - -export const packedPoints = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - opts: IObjectOf, - pts: ArrayLike -) => { - let v: any; - if ((v = attribs.fill) && v !== "none") { - __drawPoints(ctx, opts, pts, "fill", "fillRect"); - } - if ((v = attribs.stroke) && v !== "none") { - __drawPoints(ctx, opts, pts, "stroke", "strokeRect"); - } -}; - -const __drawPoints = ( - ctx: CanvasRenderingContext2D, - opts: IObjectOf, - pts: ArrayLike, - cmd: "fill" | "stroke", - cmdR: "fillRect" | "strokeRect" -) => { - const { start, cstride, estride, size } = { - start: 0, - cstride: 1, - estride: 2, - size: 1, - ...opts - }; - let num = - opts && opts.num != null - ? opts.num - : ((pts.length - start) / estride) | 0; - if (opts.shape === "circle") { - for (let i = start; --num >= 0; i += estride) { - ctx.beginPath(); - ctx.arc(pts[i], pts[i + cstride], size, 0, TAU); - ctx[cmd](); - } - } else { - const r = size / 2; - for (let i = start; --num >= 0; i += estride) { - ctx[cmdR](pts[i] - r, pts[i + cstride] - r, size, size); - } - } -}; diff --git a/packages/hdom-canvas/src/draw/path.ts b/packages/hdom-canvas/src/draw/path.ts deleted file mode 100644 index f9e2421460..0000000000 --- a/packages/hdom-canvas/src/draw/path.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { endShape } from "./end-shape"; -import type { IObjectOf } from "@thi.ng/api"; -import type { ReadonlyVec } from "@thi.ng/vectors"; - -export const path = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - segments: any[] -) => { - ctx.beginPath(); - let a: ReadonlyVec = [0, 0]; - for (let i = 0, n = segments.length; i < n; i++) { - const s = segments[i]; - let b = s[1], - c, - d; - switch (s[0]) { - // move to - case "m": - b = [a[0] + b[0], a[1] + b[1]]; - case "M": - ctx.moveTo(b[0], b[1]); - a = b; - break; - // line to - case "l": - b = [a[0] + b[0], a[1] + b[1]]; - case "L": - ctx.lineTo(b[0], b[1]); - a = b; - break; - // 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 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 curve rel - case "c": - c = s[2]; - d = s[3]; - d = [a[0] + d[0], a[1] + d[1]]; - ctx.bezierCurveTo( - a[0] + b[0], - a[1] + b[1], - a[0] + c[0], - a[1] + c[1], - d[0], - d[1] - ); - a = d; - break; - // cubic curve abs - case "C": - c = s[2]; - d = s[3]; - ctx.bezierCurveTo(b[0], b[1], c[0], c[1], d[0], d[1]); - a = d; - break; - // quadratic curve rel - case "q": - c = s[2]; - c = [a[0] + c[0], a[1] + c[1]]; - ctx.quadraticCurveTo(a[0] + b[0], a[1] + b[1], c[0], c[1]); - a = c; - break; - // quadratic curve abs - case "Q": - c = s[2]; - ctx.quadraticCurveTo(b[0], b[1], c[0], c[1]); - a = c; - break; - // circular arc rel - // Note: NOT compatible w/ SVG arc segments - case "a": - c = s[2]; - c = [a[0] + c[0], a[1] + c[1]]; - ctx.arcTo(a[0] + b[0], a[1] + b[1], c[0], c[1], s[3]); - a = c; - break; - // circular arc abs - // Note: NOT compatible w/ SVG arc segments - case "A": - c = s[2]; - ctx.arcTo(b[0], b[1], c[0], c[1], s[3]); - a = c; - break; - // close path - case "z": - case "Z": - ctx.closePath(); - } - } - endShape(ctx, attribs); -}; diff --git a/packages/hdom-canvas/src/draw/points.ts b/packages/hdom-canvas/src/draw/points.ts deleted file mode 100644 index 4eebdcbe7a..0000000000 --- a/packages/hdom-canvas/src/draw/points.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { TAU } from "@thi.ng/math"; -import type { IObjectOf } from "@thi.ng/api"; -import type { ReadonlyVec } from "@thi.ng/vectors"; - -export const points = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - opts: IObjectOf, - pts: Iterable -) => { - let v: any; - if ((v = attribs.fill) && v !== "none") { - __drawPoints(ctx, opts, pts, "fill", "fillRect"); - } - if ((v = attribs.stroke) && v !== "none") { - __drawPoints(ctx, opts, pts, "stroke", "strokeRect"); - } -}; - -const __drawPoints = ( - ctx: CanvasRenderingContext2D, - opts: IObjectOf, - pts: Iterable, - cmd: "fill" | "stroke", - cmdR: "fillRect" | "strokeRect" -) => { - const s: number = (opts && opts.size) || 1; - if (opts.shape === "circle") { - for (let p of pts) { - ctx.beginPath(); - ctx.arc(p[0], p[1], s, 0, TAU); - ctx[cmd](); - } - } else { - const r = s / 2; - for (let p of pts) { - ctx[cmdR](p[0] - r, p[1] - r, s, s); - } - } -}; diff --git a/packages/hdom-canvas/src/draw/polygon.ts b/packages/hdom-canvas/src/draw/polygon.ts deleted file mode 100644 index 388bedf2be..0000000000 --- a/packages/hdom-canvas/src/draw/polygon.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { endShape } from "./end-shape"; -import type { IObjectOf } from "@thi.ng/api"; -import type { ReadonlyVec } from "@thi.ng/vectors"; - -export const polygon = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pts: ReadonlyVec[] -) => { - if (pts.length < 2) return; - __drawPoly(ctx, pts); - ctx.closePath(); - endShape(ctx, attribs); -}; - -/** - * Shared internal helper for polygon & polyline fns. - * - * @param ctx - canvas context - * @param pts - poly vertices - */ -export const __drawPoly = ( - ctx: CanvasRenderingContext2D, - pts: ReadonlyVec[] -) => { - let p: ReadonlyVec = pts[0]; - ctx.beginPath(); - ctx.moveTo(p[0], p[1]); - for (let i = 1, n = pts.length; i < n; i++) { - p = pts[i]; - ctx.lineTo(p[0], p[1]); - } -}; diff --git a/packages/hdom-canvas/src/draw/polyline.ts b/packages/hdom-canvas/src/draw/polyline.ts deleted file mode 100644 index 5726b34b1c..0000000000 --- a/packages/hdom-canvas/src/draw/polyline.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { __drawPoly } from "./polygon"; -import type { IObjectOf } from "@thi.ng/api"; -import type { ReadonlyVec } from "@thi.ng/vectors"; - -export const polyline = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pts: ReadonlyVec[] -) => { - if (pts.length < 2 || attribs.stroke == "none") return; - __drawPoly(ctx, pts); - ctx.stroke(); -}; diff --git a/packages/hdom-canvas/src/draw/rect.ts b/packages/hdom-canvas/src/draw/rect.ts deleted file mode 100644 index 263a177293..0000000000 --- a/packages/hdom-canvas/src/draw/rect.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { path } from "./path"; -import type { IObjectOf } from "@thi.ng/api"; -import type { ReadonlyVec } from "@thi.ng/vectors"; - -export const rect = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - w: number, - h: number, - r = 0 -) => { - let v: any; - if (r > 0) { - r = Math.min(Math.min(w, h) / 2, r); - w -= 2 * r; - h -= 2 * r; - return path(ctx, attribs, [ - ["M", [pos[0] + r, pos[1]]], - ["h", w], - ["a", [r, 0], [r, r], r], - ["v", h], - ["a", [0, r], [-r, r], r], - ["h", -w], - ["a", [-r, 0], [-r, -r], r], - ["v", -h], - ["a", [0, -r], [r, -r], r] - ]); - } - if ((v = attribs.fill) && v !== "none") { - ctx.fillRect(pos[0], pos[1], w, h); - } - if ((v = attribs.stroke) && v !== "none") { - ctx.strokeRect(pos[0], pos[1], w, h); - } -}; diff --git a/packages/hdom-canvas/src/draw/text.ts b/packages/hdom-canvas/src/draw/text.ts deleted file mode 100644 index f217a7d31a..0000000000 --- a/packages/hdom-canvas/src/draw/text.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { IObjectOf } from "@thi.ng/api"; -import type { ReadonlyVec } from "@thi.ng/vectors"; - -export const text = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - body: any, - maxWidth?: number -) => { - let v: any; - if ((v = attribs.fill) && v !== "none") { - ctx.fillText(body.toString(), pos[0], pos[1], maxWidth); - } - if ((v = attribs.stroke) && v !== "none") { - ctx.strokeText(body.toString(), pos[0], pos[1], maxWidth); - } -}; diff --git a/packages/hdom-canvas/src/impl.ts b/packages/hdom-canvas/src/impl.ts deleted file mode 100644 index c143076c98..0000000000 --- a/packages/hdom-canvas/src/impl.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { assert, NO_OP } from "@thi.ng/api"; -import { isArray, isNotStringAndIterable } from "@thi.ng/checks"; -import { diffArray, DiffMode } from "@thi.ng/diff"; -import { - equiv, - HDOMImplementation, - HDOMOpts, - releaseTree -} from "@thi.ng/hdom"; -import { walk } from "./walk"; - -const FN = "function"; -const STR = "string"; - -/** - * Special HTML5 canvas component which injects a branch-local hdom - * implementation for virtual SVG-like shape components / elements. - * These elements are then translated into canvas draw commands during - * the hdom update process. - * - * The canvas component automatically adjusts its size for HDPI displays - * by adding CSS `width` & `height` properties and pre-scaling the - * drawing context accordingly before shapes are processed. - * - * Shape components are expressed in standard hiccup syntax, however - * with the following restrictions: - * - * - Shape component objects with life cycle methods are only partially - * supported, i.e. only the {@link @thi.ng/hdom#ILifecycle.render} & - * {@link @thi.ng/hdom#ILifecycle.release} methods are used (Note, for - * performance reasons `release` methods are ignored by default. If - * your shape tree contains stateful components which use the - * `release` life cycle method, you'll need to explicitly enable the - * canvas component's `__release` attribute by setting it to `true`). - * - Currently no event listeners can be assigned to shapes (ignored), - * though this is planned for a future version. The canvas element - * itself can of course have event handlers as usual. - * - * All embedded component functions receive the user context object just - * like normal hdom components. - * - * For best performance, it's recommended to ensure all resulting shapes - * elements are provided in already normalized hiccup format (i.e. - * `[tag, {attribs}, ...]`). That way the `__normalize: false` control - * attribute can be added either to the canvas component itself (or to - * individual shapes / groups), and if present, will skip normalization - * of all children. - * - * @param _ - hdom user context (ignored) - * @param attribs - canvas attribs - * @param shapes - shape components - */ -export const canvas = { - render(_: any, attribs: any, ...body: any[]) { - const cattribs = { ...attribs }; - delete cattribs.__diff; - delete cattribs.__normalize; - const dpr = window.devicePixelRatio || 1; - if (dpr !== 1) { - !cattribs.style && (cattribs.style = {}); - cattribs.style.width = `${cattribs.width}px`; - cattribs.style.height = `${cattribs.height}px`; - cattribs.width *= dpr; - cattribs.height *= dpr; - } - return [ - "canvas", - cattribs, - [ - "g", - { - __impl: IMPL, - __diff: attribs.__diff !== false, - __normalize: attribs.__normalize !== false, - __release: attribs.__release === true, - __serialize: false, - __clear: attribs.__clear, - scale: dpr !== 1 ? dpr : null - }, - ...body - ] - ]; - } -}; - -/** @internal */ -export const createTree = ( - _: Partial, - canvas: HTMLCanvasElement, - tree: any -) => { - // console.log(Date.now(), "draw"); - const ctx = canvas.getContext("2d"); - assert(!!ctx, "canvas ctx unavailable"); - const attribs = tree[1]; - if (attribs) { - if (attribs.__skip) return; - if (attribs.__clear !== false) { - ctx!.clearRect(0, 0, canvas.width, canvas.height); - } - } - walk(ctx!, tree, { attribs: {}, edits: [] }); -}; - -/** @internal */ -export const normalizeTree = (opts: Partial, tree: any): any => { - if (tree == null) { - return tree; - } - if (isArray(tree)) { - const tag = tree[0]; - if (typeof tag === FN) { - return normalizeTree( - opts, - tag.apply(null, [opts.ctx, ...tree.slice(1)]) - ); - } - if (typeof tag === STR) { - const attribs = tree[1]; - if (attribs && attribs.__normalize === false) { - return tree; - } - const res = [tree[0], attribs]; - for (let i = 2, n = tree.length; i < n; i++) { - const n = normalizeTree(opts, tree[i]); - n != null && res.push(n); - } - return res; - } - } else if (typeof tree === FN) { - return normalizeTree(opts, tree(opts.ctx)); - } else if (typeof tree.toHiccup === FN) { - return normalizeTree(opts, tree.toHiccup(opts.ctx)); - } else if (typeof tree.deref === FN) { - return normalizeTree(opts, tree.deref()); - } else if (isNotStringAndIterable(tree)) { - const res = []; - for (let t of tree) { - const n = normalizeTree(opts, t); - n != null && res.push(n); - } - return res; - } - return tree; -}; - -/** @internal */ -export const diffTree = ( - opts: Partial, - parent: HTMLCanvasElement, - prev: any[], - curr: any[], - child: number -) => { - const attribs = curr[1]; - if (attribs.__skip) return; - if (attribs.__diff === false) { - releaseTree(prev); - return createTree(opts, parent, curr); - } - // delegate to branch-local implementation - let impl: HDOMImplementation = attribs.__impl; - if (impl && impl !== IMPL) { - return impl.diffTree(opts, parent, prev, curr, child); - } - const delta = diffArray(prev, curr, DiffMode.ONLY_DISTANCE, equiv); - if (delta.distance > 0) { - return createTree(opts, parent, curr); - } -}; - -export const IMPL: HDOMImplementation = { - createTree, - normalizeTree, - diffTree, - hydrateTree: NO_OP, - getElementById: NO_OP, - createElement: NO_OP, - createTextElement: NO_OP, - replaceChild: NO_OP, - getChild: NO_OP, - removeAttribs: NO_OP, - removeChild: NO_OP, - setAttrib: NO_OP, - setContent: NO_OP -}; diff --git a/packages/hdom-canvas/src/index.ts b/packages/hdom-canvas/src/index.ts index fc085bf774..fdc3e38940 100644 --- a/packages/hdom-canvas/src/index.ts +++ b/packages/hdom-canvas/src/index.ts @@ -1,17 +1,181 @@ -export * from "./api"; -export * from "./impl"; -export * from "./state"; -export * from "./walk"; +import { assert, NO_OP } from "@thi.ng/api"; +import { isArray, isNotStringAndIterable } from "@thi.ng/checks"; +import { diffArray, DiffMode } from "@thi.ng/diff"; +import { equiv, HDOMImplementation, HDOMOpts, releaseTree } from "@thi.ng/hdom"; +import { draw } from "@thi.ng/hiccup-canvas"; -export * from "./draw/arc"; -export * from "./draw/color"; -export * from "./draw/end-shape"; -export * from "./draw/image"; -export * from "./draw/line"; -export * from "./draw/packed-points"; -export * from "./draw/path"; -export * from "./draw/points"; -export * from "./draw/polygon"; -export * from "./draw/polyline"; -export * from "./draw/rect"; -export * from "./draw/text"; +const FN = "function"; +const STR = "string"; + +/** + * Special HTML5 canvas component which injects a branch-local hdom + * implementation for virtual SVG-like shape components / elements. + * These elements are then translated into canvas draw commands during + * the hdom update process. + * + * The canvas component automatically adjusts its size for HDPI displays + * by adding CSS `width` & `height` properties and pre-scaling the + * drawing context accordingly before shapes are processed. + * + * Shape components are expressed in standard hiccup syntax, however + * with the following restrictions: + * + * - Shape component objects with life cycle methods are only partially + * supported, i.e. only the {@link @thi.ng/hdom#ILifecycle.render} & + * {@link @thi.ng/hdom#ILifecycle.release} methods are used (Note, for + * performance reasons `release` methods are ignored by default. If + * your shape tree contains stateful components which use the + * `release` life cycle method, you'll need to explicitly enable the + * canvas component's `__release` attribute by setting it to `true`). + * - Currently no event listeners can be assigned to shapes (ignored), + * though this is planned for a future version. The canvas element + * itself can of course have event handlers as usual. + * + * All embedded component functions receive the user context object just + * like normal hdom components. + * + * For best performance, it's recommended to ensure all resulting shapes + * elements are provided in already normalized hiccup format (i.e. + * `[tag, {attribs}, ...]`). That way the `__normalize: false` control + * attribute can be added either to the canvas component itself (or to + * individual shapes / groups), and if present, will skip normalization + * of all children. + * + * @param _ - hdom user context (ignored) + * @param attribs - canvas attribs + * @param shapes - shape components + */ +export const canvas = { + render(_: any, attribs: any, ...body: any[]) { + const cattribs = { ...attribs }; + delete cattribs.__diff; + delete cattribs.__normalize; + const dpr = window.devicePixelRatio || 1; + if (dpr !== 1) { + !cattribs.style && (cattribs.style = {}); + cattribs.style.width = `${cattribs.width}px`; + cattribs.style.height = `${cattribs.height}px`; + cattribs.width *= dpr; + cattribs.height *= dpr; + } + return [ + "canvas", + cattribs, + [ + "g", + { + __impl: IMPL, + __diff: attribs.__diff !== false, + __normalize: attribs.__normalize !== false, + __release: attribs.__release === true, + __serialize: false, + __clear: attribs.__clear, + scale: dpr !== 1 ? dpr : null, + }, + ...body, + ], + ]; + }, +}; + +/** @internal */ +export const createTree = ( + _: Partial, + canvas: HTMLCanvasElement, + tree: any +) => { + // console.log(Date.now(), "draw"); + const ctx = canvas.getContext("2d"); + assert(!!ctx, "canvas ctx unavailable"); + const attribs = tree[1]; + if (attribs) { + if (attribs.__skip) return; + if (attribs.__clear !== false) { + ctx!.clearRect(0, 0, canvas.width, canvas.height); + } + } + draw(ctx!, tree); +}; + +/** @internal */ +export const normalizeTree = (opts: Partial, tree: any): any => { + if (tree == null) { + return tree; + } + if (isArray(tree)) { + const tag = tree[0]; + if (typeof tag === FN) { + return normalizeTree( + opts, + tag.apply(null, [opts.ctx, ...tree.slice(1)]) + ); + } + if (typeof tag === STR) { + const attribs = tree[1]; + if (attribs && attribs.__normalize === false) { + return tree; + } + const res = [tree[0], attribs]; + for (let i = 2, n = tree.length; i < n; i++) { + const n = normalizeTree(opts, tree[i]); + n != null && res.push(n); + } + return res; + } + } else if (typeof tree === FN) { + return normalizeTree(opts, tree(opts.ctx)); + } else if (typeof tree.toHiccup === FN) { + return normalizeTree(opts, tree.toHiccup(opts.ctx)); + } else if (typeof tree.deref === FN) { + return normalizeTree(opts, tree.deref()); + } else if (isNotStringAndIterable(tree)) { + const res = []; + for (let t of tree) { + const n = normalizeTree(opts, t); + n != null && res.push(n); + } + return res; + } + return tree; +}; + +/** @internal */ +export const diffTree = ( + opts: Partial, + parent: HTMLCanvasElement, + prev: any[], + curr: any[], + child: number +) => { + const attribs = curr[1]; + if (attribs.__skip) return; + if (attribs.__diff === false) { + releaseTree(prev); + return createTree(opts, parent, curr); + } + // delegate to branch-local implementation + let impl: HDOMImplementation = attribs.__impl; + if (impl && impl !== IMPL) { + return impl.diffTree(opts, parent, prev, curr, child); + } + const delta = diffArray(prev, curr, DiffMode.ONLY_DISTANCE, equiv); + if (delta.distance > 0) { + return createTree(opts, parent, curr); + } +}; + +export const IMPL: HDOMImplementation = { + createTree, + normalizeTree, + diffTree, + hydrateTree: NO_OP, + getElementById: NO_OP, + createElement: NO_OP, + createTextElement: NO_OP, + replaceChild: NO_OP, + getChild: NO_OP, + removeAttribs: NO_OP, + removeChild: NO_OP, + setAttrib: NO_OP, + setContent: NO_OP, +}; diff --git a/packages/hdom-canvas/src/state.ts b/packages/hdom-canvas/src/state.ts deleted file mode 100644 index e0d1d9d29c..0000000000 --- a/packages/hdom-canvas/src/state.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { isArrayLike } from "@thi.ng/checks"; -import { resolveGradientOrColor } from "./draw/color"; -import type { IObjectOf } from "@thi.ng/api"; -import type { DrawState } from "./api"; - -const DEFAULTS: any = { - align: "left", - alpha: 1, - baseline: "alphabetic", - compose: "source-over", - dash: [], - dashOffset: 0, - direction: "inherit", - fill: "#000", - filter: "none", - font: "10px sans-serif", - lineCap: "butt", - lineJoin: "miter", - miterLimit: 10, - shadowBlur: 0, - shadowColor: "rgba(0,0,0,0)", - shadowX: 0, - shadowY: 0, - smooth: true, - stroke: "#000", - weight: 1 -}; - -const CTX_ATTRIBS: IObjectOf = { - align: "textAlign", - alpha: "globalAlpha", - baseline: "textBaseline", - clip: "clip", - compose: "globalCompositeOperation", - dash: "setLineDash", - dashOffset: "lineDashOffset", - direction: "direction", - fill: "fillStyle", - filter: "filter", - font: "font", - lineCap: "lineCap", - lineJoin: "lineJoin", - miterLimit: "miterLimit", - shadowBlur: "shadowBlur", - shadowColor: "shadowColor", - shadowX: "shadowOffsetX", - shadowY: "shadowOffsetY", - smooth: "imageSmoothingEnabled", - stroke: "strokeStyle", - weight: "lineWidth" -}; - -const newState = (state: DrawState, restore = false) => ({ - attribs: { ...state.attribs }, - grads: { ...state.grads }, - edits: [], - restore -}); - -/** @internal */ -export const mergeState = ( - ctx: CanvasRenderingContext2D, - state: DrawState, - attribs: IObjectOf -) => { - let res: DrawState | undefined; - if (!attribs) return; - if (applyTransform(ctx, attribs)) { - res = newState(state, true); - } - for (let id in attribs) { - const k = CTX_ATTRIBS[id]; - if (k) { - const v = attribs[id]; - if (v != null && state.attribs[id] !== v) { - !res && (res = newState(state)); - res.attribs[id] = v; - res.edits!.push(id); - setAttrib(ctx, state, id, k, v); - } - } - } - return res; -}; - -/** @internal */ -export const restoreState = ( - ctx: CanvasRenderingContext2D, - prev: DrawState, - curr: DrawState -) => { - if (curr.restore) { - ctx.restore(); - return; - } - const edits = curr.edits; - const attribs = prev.attribs; - for (let i = edits.length; --i >= 0; ) { - const id = edits[i]; - const v = attribs[id]; - setAttrib(ctx, prev, id, CTX_ATTRIBS[id], v != null ? v : DEFAULTS[id]); - } -}; - -/** @internal */ -export const registerGradient = ( - state: DrawState, - id: string, - g: CanvasGradient -) => { - !state.grads && (state.grads = {}); - state.grads[id] = g; -}; - -const setAttrib = ( - ctx: CanvasRenderingContext2D, - state: DrawState, - id: string, - k: string, - val: any -) => { - switch (id) { - case "fill": - case "stroke": - case "shadowColor": - (ctx)[k] = resolveGradientOrColor(state, val); - break; - case "dash": - (ctx)[k].call(ctx, val); - break; - case "clip": - break; - default: - (ctx)[k] = val; - } -}; - -const applyTransform = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf -) => { - let v: any; - if ( - (v = attribs.transform) || - attribs.setTransform || - attribs.translate || - attribs.scale || - attribs.rotate - ) { - ctx.save(); - if (v) { - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - } else if ((v = attribs.setTransform)) { - ctx.setTransform(v[0], v[1], v[2], v[3], v[4], v[5]); - } else { - (v = attribs.translate) && ctx.translate(v[0], v[1]); - (v = attribs.rotate) && ctx.rotate(v); - (v = attribs.scale) && - (isArrayLike(v) ? ctx.scale(v[0], v[1]) : ctx.scale(v, v)); - } - return true; - } - return false; -}; diff --git a/packages/hdom-canvas/src/walk.ts b/packages/hdom-canvas/src/walk.ts deleted file mode 100644 index 91f3f05814..0000000000 --- a/packages/hdom-canvas/src/walk.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { isArray } from "@thi.ng/checks"; -import { circularArc, ellipticArc } from "./draw/arc"; -import { defLinearGradient, defRadialGradient } from "./draw/color"; -import { image } from "./draw/image"; -import { line } from "./draw/line"; -import { packedPoints } from "./draw/packed-points"; -import { path } from "./draw/path"; -import { points } from "./draw/points"; -import { polygon } from "./draw/polygon"; -import { polyline } from "./draw/polyline"; -import { rect } from "./draw/rect"; -import { text } from "./draw/text"; -import { mergeState, registerGradient, restoreState } from "./state"; -import type { DrawState } from "./api"; - -/** @internal */ -export const walk = ( - ctx: CanvasRenderingContext2D, - shape: any[], - pstate: DrawState -) => { - if (!shape) return; - if (isArray(shape[0])) { - for (let s of shape) { - walk(ctx, s, pstate); - } - return; - } - const state = mergeState(ctx, pstate, shape[1]); - const attribs = state ? state.attribs : pstate.attribs; - if (attribs.__skip) return; - switch (shape[0]) { - case "g": - case "defs": - defs(ctx, state, pstate, shape); - break; - case "linearGradient": - registerGradient( - pstate, - shape[1].id, - defLinearGradient(ctx, shape[1], shape[2]) - ); - break; - case "radialGradient": - registerGradient( - pstate, - shape[1].id, - defRadialGradient(ctx, shape[1], shape[2]) - ); - break; - case "points": - points(ctx, attribs, shape[1], shape[2]); - break; - case "packedPoints": - packedPoints(ctx, attribs, shape[1], shape[2]); - break; - case "line": - line(ctx, attribs, shape[2], shape[3]); - break; - case "hline": - line(ctx, attribs, [-1e6, shape[2]], [1e6, shape[2]]); - break; - case "vline": - line(ctx, attribs, [shape[2], -1e6], [shape[2], 1e6]); - break; - case "polyline": - polyline(ctx, attribs, shape[2]); - break; - case "polygon": - polygon(ctx, attribs, shape[2]); - break; - case "path": - path(ctx, attribs, shape[2]); - break; - case "rect": - rect(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); - break; - 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; - case "text": - text(ctx, attribs, shape[2], shape[3], shape[4]); - break; - case "img": - image( - ctx, - attribs, - shape[1], - shape[2], - shape[3], - shape[4], - shape[5] - ); - default: - } - state && restoreState(ctx, pstate, state); -}; - -const defs = ( - ctx: CanvasRenderingContext2D, - state: DrawState | undefined, - pstate: DrawState, - shape: any[] -) => { - const n = shape.length; - const __state = shape[0] === "g" ? state || pstate : pstate; - for (let i = 2; i < n; i++) { - walk(ctx, shape[i], __state); - } -}; From f04ca31c3e79c6bed53ea15eacbfb01eaa487ec4 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 13:22:50 +0100 Subject: [PATCH 10/25] refactor(examples): rename & update arcs demo --- .../.gitignore | 0 .../README.md | 4 ++-- .../index.html | 4 ++-- .../package.json | 9 ++++---- .../src/index.ts | 22 +++++++------------ .../src/webpack.d.ts | 0 .../tsconfig.json | 0 .../webpack.config.js | 0 8 files changed, 16 insertions(+), 23 deletions(-) rename examples/{hdom-canvas-arcs => hiccup-canvas-arcs}/.gitignore (100%) rename examples/{hdom-canvas-arcs => hiccup-canvas-arcs}/README.md (74%) rename examples/{hdom-canvas-arcs => hiccup-canvas-arcs}/index.html (90%) rename examples/{hdom-canvas-arcs => hiccup-canvas-arcs}/package.json (82%) rename examples/{hdom-canvas-arcs => hiccup-canvas-arcs}/src/index.ts (86%) rename examples/{hdom-canvas-arcs => hiccup-canvas-arcs}/src/webpack.d.ts (100%) rename examples/{hdom-canvas-arcs => hiccup-canvas-arcs}/tsconfig.json (100%) rename examples/{hdom-canvas-arcs => hiccup-canvas-arcs}/webpack.config.js (100%) diff --git a/examples/hdom-canvas-arcs/.gitignore b/examples/hiccup-canvas-arcs/.gitignore similarity index 100% rename from examples/hdom-canvas-arcs/.gitignore rename to examples/hiccup-canvas-arcs/.gitignore diff --git a/examples/hdom-canvas-arcs/README.md b/examples/hiccup-canvas-arcs/README.md similarity index 74% rename from examples/hdom-canvas-arcs/README.md rename to examples/hiccup-canvas-arcs/README.md index 96dd987e78..52ed0d9df8 100644 --- a/examples/hdom-canvas-arcs/README.md +++ b/examples/hiccup-canvas-arcs/README.md @@ -1,6 +1,6 @@ -# hdom-canvas-arcs +# hiccup-canvas-arcs -[Live demo](http://demo.thi.ng/umbrella/hdom-canvas-arcs/) +[Live demo](http://demo.thi.ng/umbrella/hiccup-canvas-arcs/) Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. diff --git a/examples/hdom-canvas-arcs/index.html b/examples/hiccup-canvas-arcs/index.html similarity index 90% rename from examples/hdom-canvas-arcs/index.html rename to examples/hiccup-canvas-arcs/index.html index 2eaadf190c..d0bba4f87b 100644 --- a/examples/hdom-canvas-arcs/index.html +++ b/examples/hiccup-canvas-arcs/index.html @@ -4,7 +4,7 @@ - hdom-canvas-arcs + hiccup-canvas-arcs Source code diff --git a/examples/hdom-canvas-arcs/package.json b/examples/hiccup-canvas-arcs/package.json similarity index 82% rename from examples/hdom-canvas-arcs/package.json rename to examples/hiccup-canvas-arcs/package.json index 38499682dd..220bc188c2 100644 --- a/examples/hdom-canvas-arcs/package.json +++ b/examples/hiccup-canvas-arcs/package.json @@ -1,7 +1,7 @@ { - "name": "hdom-canvas-arcs", + "name": "hiccup-canvas-arcs", "version": "0.0.1", - "description": "Animated arcs & drawing using hdom-canvas without hdom", + "description": "Animated arcs & drawing using hiccup-canvas", "repository": "https://github.com/thi-ng/umbrella", "author": "Karsten Schmidt ", "license": "Apache-2.0", @@ -18,8 +18,7 @@ }, "dependencies": { "@thi.ng/geom": "latest", - "@thi.ng/hdom": "latest", - "@thi.ng/hdom-canvas": "latest", + "@thi.ng/hiccup-canvas": "latest", "@thi.ng/math": "latest", "@thi.ng/rstream": "latest", "@thi.ng/transducers": "latest" @@ -33,7 +32,7 @@ "thi.ng": { "readme": [ "geom", - "hdom-canvas" + "hiccup-canvas" ] } } diff --git a/examples/hdom-canvas-arcs/src/index.ts b/examples/hiccup-canvas-arcs/src/index.ts similarity index 86% rename from examples/hdom-canvas-arcs/src/index.ts rename to examples/hiccup-canvas-arcs/src/index.ts index b08ef0a1b9..78fd3bc15a 100644 --- a/examples/hdom-canvas-arcs/src/index.ts +++ b/examples/hiccup-canvas-arcs/src/index.ts @@ -5,8 +5,7 @@ import { pathFromCubics, closestPoint, } from "@thi.ng/geom"; -import { createElement } from "@thi.ng/hdom"; -import { walk } from "@thi.ng/hdom-canvas"; +import { draw } from "@thi.ng/hiccup-canvas"; import { hsla } from "@thi.ng/color"; import { SYSTEM } from "@thi.ng/random"; import { fit01, TAU } from "@thi.ng/math"; @@ -20,10 +19,9 @@ const ORIGIN = [W / 2, W / 2]; const PICK_DIST = 10; const PICK_COL = "cyan"; -const canvas = createElement(document.body, "canvas", { - width: W, - height: W, -}); +const canvas: HTMLCanvasElement = document.createElement("canvas"); +canvas.width = canvas.height = W; +document.body.appendChild(canvas); const ctx = canvas.getContext("2d")!; @@ -65,10 +63,10 @@ fromRAF().subscribe({ ctx.clearRect(0, 0, canvas.width, canvas.height); // get mouse pos const m = mouse.deref(); - walk( + draw( ctx, - // group arcs and convert to hiccup tree required by `walk()` - // (see hdom-canvas readme for details) + // group arcs and convert to hiccup tree required by `draw()` + // (see hiccup-canvas readme for details) group( {}, arcs.map(({ r, w, col, theta, spread }) => { @@ -86,11 +84,7 @@ fromRAF().subscribe({ : col, }); }) - ).toHiccup(), - { - attribs: {}, - edits: [], - } + ).toHiccup() ); }, }); diff --git a/examples/hdom-canvas-arcs/src/webpack.d.ts b/examples/hiccup-canvas-arcs/src/webpack.d.ts similarity index 100% rename from examples/hdom-canvas-arcs/src/webpack.d.ts rename to examples/hiccup-canvas-arcs/src/webpack.d.ts diff --git a/examples/hdom-canvas-arcs/tsconfig.json b/examples/hiccup-canvas-arcs/tsconfig.json similarity index 100% rename from examples/hdom-canvas-arcs/tsconfig.json rename to examples/hiccup-canvas-arcs/tsconfig.json diff --git a/examples/hdom-canvas-arcs/webpack.config.js b/examples/hiccup-canvas-arcs/webpack.config.js similarity index 100% rename from examples/hdom-canvas-arcs/webpack.config.js rename to examples/hiccup-canvas-arcs/webpack.config.js From fd3ed5d8eea2255960aa5516f622f23628f52d61 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 14:07:42 +0100 Subject: [PATCH 11/25] refactor(examples): update arcs demo, add screenshot --- assets/examples/hiccup-canvas-arcs.png | Bin 0 -> 216408 bytes examples/hiccup-canvas-arcs/package.json | 3 ++- examples/hiccup-canvas-arcs/src/index.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 assets/examples/hiccup-canvas-arcs.png diff --git a/assets/examples/hiccup-canvas-arcs.png b/assets/examples/hiccup-canvas-arcs.png new file mode 100644 index 0000000000000000000000000000000000000000..21d58ae5ba605d804b792fd338e6f905fd7ed3d1 GIT binary patch literal 216408 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%0D>fJKvS?`bVB`q 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 KT-G@yGywpK{gPP# literal 0 HcmV?d00001 diff --git a/examples/hiccup-canvas-arcs/package.json b/examples/hiccup-canvas-arcs/package.json index 220bc188c2..f9a6acd29c 100644 --- a/examples/hiccup-canvas-arcs/package.json +++ b/examples/hiccup-canvas-arcs/package.json @@ -33,6 +33,7 @@ "readme": [ "geom", "hiccup-canvas" - ] + ], + "screenshot": "examples/hiccup-canvas-arcs.png" } } diff --git a/examples/hiccup-canvas-arcs/src/index.ts b/examples/hiccup-canvas-arcs/src/index.ts index 78fd3bc15a..036ca0f28b 100644 --- a/examples/hiccup-canvas-arcs/src/index.ts +++ b/examples/hiccup-canvas-arcs/src/index.ts @@ -84,7 +84,7 @@ fromRAF().subscribe({ : col, }); }) - ).toHiccup() + ) ); }, }); From f04fc61d486aae9fd98e658439bc88393bb0c1e7 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 14:12:26 +0100 Subject: [PATCH 12/25] docs(examples): update hdom-canvas-* metadata --- examples/hdom-canvas-clock/package.json | 1 + examples/hdom-canvas-draw/package.json | 10 +++++++++- examples/hdom-canvas-particles/package.json | 1 + examples/hdom-canvas-shapes/package.json | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/hdom-canvas-clock/package.json b/examples/hdom-canvas-clock/package.json index 846de4386a..c17934eb4e 100644 --- a/examples/hdom-canvas-clock/package.json +++ b/examples/hdom-canvas-clock/package.json @@ -32,6 +32,7 @@ "readme": [ "hdom", "hdom-canvas", + "hiccup-canvas", "vectors" ], "screenshot": "examples/hdom-canvas-clock.png" diff --git a/examples/hdom-canvas-draw/package.json b/examples/hdom-canvas-draw/package.json index cc550db0b6..4ef3d20108 100644 --- a/examples/hdom-canvas-draw/package.json +++ b/examples/hdom-canvas-draw/package.json @@ -31,7 +31,15 @@ "process": false }, "thi.ng": { - "readme": true, + "readme": [ + "hdom-canvas", + "hiccup-canvas", + "rstream", + "rstream-gestures", + "transducers", + "transducers-hdom", + "vectors" + ], "screenshot": "examples/hdom-canvas-draw.jpg" } } diff --git a/examples/hdom-canvas-particles/package.json b/examples/hdom-canvas-particles/package.json index bc859f2e86..912cbc39be 100644 --- a/examples/hdom-canvas-particles/package.json +++ b/examples/hdom-canvas-particles/package.json @@ -37,6 +37,7 @@ "geom", "hdom", "hdom-canvas", + "hiccup-canvas", "math", "vectors" ], diff --git a/examples/hdom-canvas-shapes/package.json b/examples/hdom-canvas-shapes/package.json index f1de41c477..95c3d674c0 100644 --- a/examples/hdom-canvas-shapes/package.json +++ b/examples/hdom-canvas-shapes/package.json @@ -44,6 +44,7 @@ "hdom-canvas", "hdom-components", "hiccup", + "hiccup-canvas", "hiccup-svg", "transducers", "transducers-hdom", From 6f4ddf1520a207170fd88285bc7bbe1be5f8aa80 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 14:12:56 +0100 Subject: [PATCH 13/25] docs(hdom-canvas): update readme --- packages/hdom-canvas/README.md | 380 ++--------------------------- packages/hdom-canvas/tpl.readme.md | 341 +------------------------- 2 files changed, 25 insertions(+), 696 deletions(-) diff --git a/packages/hdom-canvas/README.md b/packages/hdom-canvas/README.md index 17da3b1be6..342fee8384 100644 --- a/packages/hdom-canvas/README.md +++ b/packages/hdom-canvas/README.md @@ -11,6 +11,7 @@ This project is part of the - [About](#about) - [Status](#status) + - [BREAKING CHANGES 3.0.0](#breaking-changes-300) - [Related packages](#related-packages) - [Installation](#installation) - [Dependencies](#dependencies) @@ -21,35 +22,6 @@ This project is part of the - [HDPI support](#hdpi-support) - [SVG conversion](#svg-conversion) - [Supported shape types](#supported-shape-types) - - [Group](#group) - - [Definition group](#definition-group) - - [Circle](#circle) - - [Circular arc](#circular-arc) - - [Ellipse / elliptic arc](#ellipse---elliptic-arc) - - [Rect](#rect) - - [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) - - [Packed points](#packed-points) - - [Text](#text) - - [Image](#image) - - [Gradients](#gradients) -- [Attributes](#attributes) - - [Color attributes](#color-attributes) - - [String](#string) - - [Number](#number) - - [Array](#array) - - [@thi.ng/color values](#thing-color-values) - - [Coordinate transformations](#coordinate-transformations) - - [Transform matrix](#transform-matrix) - - [Override transform](#override-transform) - - [Translation](#translation) - - [Scaling](#scaling) - - [Rotation](#rotation) - [Authors](#authors) - [Maintainer](#maintainer) - [Contributors](#contributors) @@ -57,7 +29,7 @@ This project is part of the ## About -Declarative canvas scenegraph & visualization for [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom). +[@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) component wrapper for declarative canvas scenegraphs. This package provides a [re-usable canvas component](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-canvas/src/index.ts#L66), @@ -71,10 +43,17 @@ API draw calls during the hdom update process / cycle. **STABLE** - used in production +### BREAKING CHANGES 3.0.0 + +The actual tree traversal & drawing has been extracted to the new +[@thi.ng/hiccup-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-canvas) +package for better re-usability, also outside without hdom. + ### Related packages - [@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom) - Functional, polymorphic API for 2D geometry types & SVG generation - [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) - Lightweight vanilla ES6 UI component trees with customizable branch-local behaviors +- [@thi.ng/hiccup-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-canvas) - Hiccup shape tree renderer for vanilla Canvas 2D contexts - [@thi.ng/hiccup-svg](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-svg) - SVG element functions for [@thi.ng/hiccup](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup) & [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) ## Installation @@ -91,18 +70,15 @@ yarn add @thi.ng/hdom-canvas ``` -Package sizes (gzipped, pre-treeshake): ESM: 3.19 KB / CJS: 3.33 KB / UMD: 3.31 KB +Package sizes (gzipped, pre-treeshake): ESM: 910 bytes / CJS: 979 bytes / UMD: 1.05 KB ## Dependencies - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api) - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks) -- [@thi.ng/color](https://github.com/thi-ng/umbrella/tree/develop/packages/color) - [@thi.ng/diff](https://github.com/thi-ng/umbrella/tree/develop/packages/diff) - [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) -- [@thi.ng/math](https://github.com/thi-ng/umbrella/tree/develop/packages/math) -- [@thi.ng/vectors](https://github.com/thi-ng/umbrella/tree/develop/packages/vectors) -- [tslib](https://github.com/thi-ng/umbrella/tree/develop/packages/undefined) +- [@thi.ng/hiccup-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-canvas) ## Usage examples @@ -290,336 +266,10 @@ serialize( ## Supported shape types -In the near future, factory functions for these shape types will be -provided... - -### Group - -```ts -["g", attribs, child1, child2, ...] -``` - -Attributes defined at group level are inherited by child elements. - -### Definition group - -```ts -["defs", {}, def1, def2, ...] -``` - -Special group / container for [gradient definitions](#gradients). If -used, should always come first in a scene tree. - -### Circle - -```ts -["circle", attribs, [x, y], radius] -``` - -### Circular arc - -```ts -["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). - -### Ellipse / elliptic arc - -```ts -["ellipse", attribs, [x, y], [rx, ry], axisTheta?, start?, end?, ccw?] -``` - -### Rect - -```ts -["rect", attribs, [x, y], w, h, radius?] -``` - -If `radius` is given, creates a rounded rectangle. `radius` will be -clamped to `Math.min(w, h)/2`. - -### Line - -```ts -["line", attribs, [x1, y1], [x2, y2]] -``` - -### Horizontal Line - -```ts -["hline", attribs, y] -``` - -### Vertical Line - -```ts -["vline", attribs, x] -``` - -### Polyline / Polygon - -```ts -["polyline", attribs, [[x1, y1], [x2, y2], [x3, y3]...]] -``` - -Always non-filled (even if `fill` attrib is given or inherited) - -```ts -["polygon", attribs, [[x1, y1], [x2, y2], [x3, y3]...]] -``` - -Always closed, can be filled and/or stroked. - -### Path - -```ts -["path", attribs, [seg1, seg2, ...]] -``` - -Path segments are tuples of `[type, [x,y]...]`. The following segment -types are supported and (as with SVG), absolute and relative versions -can be used. Relative versions use lowercase letters and are always -relative to the end point of the previous segment. The first segment -(usually of type `"M"`) must be absolute. - -| Format | Description | -|--------------------------------------|--------------------------| -| `["M", [x, y]]` | Move | -| `["L", [x, y]]` | Line | -| `["H", x]` | Horizontal line | -| `["V", y]` | Vertical line | -| `["C", [x1,y1], [x2, y2], [x3, y3]]` | Cubic / bezier curve | -| `["Q", [x1,y1], [x2, y2]]` | Quadratic curve | -| `["A", [x1,y1], [x2, y2], r]` | Circular arc (see below) | -| `["Z"]` | Close (sub)path | - -#### SVG paths with arc segments - -**IMPORTANT:** Currently, due to differences between SVG and canvas API -arc handling, SVG paths containing arc segments are **NOT** compatible -with the above format. [This -issue](https://github.com/thi-ng/umbrella/issues/69) is being worked on, -but in the meantime, to use such paths, these should first be converted -to use cubics or polygon / polyline. E.g. here using -[@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom): - -```ts -import { normalizedPath, pathFromSVG, asPolyline } from "@thi.ng/geom"; - -// path w/ arc segments (*not* usable by hdom-canvas) -const a = pathFromSvg("M0,0H80A20,20,0,0,1,100,20V30A20,20,0,0,1,80,50")[0]; - -// normalized to only use cubic curves (usable by hdom-canvas) -const b = normalizedPath(a); - -// converted to polyline (usable by hdom-canvas) -const c = asPolyline(a); - -asSvg(b); -// - -asSvg(c); -// -``` - -### Points - -```ts -["points", attribs, [[x1,y1], [x2,y2],...]] -``` - -The following shape specific attributes are used: - -- `shape`: `circle` or `rect` (default) -- `size`: point size (radius for circles, width for rects) - default: 1 - -### Packed points - -Similar to `points`, but uses a single packed buffer for all point -coordinates. - -```ts -["packedPoints", attribs, [x1,y1, x2,y2,...]] -``` - -Optional start index, number of points, component & point stride lengths -(number of indices between each vector component and each point -respectively) can be given as attributes. - -Defaults: - -- start index: 0 -- number of points: (array_length - start) / estride -- component stride: 1 -- element stride: 2 - -```ts -["packedPoints", { cstride: 1, estride: 4 }, - [x1, y1, 0, 0, x2, y2, 0, 0, ...]] - -["packedPoints", { offset: 8, num: 3, cstride: 4, estride: 1 }, - [0, 0, 0, 0, 0, 0, 0, 0, x1, x2, x3, 0, y1, y2, y3, 0...]] -``` - -### Text - -```ts -["text", attribs, [x,y], "body...", maxWidth?] -``` - -### Image - -```ts -["img", { width?, height? }, img, dpos, spos?, ssize?] -``` - -**IMPORTANT**: Since v2.0.0 this element has new/changed args... - -`img` MUST be an HTML image, canvas or video element. `dpos`, `spos`, -`ssize` are 2D vectors. The latter two are optional, as are `width` and `height` attribs. Defaults: - -- `width` - original image width -- `height` - original image height -- `spos` - `[0,0]` -- `ssize` - `[width, height]` - -Note: For SVG conversion `spos` & `ssize` will be ignored. Sub-image -blitting is not supported in SVG. - -### Gradients - -Gradients MUST be defined within a root-level `defs` group, which itself -MUST be given prior to any other shapes. Use the `$` prefix to refer to -a gradient in a `fill` or `stroke` attribute, e.g. `{stroke: "$foo" }` - -```ts -["linearGradient", - {id: "foo", from: [x1,y1], to: [x2, y2]}, - [[offset1, color1], [offset2, color2], ...] -] -``` - -```ts -["radialGradient", - {id: "foo", from: [x1,y1], to: [x2, y2], r1: r1, r2: r2 }, - [[offset1, color1], [offset2, color2], ...] -] -``` - -## Attributes - -Some attributes use different names than their actual names in the -`CanvasRenderingContext2D`: - -| Attribute | Context 2D property | -|-------------|--------------------------| -| align | textAlign | -| alpha | globalAlpha | -| baseline | textBaseline | -| compose | globalCompositeOperation | -| dash | setLineDash | -| dashOffset | lineDashOffset | -| direction | direction | -| fill | fillStyle | -| filter | filter | -| font | font | -| lineCap | lineCap | -| lineJoin | lineJoin | -| miterLimit | miterLimit | -| shadowBlur | shadowBlur | -| shadowColor | shadowColor | -| shadowX | shadowOffsetX | -| shadowY | shadowOffsetY | -| smooth | imageSmoothingEnabled | -| stroke | strokeStyle | -| weight | lineWidth | - -### Color attributes - -Color conversions are only applied to `fill`, `stroke`, `shadowColor` -attributes and color stops provided to gradient definitions. - -#### String - -String color attribs prefixed with `$` are replaced with `url(#...)` -refs (e.g. to refer to gradients), else used as is (untransformed) - -#### Number - -Interpreted as ARGB hex value: - -`{ fill: 0xffaabbcc }` => `{ fill: "#aabbcc" }` - -#### Array - -Interpreted as float RGB(A): - -`{ fill: [1, 0.8, 0.6, 0.4] }` => `{ fill: "rgba(255,204,153,0.40)" }` - -#### @thi.ng/color values - -Colors defined via the -[@thi.ng/color](https://github.com/thi-ng/umbrella/tree/develop/packages/color) -package can be automatically converted to CSS color strings: - -`{ fill: hcya(0.1666, 1, 0.8859) }` => `{ fill: "#ffff00" }` - -### Coordinate transformations - -Coordinate system transformations can be achieved via the following -attributes (for groups and individual shapes). -Nested transformations are supported. - -If using a combination of `translate`, `scale` and/or `rotate` attribs, -the order of application is always TRS. - -#### Transform matrix - -```ts -{ transform: [xx, xy, yx, yy, ox, oy] } -``` - -#### Override transform - -```ts -{ setTransform: [xx, xy, yx, yy, ox, oy] } -``` - -Similar to `transform` but completely overrides transformation matrix, -rather than concatenating with existing one. - -See [MDN -docs](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform) -for further details. - -Also see the [2x3 matrix functions in the -@thi.ng/matrices](https://github.com/thi-ng/umbrella/tree/develop/packages/matrices/README.md) -package for creating different kinds of transformation matrices, e.g. - -``` -{ transform: skewX23([], Math.PI / 12) } -``` - -#### Translation - -```ts -{ translate: [x, y] } -``` - -#### Scaling - -```ts -{ scale: [x, y] } // non-uniform -{ scale: x } // uniform -``` - -#### Rotation - -```ts -{ rotate: theta } // in radians -``` +Please see the [@thi.ng/hiccup-canvas +README](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-canvas/README.md#supported-shape-types) +for the full list of supported shapes, gradients, attributes, colors and +transformations. ## Authors diff --git a/packages/hdom-canvas/tpl.readme.md b/packages/hdom-canvas/tpl.readme.md index 469684e38e..0ab9f98afe 100644 --- a/packages/hdom-canvas/tpl.readme.md +++ b/packages/hdom-canvas/tpl.readme.md @@ -23,6 +23,12 @@ API draw calls during the hdom update process / cycle. ${status} +### BREAKING CHANGES 3.0.0 + +The actual tree traversal & drawing has been extracted to the new +[@thi.ng/hiccup-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-canvas) +package for better re-usability, also outside without hdom. + ${supportPackages} ${relatedPackages} @@ -201,337 +207,10 @@ serialize( ## Supported shape types -In the near future, factory functions for these shape types will be -provided... - - -### Group - -```ts -["g", attribs, child1, child2, ...] -``` - -Attributes defined at group level are inherited by child elements. - -### Definition group - -```ts -["defs", {}, def1, def2, ...] -``` - -Special group / container for [gradient definitions](#gradients). If -used, should always come first in a scene tree. - -### Circle - -```ts -["circle", attribs, [x, y], radius] -``` - -### Circular arc - -```ts -["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). - -### Ellipse / elliptic arc - -```ts -["ellipse", attribs, [x, y], [rx, ry], axisTheta?, start?, end?, ccw?] -``` - -### Rect - -```ts -["rect", attribs, [x, y], w, h, radius?] -``` - -If `radius` is given, creates a rounded rectangle. `radius` will be -clamped to `Math.min(w, h)/2`. - -### Line - -```ts -["line", attribs, [x1, y1], [x2, y2]] -``` - -### Horizontal Line - -```ts -["hline", attribs, y] -``` - -### Vertical Line - -```ts -["vline", attribs, x] -``` - -### Polyline / Polygon - -```ts -["polyline", attribs, [[x1, y1], [x2, y2], [x3, y3]...]] -``` - -Always non-filled (even if `fill` attrib is given or inherited) - -```ts -["polygon", attribs, [[x1, y1], [x2, y2], [x3, y3]...]] -``` - -Always closed, can be filled and/or stroked. - -### Path - -```ts -["path", attribs, [seg1, seg2, ...]] -``` - -Path segments are tuples of `[type, [x,y]...]`. The following segment -types are supported and (as with SVG), absolute and relative versions -can be used. Relative versions use lowercase letters and are always -relative to the end point of the previous segment. The first segment -(usually of type `"M"`) must be absolute. - -| Format | Description | -|--------------------------------------|--------------------------| -| `["M", [x, y]]` | Move | -| `["L", [x, y]]` | Line | -| `["H", x]` | Horizontal line | -| `["V", y]` | Vertical line | -| `["C", [x1,y1], [x2, y2], [x3, y3]]` | Cubic / bezier curve | -| `["Q", [x1,y1], [x2, y2]]` | Quadratic curve | -| `["A", [x1,y1], [x2, y2], r]` | Circular arc (see below) | -| `["Z"]` | Close (sub)path | - -#### SVG paths with arc segments - -**IMPORTANT:** Currently, due to differences between SVG and canvas API -arc handling, SVG paths containing arc segments are **NOT** compatible -with the above format. [This -issue](https://github.com/thi-ng/umbrella/issues/69) is being worked on, -but in the meantime, to use such paths, these should first be converted -to use cubics or polygon / polyline. E.g. here using -[@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom): - -```ts -import { normalizedPath, pathFromSVG, asPolyline } from "@thi.ng/geom"; - -// path w/ arc segments (*not* usable by hdom-canvas) -const a = pathFromSvg("M0,0H80A20,20,0,0,1,100,20V30A20,20,0,0,1,80,50")[0]; - -// normalized to only use cubic curves (usable by hdom-canvas) -const b = normalizedPath(a); - -// converted to polyline (usable by hdom-canvas) -const c = asPolyline(a); - -asSvg(b); -// - -asSvg(c); -// -``` - -### Points - -```ts -["points", attribs, [[x1,y1], [x2,y2],...]] -``` - -The following shape specific attributes are used: - -- `shape`: `circle` or `rect` (default) -- `size`: point size (radius for circles, width for rects) - default: 1 - -### Packed points - -Similar to `points`, but uses a single packed buffer for all point -coordinates. - -```ts -["packedPoints", attribs, [x1,y1, x2,y2,...]] -``` - -Optional start index, number of points, component & point stride lengths -(number of indices between each vector component and each point -respectively) can be given as attributes. - -Defaults: - -- start index: 0 -- number of points: (array_length - start) / estride -- component stride: 1 -- element stride: 2 - -```ts -["packedPoints", { cstride: 1, estride: 4 }, - [x1, y1, 0, 0, x2, y2, 0, 0, ...]] - -["packedPoints", { offset: 8, num: 3, cstride: 4, estride: 1 }, - [0, 0, 0, 0, 0, 0, 0, 0, x1, x2, x3, 0, y1, y2, y3, 0...]] -``` - -### Text - -```ts -["text", attribs, [x,y], "body...", maxWidth?] -``` - -### Image - -```ts -["img", { width?, height? }, img, dpos, spos?, ssize?] -``` - -**IMPORTANT**: Since v2.0.0 this element has new/changed args... - -`img` MUST be an HTML image, canvas or video element. `dpos`, `spos`, -`ssize` are 2D vectors. The latter two are optional, as are `width` and `height` attribs. Defaults: - -- `width` - original image width -- `height` - original image height -- `spos` - `[0,0]` -- `ssize` - `[width, height]` - -Note: For SVG conversion `spos` & `ssize` will be ignored. Sub-image -blitting is not supported in SVG. - -### Gradients - -Gradients MUST be defined within a root-level `defs` group, which itself -MUST be given prior to any other shapes. Use the `$` prefix to refer to -a gradient in a `fill` or `stroke` attribute, e.g. `{stroke: "$foo" }` - -```ts -["linearGradient", - {id: "foo", from: [x1,y1], to: [x2, y2]}, - [[offset1, color1], [offset2, color2], ...] -] -``` - -```ts -["radialGradient", - {id: "foo", from: [x1,y1], to: [x2, y2], r1: r1, r2: r2 }, - [[offset1, color1], [offset2, color2], ...] -] -``` - -## Attributes - -Some attributes use different names than their actual names in the -`CanvasRenderingContext2D`: - -| Attribute | Context 2D property | -|-------------|--------------------------| -| align | textAlign | -| alpha | globalAlpha | -| baseline | textBaseline | -| compose | globalCompositeOperation | -| dash | setLineDash | -| dashOffset | lineDashOffset | -| direction | direction | -| fill | fillStyle | -| filter | filter | -| font | font | -| lineCap | lineCap | -| lineJoin | lineJoin | -| miterLimit | miterLimit | -| shadowBlur | shadowBlur | -| shadowColor | shadowColor | -| shadowX | shadowOffsetX | -| shadowY | shadowOffsetY | -| smooth | imageSmoothingEnabled | -| stroke | strokeStyle | -| weight | lineWidth | - -### Color attributes - -Color conversions are only applied to `fill`, `stroke`, `shadowColor` -attributes and color stops provided to gradient definitions. - -#### String - -String color attribs prefixed with `$` are replaced with `url(#...)` -refs (e.g. to refer to gradients), else used as is (untransformed) - -#### Number - -Interpreted as ARGB hex value: - -`{ fill: 0xffaabbcc }` => `{ fill: "#aabbcc" }` - -#### Array - -Interpreted as float RGB(A): - -`{ fill: [1, 0.8, 0.6, 0.4] }` => `{ fill: "rgba(255,204,153,0.40)" }` - -#### @thi.ng/color values - -Colors defined via the -[@thi.ng/color](https://github.com/thi-ng/umbrella/tree/develop/packages/color) -package can be automatically converted to CSS color strings: - -`{ fill: hcya(0.1666, 1, 0.8859) }` => `{ fill: "#ffff00" }` - -### Coordinate transformations - -Coordinate system transformations can be achieved via the following -attributes (for groups and individual shapes). -Nested transformations are supported. - -If using a combination of `translate`, `scale` and/or `rotate` attribs, -the order of application is always TRS. - -#### Transform matrix - -```ts -{ transform: [xx, xy, yx, yy, ox, oy] } -``` - -#### Override transform - -```ts -{ setTransform: [xx, xy, yx, yy, ox, oy] } -``` - -Similar to `transform` but completely overrides transformation matrix, -rather than concatenating with existing one. - -See [MDN -docs](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform) -for further details. - -Also see the [2x3 matrix functions in the -@thi.ng/matrices](https://github.com/thi-ng/umbrella/tree/develop/packages/matrices/README.md) -package for creating different kinds of transformation matrices, e.g. - -``` -{ transform: skewX23([], Math.PI / 12) } -``` - -#### Translation - -```ts -{ translate: [x, y] } -``` - -#### Scaling - -```ts -{ scale: [x, y] } // non-uniform -{ scale: x } // uniform -``` - -#### Rotation - -```ts -{ rotate: theta } // in radians -``` +Please see the [@thi.ng/hiccup-canvas +README](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-canvas/README.md#supported-shape-types) +for the full list of supported shapes, gradients, attributes, colors and +transformations. ## Authors From a59bb0923f37677d6579aede0dbe9958b0150d81 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 14:14:33 +0100 Subject: [PATCH 14/25] feat(hiccup-canvas): add IToHiccup support in draw --- packages/hiccup-canvas/src/draw.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/hiccup-canvas/src/draw.ts b/packages/hiccup-canvas/src/draw.ts index 53747f4ca6..ea93e1d02c 100644 --- a/packages/hiccup-canvas/src/draw.ts +++ b/packages/hiccup-canvas/src/draw.ts @@ -1,4 +1,4 @@ -import { isArray } from "@thi.ng/checks"; +import { implementsFunction, isArray } from "@thi.ng/checks"; import type { DrawState } from "./api"; import { circularArc, ellipticArc } from "./arc"; import { defLinearGradient, defRadialGradient } from "./color"; @@ -15,10 +15,14 @@ import { text } from "./text"; export const draw = ( ctx: CanvasRenderingContext2D, - shape: any[], + shape: any, pstate: DrawState = { attribs: {}, edits: [] } ) => { if (!shape) return; + if (implementsFunction(shape, "toHiccup")) { + draw(ctx, shape.toHiccup(), pstate); + return; + } if (isArray(shape[0])) { for (let s of shape) { draw(ctx, s, pstate); From 1b212dfa9c02860adc7960a372c71c02b366f19d Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 14:18:15 +0100 Subject: [PATCH 15/25] docs(hiccup-canvas): add readme --- packages/hiccup-canvas/README.md | 492 +++++++++++++++++++++++++++ packages/hiccup-canvas/tpl.readme.md | 422 +++++++++++++++++++++++ 2 files changed, 914 insertions(+) create mode 100644 packages/hiccup-canvas/README.md create mode 100644 packages/hiccup-canvas/tpl.readme.md diff --git a/packages/hiccup-canvas/README.md b/packages/hiccup-canvas/README.md new file mode 100644 index 0000000000..854e679821 --- /dev/null +++ b/packages/hiccup-canvas/README.md @@ -0,0 +1,492 @@ + + +# ![hiccup-canvas](https://media.thi.ng/umbrella/banners/thing-hiccup-canvas.svg?c8854f58) + +[![npm version](https://img.shields.io/npm/v/@thi.ng/hiccup-canvas.svg)](https://www.npmjs.com/package/@thi.ng/hiccup-canvas) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/hiccup-canvas.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +- [About](#about) + - [Status](#status) + - [Related packages](#related-packages) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) +- [API](#api) +- [SVG conversion](#svg-conversion) +- [Supported shape types](#supported-shape-types) + - [Group](#group) + - [Definition group](#definition-group) + - [Circle](#circle) + - [Circular arc](#circular-arc) + - [Ellipse / elliptic arc](#ellipse---elliptic-arc) + - [Rect](#rect) + - [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) + - [Packed points](#packed-points) + - [Text](#text) + - [Image](#image) + - [Gradients](#gradients) +- [Attributes](#attributes) + - [Color attributes](#color-attributes) + - [String](#string) + - [Number](#number) + - [Array](#array) + - [@thi.ng/color values](#thing-color-values) + - [Coordinate transformations](#coordinate-transformations) + - [Transform matrix](#transform-matrix) + - [Override transform](#override-transform) + - [Translation](#translation) + - [Scaling](#scaling) + - [Rotation](#rotation) +- [Authors](#authors) +- [License](#license) + +## About + +Hiccup shape tree renderer for vanilla Canvas 2D contexts. + +This package provides a simple `draw()` function, which accepts a scene +tree of different shape types in +[@thi.ng/hiccup](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup) +syntax/format (i.e. nested arrays, +[`IToHiccup`](https://github.com/thi-ng/umbrella/blob/develop/packages/api/src/api/hiccup.ts) +implementations) and then translates these into canvas API draw calls. + +### Status + +**STABLE** - used in production + +### Related packages + +- [@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-canvas) - [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) component wrapper for declarative canvas scenegraphs +- [@thi.ng/hiccup](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup) - HTML/SVG/XML serialization of nested data structures, iterables & closures + +## Installation + +```bash +yarn add @thi.ng/hiccup-canvas +``` + +```html +// ES module + + +// UMD + +``` + +Package sizes (gzipped, pre-treeshake): ESM: 2.51 KB / CJS: 2.62 KB / UMD: 2.61 KB + +## Dependencies + +- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api) +- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks) +- [@thi.ng/color](https://github.com/thi-ng/umbrella/tree/develop/packages/color) +- [@thi.ng/math](https://github.com/thi-ng/umbrella/tree/develop/packages/math) +- [@thi.ng/vectors](https://github.com/thi-ng/umbrella/tree/develop/packages/vectors) + +## Usage examples + +Several demos in this repo's +[/examples](https://github.com/thi-ng/umbrella/tree/develop/examples) +directory are using this package. + +A selection: + +| Screenshot | Description | Live demo | Source | +| ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| | Realtime analog clock demo | [Demo](https://demo.thi.ng/umbrella/hdom-canvas-clock/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-clock) | +| | Interactive pattern drawing demo using transducers | [Demo](https://demo.thi.ng/umbrella/hdom-canvas-draw/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-draw) | +| | 2D Bezier curve-guided particle system | [Demo](https://demo.thi.ng/umbrella/hdom-canvas-particles/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-particles) | +| | Various hdom-canvas shape drawing examples & SVG conversion / export | [Demo](https://demo.thi.ng/umbrella/hdom-canvas-shapes/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-shapes) | +| | Animated arcs & drawing using hiccup-canvas | [Demo](https://demo.thi.ng/umbrella/hiccup-canvas-arcs/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/hiccup-canvas-arcs) | + +## API + +[Generated API docs](https://docs.thi.ng/umbrella/hiccup-canvas/) + +The shape tree given to `draw()` MUST consist of standard, normalized +hiccup syntax (incl. objects implementing the `IToHiccup()` interface, +like the shape types provided by +[@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom)). + +## SVG conversion + +Even though the shape element names & syntax are intentionally *very +similar* (largely the same) to SVG elements, for performance reasons all +geometry data given to each shape remains un-stringified (only styling +attributes can be strings). However, the +[@thi.ng/hiccup-svg](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-svg) +package provides a `convertTree()` function which takes the arguably +more "raw" shape format used by this package and converts an entire +shape tree into SVG compatible & serializable format. + +It's very likely (and recommended) you're using the shape type provided [@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom), in which case these can be provided as is to this package's `draw()` function and SVG conversion can be done like so: + +```ts +import { asSvg, svgDoc, group, circle } from "@thi.ng/geom"; +import { draw } from "@thi.ng/hiccup-canvas"; + +const dots = group({}, [ + circle([100, 100], 20, { fill: "red" }), + circle([140, 100], 20, { fill: "green" }), + circle([160, 100], 20, { fill: "blue" }), +]); + +// draw to canvas +draw(canvas.getContext("2d"), dots); + +// convert to SVG +// (unless given, width, height and viewBox will be auto-computed) +asSvg(svgDoc({}, dots)) +``` + +## Supported shape types + +In the near future, factory functions for these shape types will be +provided... + +### Group + +```ts +["g", attribs, child1, child2, ...] +``` + +Attributes defined at group level are inherited by child elements. + +### Definition group + +```ts +["defs", {}, def1, def2, ...] +``` + +Special group / container for [gradient definitions](#gradients). If +used, should always come first in a scene tree. + +### Circle + +```ts +["circle", attribs, [x, y], radius] +``` + +### Circular arc + +```ts +["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). + +### Ellipse / elliptic arc + +```ts +["ellipse", attribs, [x, y], [rx, ry], axisTheta?, start?, end?, ccw?] +``` + +### Rect + +```ts +["rect", attribs, [x, y], w, h, radius?] +``` + +If `radius` is given, creates a rounded rectangle. `radius` will be +clamped to `Math.min(w, h)/2`. + +### Line + +```ts +["line", attribs, [x1, y1], [x2, y2]] +``` + +### Horizontal Line + +```ts +["hline", attribs, y] +``` + +### Vertical Line + +```ts +["vline", attribs, x] +``` + +### Polyline / Polygon + +```ts +["polyline", attribs, [[x1, y1], [x2, y2], [x3, y3]...]] +``` + +Always non-filled (even if `fill` attrib is given or inherited) + +```ts +["polygon", attribs, [[x1, y1], [x2, y2], [x3, y3]...]] +``` + +Always closed, can be filled and/or stroked. + +### Path + +```ts +["path", attribs, [seg1, seg2, ...]] +``` + +Path segments are tuples of `[type, [x,y]...]`. The following segment +types are supported and (as with SVG), absolute and relative versions +can be used. Relative versions use lowercase letters and are always +relative to the end point of the previous segment. The first segment +(usually of type `"M"`) must be absolute. + +| Format | Description | +|--------------------------------------|--------------------------| +| `["M", [x, y]]` | Move | +| `["L", [x, y]]` | Line | +| `["H", x]` | Horizontal line | +| `["V", y]` | Vertical line | +| `["C", [x1,y1], [x2, y2], [x3, y3]]` | Cubic / bezier curve | +| `["Q", [x1,y1], [x2, y2]]` | Quadratic curve | +| `["A", [x1,y1], [x2, y2], r]` | Circular arc (see below) | +| `["Z"]` | Close (sub)path | + +#### SVG paths with arc segments + +**IMPORTANT:** Currently, due to differences between SVG and canvas API +arc handling, SVG paths containing arc segments are **NOT** compatible +with the above format. [This +issue](https://github.com/thi-ng/umbrella/issues/69) is being worked on, +but in the meantime, to use such paths, these should first be converted +to use cubics or polygon / polyline. E.g. here using +[@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom): + +```ts +import { normalizedPath, pathFromSVG, asPolyline } from "@thi.ng/geom"; + +// path w/ arc segments (*not* usable by hiccup-canvas) +const a = pathFromSvg("M0,0H80A20,20,0,0,1,100,20V30A20,20,0,0,1,80,50")[0]; + +// normalized to only use cubic curves (usable by hiccup-canvas) +const b = normalizedPath(a); + +// converted to polyline (usable by hiccup-canvas) +const c = asPolyline(a); + +asSvg(b); +// + +asSvg(c); +// +``` + +### Points + +```ts +["points", attribs, [[x1,y1], [x2,y2],...]] +``` + +The following shape specific attributes are used: + +- `shape`: `circle` or `rect` (default) +- `size`: point size (radius for circles, width for rects) - default: 1 + +### Packed points + +Similar to `points`, but uses a single packed buffer for all point +coordinates. + +```ts +["packedPoints", attribs, [x1,y1, x2,y2,...]] +``` + +Optional start index, number of points, component & point stride lengths +(number of indices between each vector component and each point +respectively) can be given as attributes. + +Defaults: + +- start index: 0 +- number of points: (array_length - start) / estride +- component stride: 1 +- element stride: 2 + +```ts +["packedPoints", { cstride: 1, estride: 4 }, + [x1, y1, 0, 0, x2, y2, 0, 0, ...]] + +["packedPoints", { offset: 8, num: 3, cstride: 4, estride: 1 }, + [0, 0, 0, 0, 0, 0, 0, 0, x1, x2, x3, 0, y1, y2, y3, 0...]] +``` + +### Text + +```ts +["text", attribs, [x,y], "body...", maxWidth?] +``` + +### Image + +```ts +["img", { width?, height? }, img, dpos, spos?, ssize?] +``` + +**IMPORTANT**: Since v2.0.0 this element has new/changed args... + +`img` MUST be an HTML image, canvas or video element. `dpos`, `spos`, +`ssize` are 2D vectors. The latter two are optional, as are `width` and `height` attribs. Defaults: + +- `width` - original image width +- `height` - original image height +- `spos` - `[0,0]` +- `ssize` - `[width, height]` + +Note: For SVG conversion `spos` & `ssize` will be ignored. Sub-image +blitting is not supported in SVG. + +### Gradients + +Gradients MUST be defined within a root-level `defs` group, which itself +MUST be given prior to any other shapes. Use the `$` prefix to refer to +a gradient in a `fill` or `stroke` attribute, e.g. `{stroke: "$foo" }` + +```ts +["linearGradient", + {id: "foo", from: [x1,y1], to: [x2, y2]}, + [[offset1, color1], [offset2, color2], ...] +] +``` + +```ts +["radialGradient", + {id: "foo", from: [x1,y1], to: [x2, y2], r1: r1, r2: r2 }, + [[offset1, color1], [offset2, color2], ...] +] +``` + +## Attributes + +Some attributes use different names than their actual names in the +`CanvasRenderingContext2D`: + +| Attribute | Context 2D property | +|-------------|--------------------------| +| align | textAlign | +| alpha | globalAlpha | +| baseline | textBaseline | +| compose | globalCompositeOperation | +| dash | setLineDash | +| dashOffset | lineDashOffset | +| direction | direction | +| fill | fillStyle | +| filter | filter | +| font | font | +| lineCap | lineCap | +| lineJoin | lineJoin | +| miterLimit | miterLimit | +| shadowBlur | shadowBlur | +| shadowColor | shadowColor | +| shadowX | shadowOffsetX | +| shadowY | shadowOffsetY | +| smooth | imageSmoothingEnabled | +| stroke | strokeStyle | +| weight | lineWidth | + +### Color attributes + +Color conversions are only applied to `fill`, `stroke`, `shadowColor` +attributes and color stops provided to gradient definitions. + +#### String + +String color attribs prefixed with `$` are replaced with `url(#...)` +refs (e.g. to refer to gradients), else used as is (untransformed) + +#### Number + +Interpreted as ARGB hex value: + +`{ fill: 0xffaabbcc }` => `{ fill: "#aabbcc" }` + +#### Array + +Interpreted as float RGB(A): + +`{ fill: [1, 0.8, 0.6, 0.4] }` => `{ fill: "rgba(255,204,153,0.40)" }` + +#### @thi.ng/color values + +Colors defined via the +[@thi.ng/color](https://github.com/thi-ng/umbrella/tree/develop/packages/color) +package can be automatically converted to CSS color strings: + +`{ fill: hcya(0.1666, 1, 0.8859) }` => `{ fill: "#ffff00" }` + +### Coordinate transformations + +Coordinate system transformations can be achieved via the following +attributes (for groups and individual shapes). +Nested transformations are supported. + +If using a combination of `translate`, `scale` and/or `rotate` attribs, +the order of application is always TRS. + +#### Transform matrix + +```ts +{ transform: [xx, xy, yx, yy, ox, oy] } +``` + +#### Override transform + +```ts +{ setTransform: [xx, xy, yx, yy, ox, oy] } +``` + +Similar to `transform` but completely overrides transformation matrix, +rather than concatenating with existing one. + +See [MDN +docs](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform) +for further details. + +Also see the [2x3 matrix functions in the +@thi.ng/matrices](https://github.com/thi-ng/umbrella/tree/develop/packages/matrices/README.md) +package for creating different kinds of transformation matrices, e.g. + +``` +{ transform: skewX23([], Math.PI / 12) } +``` + +#### Translation + +```ts +{ translate: [x, y] } +``` + +#### Scaling + +```ts +{ scale: [x, y] } // non-uniform +{ scale: x } // uniform +``` + +#### Rotation + +```ts +{ rotate: theta } // in radians +``` + +## Authors + +Karsten Schmidt + +## License + +© 2018 - 2020 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/hiccup-canvas/tpl.readme.md b/packages/hiccup-canvas/tpl.readme.md new file mode 100644 index 0000000000..cd056d2d0e --- /dev/null +++ b/packages/hiccup-canvas/tpl.readme.md @@ -0,0 +1,422 @@ +# ${pkg.banner} + +[![npm version](https://img.shields.io/npm/v/${pkg.name}.svg)](https://www.npmjs.com/package/${pkg.name}) +![npm downloads](https://img.shields.io/npm/dm/${pkg.name}.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +## About + +${pkg.description} + +This package provides a simple `draw()` function, which accepts a scene +tree of different shape types in +[@thi.ng/hiccup](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup) +syntax/format (i.e. nested arrays, +[`IToHiccup`](https://github.com/thi-ng/umbrella/blob/develop/packages/api/src/api/hiccup.ts) +implementations) and then translates these into canvas API draw calls. + +${status} + +${supportPackages} + +${relatedPackages} + +${blogPosts} + +## Installation + +${pkg.install} + +${pkg.size} + +## Dependencies + +${pkg.deps} + +${examples} + +## API + +${docLink} + +The shape tree given to `draw()` MUST consist of standard, normalized +hiccup syntax (incl. objects implementing the `IToHiccup()` interface, +like the shape types provided by +[@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom)). + +## SVG conversion + +Even though the shape element names & syntax are intentionally *very +similar* (largely the same) to SVG elements, for performance reasons all +geometry data given to each shape remains un-stringified (only styling +attributes can be strings). However, the +[@thi.ng/hiccup-svg](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-svg) +package provides a `convertTree()` function which takes the arguably +more "raw" shape format used by this package and converts an entire +shape tree into SVG compatible & serializable format. + +It's very likely (and recommended) you're using the shape type provided [@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom), in which case these can be provided as is to this package's `draw()` function and SVG conversion can be done like so: + +```ts +import { asSvg, svgDoc, group, circle } from "@thi.ng/geom"; +import { draw } from "@thi.ng/hiccup-canvas"; + +const dots = group({}, [ + circle([100, 100], 20, { fill: "red" }), + circle([140, 100], 20, { fill: "green" }), + circle([160, 100], 20, { fill: "blue" }), +]); + +// draw to canvas +draw(canvas.getContext("2d"), dots); + +// convert to SVG +// (unless given, width, height and viewBox will be auto-computed) +asSvg(svgDoc({}, dots)) +``` + +## Supported shape types + +In the near future, factory functions for these shape types will be +provided... + +### Group + +```ts +["g", attribs, child1, child2, ...] +``` + +Attributes defined at group level are inherited by child elements. + +### Definition group + +```ts +["defs", {}, def1, def2, ...] +``` + +Special group / container for [gradient definitions](#gradients). If +used, should always come first in a scene tree. + +### Circle + +```ts +["circle", attribs, [x, y], radius] +``` + +### Circular arc + +```ts +["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). + +### Ellipse / elliptic arc + +```ts +["ellipse", attribs, [x, y], [rx, ry], axisTheta?, start?, end?, ccw?] +``` + +### Rect + +```ts +["rect", attribs, [x, y], w, h, radius?] +``` + +If `radius` is given, creates a rounded rectangle. `radius` will be +clamped to `Math.min(w, h)/2`. + +### Line + +```ts +["line", attribs, [x1, y1], [x2, y2]] +``` + +### Horizontal Line + +```ts +["hline", attribs, y] +``` + +### Vertical Line + +```ts +["vline", attribs, x] +``` + +### Polyline / Polygon + +```ts +["polyline", attribs, [[x1, y1], [x2, y2], [x3, y3]...]] +``` + +Always non-filled (even if `fill` attrib is given or inherited) + +```ts +["polygon", attribs, [[x1, y1], [x2, y2], [x3, y3]...]] +``` + +Always closed, can be filled and/or stroked. + +### Path + +```ts +["path", attribs, [seg1, seg2, ...]] +``` + +Path segments are tuples of `[type, [x,y]...]`. The following segment +types are supported and (as with SVG), absolute and relative versions +can be used. Relative versions use lowercase letters and are always +relative to the end point of the previous segment. The first segment +(usually of type `"M"`) must be absolute. + +| Format | Description | +|--------------------------------------|--------------------------| +| `["M", [x, y]]` | Move | +| `["L", [x, y]]` | Line | +| `["H", x]` | Horizontal line | +| `["V", y]` | Vertical line | +| `["C", [x1,y1], [x2, y2], [x3, y3]]` | Cubic / bezier curve | +| `["Q", [x1,y1], [x2, y2]]` | Quadratic curve | +| `["A", [x1,y1], [x2, y2], r]` | Circular arc (see below) | +| `["Z"]` | Close (sub)path | + +#### SVG paths with arc segments + +**IMPORTANT:** Currently, due to differences between SVG and canvas API +arc handling, SVG paths containing arc segments are **NOT** compatible +with the above format. [This +issue](https://github.com/thi-ng/umbrella/issues/69) is being worked on, +but in the meantime, to use such paths, these should first be converted +to use cubics or polygon / polyline. E.g. here using +[@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom): + +```ts +import { normalizedPath, pathFromSVG, asPolyline } from "@thi.ng/geom"; + +// path w/ arc segments (*not* usable by hiccup-canvas) +const a = pathFromSvg("M0,0H80A20,20,0,0,1,100,20V30A20,20,0,0,1,80,50")[0]; + +// normalized to only use cubic curves (usable by hiccup-canvas) +const b = normalizedPath(a); + +// converted to polyline (usable by hiccup-canvas) +const c = asPolyline(a); + +asSvg(b); +// + +asSvg(c); +// +``` + +### Points + +```ts +["points", attribs, [[x1,y1], [x2,y2],...]] +``` + +The following shape specific attributes are used: + +- `shape`: `circle` or `rect` (default) +- `size`: point size (radius for circles, width for rects) - default: 1 + +### Packed points + +Similar to `points`, but uses a single packed buffer for all point +coordinates. + +```ts +["packedPoints", attribs, [x1,y1, x2,y2,...]] +``` + +Optional start index, number of points, component & point stride lengths +(number of indices between each vector component and each point +respectively) can be given as attributes. + +Defaults: + +- start index: 0 +- number of points: (array_length - start) / estride +- component stride: 1 +- element stride: 2 + +```ts +["packedPoints", { cstride: 1, estride: 4 }, + [x1, y1, 0, 0, x2, y2, 0, 0, ...]] + +["packedPoints", { offset: 8, num: 3, cstride: 4, estride: 1 }, + [0, 0, 0, 0, 0, 0, 0, 0, x1, x2, x3, 0, y1, y2, y3, 0...]] +``` + +### Text + +```ts +["text", attribs, [x,y], "body...", maxWidth?] +``` + +### Image + +```ts +["img", { width?, height? }, img, dpos, spos?, ssize?] +``` + +**IMPORTANT**: Since v2.0.0 this element has new/changed args... + +`img` MUST be an HTML image, canvas or video element. `dpos`, `spos`, +`ssize` are 2D vectors. The latter two are optional, as are `width` and `height` attribs. Defaults: + +- `width` - original image width +- `height` - original image height +- `spos` - `[0,0]` +- `ssize` - `[width, height]` + +Note: For SVG conversion `spos` & `ssize` will be ignored. Sub-image +blitting is not supported in SVG. + +### Gradients + +Gradients MUST be defined within a root-level `defs` group, which itself +MUST be given prior to any other shapes. Use the `$` prefix to refer to +a gradient in a `fill` or `stroke` attribute, e.g. `{stroke: "$foo" }` + +```ts +["linearGradient", + {id: "foo", from: [x1,y1], to: [x2, y2]}, + [[offset1, color1], [offset2, color2], ...] +] +``` + +```ts +["radialGradient", + {id: "foo", from: [x1,y1], to: [x2, y2], r1: r1, r2: r2 }, + [[offset1, color1], [offset2, color2], ...] +] +``` + +## Attributes + +Some attributes use different names than their actual names in the +`CanvasRenderingContext2D`: + +| Attribute | Context 2D property | +|-------------|--------------------------| +| align | textAlign | +| alpha | globalAlpha | +| baseline | textBaseline | +| compose | globalCompositeOperation | +| dash | setLineDash | +| dashOffset | lineDashOffset | +| direction | direction | +| fill | fillStyle | +| filter | filter | +| font | font | +| lineCap | lineCap | +| lineJoin | lineJoin | +| miterLimit | miterLimit | +| shadowBlur | shadowBlur | +| shadowColor | shadowColor | +| shadowX | shadowOffsetX | +| shadowY | shadowOffsetY | +| smooth | imageSmoothingEnabled | +| stroke | strokeStyle | +| weight | lineWidth | + +### Color attributes + +Color conversions are only applied to `fill`, `stroke`, `shadowColor` +attributes and color stops provided to gradient definitions. + +#### String + +String color attribs prefixed with `$` are replaced with `url(#...)` +refs (e.g. to refer to gradients), else used as is (untransformed) + +#### Number + +Interpreted as ARGB hex value: + +`{ fill: 0xffaabbcc }` => `{ fill: "#aabbcc" }` + +#### Array + +Interpreted as float RGB(A): + +`{ fill: [1, 0.8, 0.6, 0.4] }` => `{ fill: "rgba(255,204,153,0.40)" }` + +#### @thi.ng/color values + +Colors defined via the +[@thi.ng/color](https://github.com/thi-ng/umbrella/tree/develop/packages/color) +package can be automatically converted to CSS color strings: + +`{ fill: hcya(0.1666, 1, 0.8859) }` => `{ fill: "#ffff00" }` + +### Coordinate transformations + +Coordinate system transformations can be achieved via the following +attributes (for groups and individual shapes). +Nested transformations are supported. + +If using a combination of `translate`, `scale` and/or `rotate` attribs, +the order of application is always TRS. + +#### Transform matrix + +```ts +{ transform: [xx, xy, yx, yy, ox, oy] } +``` + +#### Override transform + +```ts +{ setTransform: [xx, xy, yx, yy, ox, oy] } +``` + +Similar to `transform` but completely overrides transformation matrix, +rather than concatenating with existing one. + +See [MDN +docs](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform) +for further details. + +Also see the [2x3 matrix functions in the +@thi.ng/matrices](https://github.com/thi-ng/umbrella/tree/develop/packages/matrices/README.md) +package for creating different kinds of transformation matrices, e.g. + +``` +{ transform: skewX23([], Math.PI / 12) } +``` + +#### Translation + +```ts +{ translate: [x, y] } +``` + +#### Scaling + +```ts +{ scale: [x, y] } // non-uniform +{ scale: x } // uniform +``` + +#### Rotation + +```ts +{ rotate: theta } // in radians +``` + +## Authors + +${authors} + +## License + +© ${copyright} // ${license} From e11bc8ebcab2fad62f53f1a6ab78420e1415030c Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 14:29:06 +0100 Subject: [PATCH 16/25] docs: update readmes --- examples/README.md | 109 ++++++++++++++++--------------- packages/geom/README.md | 1 + packages/hdom/README.md | 2 +- packages/hiccup-canvas/README.md | 2 +- packages/hiccup/README.md | 3 +- packages/math/README.md | 1 - packages/unionstruct/README.md | 2 +- packages/vectors/README.md | 2 +- 8 files changed, 62 insertions(+), 60 deletions(-) diff --git a/examples/README.md b/examples/README.md index 3872f7c4c9..e7bf20e96b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,7 +1,7 @@ # @thi.ng/umbrella examples -This directory contains a growing number (currently 88) of standalone +This directory contains a growing number (currently 89) of standalone example projects, including live online versions, build instructions and commented source code. @@ -45,57 +45,58 @@ in touch via PR, issue tracker, email or twitter! | 033 | | [hdom-theme](./hdom-theme/) | Example for themed components proposal | | 034 | | [hdom-toggle](./hdom-toggle/) | Customizable slide toggle component demo | | 035 | | [hdom-vscroller](./hdom-vscroller/) | Virtual scroller component for large tables / lists | -| 036 | | [hmr-basics](./hmr-basics/) | hdom & hot module replacement | -| 037 | | [hydrate-basics](./hydrate-basics/) | Hiccup / hdom DOM hydration example | -| 038 | | [imgui](./imgui/) | Canvas based Immediate Mode GUI components | -| 039 | | [interceptor-basics](./interceptor-basics/) | Event handling w/ interceptors and side effects | -| 040 | | [interceptor-basics2](./interceptor-basics2/) | Event handling w/ interceptors and side effects | -| 041 | | [iso-plasma](./iso-plasma/) | Animated sine plasma effect visualized using contour lines | -| 042 | | [json-components](./json-components/) | Transforming JSON into UI components | -| 043 | | [login-form](./login-form/) | Basic SPA example with atom-based UI router | -| 044 | | [mandelbrot](./mandelbrot/) | Worker based, interactive Mandelbrot visualization | -| 045 | | [markdown](./markdown/) | Minimal Markdown to Hiccup to HTML parser / transformer | -| 046 | | [multitouch](./multitouch/) | Basic rstream-gestures multi-touch demo | -| 047 | | [package-stats](./package-stats/) | CLI util to visualize umbrella pkg stats | -| 048 | | [pixel-basics](./pixel-basics/) | Pixel buffer manipulations | -| 049 | | [pointfree-svg](./pointfree-svg/) | Generate SVG using pointfree DSL | -| 050 | | [poisson-circles](./poisson-circles/) | 2D Poisson-disc sampler with procedural gradient map | -| 051 | | [poly-spline](./poly-spline/) | Polygon to cubic curve conversion & visualization | -| 052 | | [porter-duff](./porter-duff/) | Port-Duff image compositing / alpha blending | -| 053 | | [ramp-synth](./ramp-synth/) | Unison wavetable synth with waveform editor | -| 054 | | [rotating-voronoi](./rotating-voronoi/) | Animated Voronoi diagram, cubic splines & SVG download | -| 055 | | [router-basics](./router-basics/) | Complete mini SPA app w/ router & async content loading | -| 056 | | [rstream-dataflow](./rstream-dataflow/) | Minimal rstream dataflow graph | -| 057 | | [rstream-event-loop](./rstream-event-loop/) | Minimal demo of using rstream constructs to form an interceptor-style event loop | -| 058 | | [rstream-grid](./rstream-grid/) | Interactive grid generator, SVG generation & export, undo/redo support | -| 059 | | [rstream-hdom](./rstream-hdom/) | rstream based UI updates & state handling | -| 060 | | [rstream-spreadsheet](./rstream-spreadsheet/) | rstream based spreadsheet w/ S-expression formula DSL | -| 061 | | [scenegraph](./scenegraph/) | 2D scenegraph & shape picking | -| 062 | | [scenegraph-image](./scenegraph-image/) | 2D scenegraph & image map based geometry manipulation | -| 063 | | [shader-ast-canvas2d](./shader-ast-canvas2d/) | 2D canvas shader emulation | -| 064 | | [shader-ast-evo](./shader-ast-evo/) | Evolutionary shader generation using genetic programming | -| 065 | | [shader-ast-noise](./shader-ast-noise/) | HOF shader procedural noise function composition | -| 066 | | [shader-ast-raymarch](./shader-ast-raymarch/) | WebGL & JS canvas2D raymarch shader cross-compilation | -| 067 | | [shader-ast-sdf2d](./shader-ast-sdf2d/) | WebGL & JS canvas 2D SDF | -| 068 | | [shader-ast-tunnel](./shader-ast-tunnel/) | WebGL & Canvas2D textured tunnel shader | -| 069 | | [shader-ast-workers](./shader-ast-workers/) | Fork-join worker-based raymarch renderer | -| 070 | | [soa-ecs](./soa-ecs/) | Entity Component System w/ 100k 3D particles | -| 071 | | [stratified-grid](./stratified-grid/) | 2D Stratified grid sampling example | -| 072 | | [svg-barchart](./svg-barchart/) | Simplistic SVG bar chart component | -| 073 | | [svg-particles](./svg-particles/) | Basic 2D particle system w/ SVG shapes | -| 074 | | [svg-waveform](./svg-waveform/) | Additive waveform synthesis & SVG visualization with undo/redo | -| 075 | | [talk-slides](./talk-slides/) | hdom based slide deck viewer & slides from my ClojureX 2018 keynote | -| 076 | | [text-canvas](./text-canvas/) | 3D wireframe textmode demo | -| 077 | | [todo-list](./todo-list/) | Obligatory to-do list example with undo/redo | -| 078 | | [transducers-hdom](./transducers-hdom/) | Transducer & rstream based hdom UI updates | -| 079 | | [triple-query](./triple-query/) | Triple store query results & sortable table | -| 080 | | [webgl-cube](./webgl-cube/) | WebGL multi-colored cube mesh | -| 081 | | [webgl-cubemap](./webgl-cubemap/) | WebGL cube maps with async texture loading | -| 082 | | [webgl-grid](./webgl-grid/) | WebGL instancing, animated grid | -| 083 | | [webgl-msdf](./webgl-msdf/) | WebGL MSDF text rendering & particle system | -| 084 | | [webgl-multipass](./webgl-multipass/) | Minimal multi-pass / GPGPU example | -| 085 | | [webgl-shadertoy](./webgl-shadertoy/) | Shadertoy-like WebGL setup | -| 086 | | [webgl-ssao](./webgl-ssao/) | WebGL screenspace ambient occlusion | -| 087 | | [wolfram](./wolfram/) | 1D Wolfram automata with OBJ point cloud export | -| 088 | | [xml-converter](./xml-converter/) | XML/HTML/SVG to hiccup/JS conversion | +| 036 | | [hiccup-canvas-arcs](./hiccup-canvas-arcs/) | Animated arcs & drawing using hiccup-canvas | +| 037 | | [hmr-basics](./hmr-basics/) | hdom & hot module replacement | +| 038 | | [hydrate-basics](./hydrate-basics/) | Hiccup / hdom DOM hydration example | +| 039 | | [imgui](./imgui/) | Canvas based Immediate Mode GUI components | +| 040 | | [interceptor-basics](./interceptor-basics/) | Event handling w/ interceptors and side effects | +| 041 | | [interceptor-basics2](./interceptor-basics2/) | Event handling w/ interceptors and side effects | +| 042 | | [iso-plasma](./iso-plasma/) | Animated sine plasma effect visualized using contour lines | +| 043 | | [json-components](./json-components/) | Transforming JSON into UI components | +| 044 | | [login-form](./login-form/) | Basic SPA example with atom-based UI router | +| 045 | | [mandelbrot](./mandelbrot/) | Worker based, interactive Mandelbrot visualization | +| 046 | | [markdown](./markdown/) | Minimal Markdown to Hiccup to HTML parser / transformer | +| 047 | | [multitouch](./multitouch/) | Basic rstream-gestures multi-touch demo | +| 048 | | [package-stats](./package-stats/) | CLI util to visualize umbrella pkg stats | +| 049 | | [pixel-basics](./pixel-basics/) | Pixel buffer manipulations | +| 050 | | [pointfree-svg](./pointfree-svg/) | Generate SVG using pointfree DSL | +| 051 | | [poisson-circles](./poisson-circles/) | 2D Poisson-disc sampler with procedural gradient map | +| 052 | | [poly-spline](./poly-spline/) | Polygon to cubic curve conversion & visualization | +| 053 | | [porter-duff](./porter-duff/) | Port-Duff image compositing / alpha blending | +| 054 | | [ramp-synth](./ramp-synth/) | Unison wavetable synth with waveform editor | +| 055 | | [rotating-voronoi](./rotating-voronoi/) | Animated Voronoi diagram, cubic splines & SVG download | +| 056 | | [router-basics](./router-basics/) | Complete mini SPA app w/ router & async content loading | +| 057 | | [rstream-dataflow](./rstream-dataflow/) | Minimal rstream dataflow graph | +| 058 | | [rstream-event-loop](./rstream-event-loop/) | Minimal demo of using rstream constructs to form an interceptor-style event loop | +| 059 | | [rstream-grid](./rstream-grid/) | Interactive grid generator, SVG generation & export, undo/redo support | +| 060 | | [rstream-hdom](./rstream-hdom/) | rstream based UI updates & state handling | +| 061 | | [rstream-spreadsheet](./rstream-spreadsheet/) | rstream based spreadsheet w/ S-expression formula DSL | +| 062 | | [scenegraph](./scenegraph/) | 2D scenegraph & shape picking | +| 063 | | [scenegraph-image](./scenegraph-image/) | 2D scenegraph & image map based geometry manipulation | +| 064 | | [shader-ast-canvas2d](./shader-ast-canvas2d/) | 2D canvas shader emulation | +| 065 | | [shader-ast-evo](./shader-ast-evo/) | Evolutionary shader generation using genetic programming | +| 066 | | [shader-ast-noise](./shader-ast-noise/) | HOF shader procedural noise function composition | +| 067 | | [shader-ast-raymarch](./shader-ast-raymarch/) | WebGL & JS canvas2D raymarch shader cross-compilation | +| 068 | | [shader-ast-sdf2d](./shader-ast-sdf2d/) | WebGL & JS canvas 2D SDF | +| 069 | | [shader-ast-tunnel](./shader-ast-tunnel/) | WebGL & Canvas2D textured tunnel shader | +| 070 | | [shader-ast-workers](./shader-ast-workers/) | Fork-join worker-based raymarch renderer | +| 071 | | [soa-ecs](./soa-ecs/) | Entity Component System w/ 100k 3D particles | +| 072 | | [stratified-grid](./stratified-grid/) | 2D Stratified grid sampling example | +| 073 | | [svg-barchart](./svg-barchart/) | Simplistic SVG bar chart component | +| 074 | | [svg-particles](./svg-particles/) | Basic 2D particle system w/ SVG shapes | +| 075 | | [svg-waveform](./svg-waveform/) | Additive waveform synthesis & SVG visualization with undo/redo | +| 076 | | [talk-slides](./talk-slides/) | hdom based slide deck viewer & slides from my ClojureX 2018 keynote | +| 077 | | [text-canvas](./text-canvas/) | 3D wireframe textmode demo | +| 078 | | [todo-list](./todo-list/) | Obligatory to-do list example with undo/redo | +| 079 | | [transducers-hdom](./transducers-hdom/) | Transducer & rstream based hdom UI updates | +| 080 | | [triple-query](./triple-query/) | Triple store query results & sortable table | +| 081 | | [webgl-cube](./webgl-cube/) | WebGL multi-colored cube mesh | +| 082 | | [webgl-cubemap](./webgl-cubemap/) | WebGL cube maps with async texture loading | +| 083 | | [webgl-grid](./webgl-grid/) | WebGL instancing, animated grid | +| 084 | | [webgl-msdf](./webgl-msdf/) | WebGL MSDF text rendering & particle system | +| 085 | | [webgl-multipass](./webgl-multipass/) | Minimal multi-pass / GPGPU example | +| 086 | | [webgl-shadertoy](./webgl-shadertoy/) | Shadertoy-like WebGL setup | +| 087 | | [webgl-ssao](./webgl-ssao/) | WebGL screenspace ambient occlusion | +| 088 | | [wolfram](./wolfram/) | 1D Wolfram automata with OBJ point cloud export | +| 089 | | [xml-converter](./xml-converter/) | XML/HTML/SVG to hiccup/JS conversion | diff --git a/packages/geom/README.md b/packages/geom/README.md index 4541381282..6b6885f6a3 100644 --- a/packages/geom/README.md +++ b/packages/geom/README.md @@ -117,6 +117,7 @@ A selection: | | Poisson-disk shape-aware sampling, Voronoi & Minimum Spanning Tree visualization | [Demo](https://demo.thi.ng/umbrella/geom-voronoi-mst/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/geom-voronoi-mst) | | | Mouse gesture / stroke analysis, simplification, corner detection | [Demo](https://demo.thi.ng/umbrella/gesture-analysis/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/gesture-analysis) | | | 2D Bezier curve-guided particle system | [Demo](https://demo.thi.ng/umbrella/hdom-canvas-particles/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-particles) | +| | Animated arcs & drawing using hiccup-canvas | [Demo](https://demo.thi.ng/umbrella/hiccup-canvas-arcs/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/hiccup-canvas-arcs) | | | Canvas based Immediate Mode GUI components | [Demo](https://demo.thi.ng/umbrella/imgui/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/imgui) | | | Animated sine plasma effect visualized using contour lines | [Demo](https://demo.thi.ng/umbrella/iso-plasma/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/iso-plasma) | | | Polygon to cubic curve conversion & visualization | [Demo](https://demo.thi.ng/umbrella/poly-spline/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/poly-spline) | diff --git a/packages/hdom/README.md b/packages/hdom/README.md index 47f4635f25..dc9a99380c 100644 --- a/packages/hdom/README.md +++ b/packages/hdom/README.md @@ -102,7 +102,7 @@ Benefits: ### Support packages -- [@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-canvas) - Declarative canvas scenegraph & visualization for [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) +- [@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-canvas) - [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) component wrapper for declarative canvas scenegraphs - [@thi.ng/hdom-components](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-components) - Raw, skinnable UI & SVG components for [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) - [@thi.ng/hdom-mock](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-mock) - Mock base implementation for [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) API diff --git a/packages/hiccup-canvas/README.md b/packages/hiccup-canvas/README.md index 854e679821..3020004896 100644 --- a/packages/hiccup-canvas/README.md +++ b/packages/hiccup-canvas/README.md @@ -84,7 +84,7 @@ yarn add @thi.ng/hiccup-canvas ``` -Package sizes (gzipped, pre-treeshake): ESM: 2.51 KB / CJS: 2.62 KB / UMD: 2.61 KB +Package sizes (gzipped, pre-treeshake): ESM: 2.51 KB / CJS: 2.63 KB / UMD: 2.61 KB ## Dependencies diff --git a/packages/hiccup/README.md b/packages/hiccup/README.md index d32dee236b..33ddb4ac82 100644 --- a/packages/hiccup/README.md +++ b/packages/hiccup/README.md @@ -116,6 +116,7 @@ iterable ### Support packages +- [@thi.ng/hiccup-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-canvas) - Hiccup shape tree renderer for vanilla Canvas 2D contexts - [@thi.ng/hiccup-carbon-icons](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-carbon-icons) - Full set of IBM's Carbon icons in hiccup format - [@thi.ng/hiccup-css](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-css) - CSS from nested JS data structures - [@thi.ng/hiccup-markdown](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-markdown) - Markdown parser & serializer from/to Hiccup format @@ -124,7 +125,7 @@ iterable ### Related packages - [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) - Lightweight vanilla ES6 UI component trees with customizable branch-local behaviors -- [@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-canvas) - Declarative canvas scenegraph & visualization for [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) +- [@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-canvas) - [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) component wrapper for declarative canvas scenegraphs ### Blog posts diff --git a/packages/math/README.md b/packages/math/README.md index 271ee9e467..44376f5488 100644 --- a/packages/math/README.md +++ b/packages/math/README.md @@ -63,7 +63,6 @@ A selection: | Screenshot | Description | Live demo | Source | | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | | Basic crypto-currency candle chart with multiple moving averages plots | [Demo](https://demo.thi.ng/umbrella/crypto-chart/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/crypto-chart) | -| | Interactive pattern drawing demo using transducers | [Demo](https://demo.thi.ng/umbrella/hdom-canvas-draw/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-draw) | | | 2D Bezier curve-guided particle system | [Demo](https://demo.thi.ng/umbrella/hdom-canvas-particles/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/hdom-canvas-particles) | | | Animated sine plasma effect visualized using contour lines | [Demo](https://demo.thi.ng/umbrella/iso-plasma/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/iso-plasma) | | | Worker based, interactive Mandelbrot visualization | [Demo](https://demo.thi.ng/umbrella/mandelbrot/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/mandelbrot) | diff --git a/packages/unionstruct/README.md b/packages/unionstruct/README.md index 380b65c043..fc37a1efe2 100644 --- a/packages/unionstruct/README.md +++ b/packages/unionstruct/README.md @@ -33,7 +33,7 @@ Features: - Packed bitfields (signed / unsigned) - Auto-alignment of fields to respective word boundaries (can be disabled) -- Configurable endianess (bitfields currently assume network order / big +- Configurable endianness (bitfields currently assume network order / big endian) - No runtime dependencies, works in node & browser - Small: 2.35KB minified, 1.14KB gzipped diff --git a/packages/vectors/README.md b/packages/vectors/README.md index 217f4dd440..55cd6bdbf6 100644 --- a/packages/vectors/README.md +++ b/packages/vectors/README.md @@ -130,7 +130,7 @@ Partially ported from [thi.ng/geom](http://thi.ng/geom) (Clojure) and - [@thi.ng/color](https://github.com/thi-ng/umbrella/tree/develop/packages/color) - Array-based color ops, conversions, multi-color gradients, presets - [@thi.ng/ecs](https://github.com/thi-ng/umbrella/tree/develop/packages/ecs) - Entity Component System based around typed arrays & sparse sets - [@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom) - Functional, polymorphic API for 2D geometry types & SVG generation -- [@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-canvas) - Declarative canvas scenegraph & visualization for [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) +- [@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom-canvas) - [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom) component wrapper for declarative canvas scenegraphs - [@thi.ng/imgui](https://github.com/thi-ng/umbrella/tree/develop/packages/imgui) - Immediate mode GUI with flexible state handling & data only shape output - [@thi.ng/matrices](https://github.com/thi-ng/umbrella/tree/develop/packages/matrices) - Matrix & quaternion operations for 2D/3D geometry processing - [@thi.ng/soa](https://github.com/thi-ng/umbrella/tree/develop/packages/soa) - SOA & AOS memory mapped structured views with optional & extensible serialization From 8ae3a76664b706b25a0d1747e841347a24407eac Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Jun 2020 14:29:42 +0100 Subject: [PATCH 17/25] Publish - @thi.ng/hdom-canvas@3.0.0 - @thi.ng/hiccup-canvas@1.0.0 --- packages/hdom-canvas/CHANGELOG.md | 19 +++++++++++++++++++ packages/hdom-canvas/package.json | 4 ++-- packages/hiccup-canvas/CHANGELOG.md | 20 ++++++++++++++++++++ packages/hiccup-canvas/package.json | 2 +- 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 packages/hiccup-canvas/CHANGELOG.md diff --git a/packages/hdom-canvas/CHANGELOG.md b/packages/hdom-canvas/CHANGELOG.md index b94223c834..a235dfb7d0 100644 --- a/packages/hdom-canvas/CHANGELOG.md +++ b/packages/hdom-canvas/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.4.26...@thi.ng/hdom-canvas@3.0.0) (2020-06-05) + + +### Features + +* **hdom-canvas:** remove obsolete files ([41c8a9d](https://github.com/thi-ng/umbrella/commit/41c8a9d696211b13bde358dae431f110ab7b4be5)) + + +### BREAKING CHANGES + +* **hdom-canvas:** tree traversal & rendering parts extracted to new +package @thi.ng/hiccup-canvas + +From now on, this package only contains the canvas component wrapper & hdom related interface implementations, allowing canvas rendering parts to be used separately. + + + + + ## [2.4.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.4.25...@thi.ng/hdom-canvas@2.4.26) (2020-06-01) **Note:** Version bump only for package @thi.ng/hdom-canvas diff --git a/packages/hdom-canvas/package.json b/packages/hdom-canvas/package.json index c5c02b93ea..95adaacecd 100644 --- a/packages/hdom-canvas/package.json +++ b/packages/hdom-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom-canvas", - "version": "2.4.26", + "version": "3.0.0", "description": "@thi.ng/hdom component wrapper for declarative canvas scenegraphs", "module": "./index.js", "main": "./lib/index.js", @@ -47,7 +47,7 @@ "@thi.ng/checks": "^2.7.0", "@thi.ng/diff": "^3.2.22", "@thi.ng/hdom": "^8.0.27", - "@thi.ng/hiccup-canvas": "^0.0.1" + "@thi.ng/hiccup-canvas": "^1.0.0" }, "files": [ "*.js", diff --git a/packages/hiccup-canvas/CHANGELOG.md b/packages/hiccup-canvas/CHANGELOG.md new file mode 100644 index 0000000000..ad5d42b5aa --- /dev/null +++ b/packages/hiccup-canvas/CHANGELOG.md @@ -0,0 +1,20 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# 1.0.0 (2020-06-05) + + +### Features + +* **hdom-canvas:** rename package, add text support, refactor ([f41014e](https://github.com/thi-ng/umbrella/commit/f41014ebffa8d4051fccbf04080d814fd62a474b)) +* **hiccup-canvas:** add canvas comp, createTree impl, update deps ([60f12c5](https://github.com/thi-ng/umbrella/commit/60f12c5da7a7803e00846da6c316f65952097067)) +* **hiccup-canvas:** add hiccup-canvas package ([eb284f0](https://github.com/thi-ng/umbrella/commit/eb284f0129118e5ef180383a3cd4a31915a5d82a)) +* **hiccup-canvas:** add IToHiccup support in draw ([a59bb09](https://github.com/thi-ng/umbrella/commit/a59bb0923f37677d6579aede0dbe9958b0150d81)) +* **hiccup-canvas:** extract as new package ([4b3c516](https://github.com/thi-ng/umbrella/commit/4b3c516573dc9cb247dedc211210151575709925)) + + +### BREAKING CHANGES + +* **hiccup-canvas:** extract as new package from former @thi.ng/hdom-canvas diff --git a/packages/hiccup-canvas/package.json b/packages/hiccup-canvas/package.json index 465a2bedf9..bd3f29c7a6 100644 --- a/packages/hiccup-canvas/package.json +++ b/packages/hiccup-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-canvas", - "version": "0.0.1", + "version": "1.0.0", "description": "Hiccup shape tree renderer for vanilla Canvas 2D contexts", "module": "./index.js", "main": "./lib/index.js", From 542faf58e4421c53410e30b4cecdf6a31ef39c16 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Jun 2020 10:32:41 +0100 Subject: [PATCH 18/25] docs: minor update contrib doc - update `tpl.readme.md` naming --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41eef4e6ba..141588e232 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,7 +80,7 @@ code](#doc-strings) below. ### Changes to readme files The readme files for all packages are generated from their respective -templated versions (`README.tpl.md` files). Please only ever edit the +templated versions (`tpl.readme.md` files). Please only ever edit the template and then re-generate the actual readme like so: ```bash From 8a8c40f220440abccbb6cccdfc844edd6155a0ea Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Jun 2020 10:35:05 +0100 Subject: [PATCH 19/25] docs(hdom-canvas): add fix (#226) to readme tpl --- packages/hdom-canvas/README.md | 2 +- packages/hdom-canvas/tpl.readme.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/hdom-canvas/README.md b/packages/hdom-canvas/README.md index ff42472cd0..eee2ebb364 100644 --- a/packages/hdom-canvas/README.md +++ b/packages/hdom-canvas/README.md @@ -142,7 +142,7 @@ start(() => { { rotate: t % Math.PI, stroke: "red" } ), g.star(25 + 25 * Math.sin(t), 6, [0.5, 1], { stroke: "blue" }), - ], + ] ) ]; }); diff --git a/packages/hdom-canvas/tpl.readme.md b/packages/hdom-canvas/tpl.readme.md index 0ab9f98afe..56690ee2c7 100644 --- a/packages/hdom-canvas/tpl.readme.md +++ b/packages/hdom-canvas/tpl.readme.md @@ -76,14 +76,14 @@ 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: t % Math.PI, stroke: "red" } ), g.star(25 + 25 * Math.sin(t), 6, [0.5, 1], { stroke: "blue" }), - ], - { translate: [50, 50], fill: "none" } + ] ) ]; }); From 7250041e30995844ac20295bdb36b351f5b2ccc8 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Jun 2020 15:20:35 +0100 Subject: [PATCH 20/25] feat(adapt-dpi): extract as new pkg BREAKING CHANGE: extracted from hdom-components pkg for better re-use --- packages/adapt-dpi/LICENSE | 201 ++++++++++++++++++++++++++ packages/adapt-dpi/README.md | 88 +++++++++++ packages/adapt-dpi/api-extractor.json | 3 + packages/adapt-dpi/package.json | 65 +++++++++ packages/adapt-dpi/src/index.ts | 23 +++ packages/adapt-dpi/test/index.ts | 6 + packages/adapt-dpi/test/tsconfig.json | 11 ++ packages/adapt-dpi/tpl.readme.md | 60 ++++++++ packages/adapt-dpi/tsconfig.json | 11 ++ 9 files changed, 468 insertions(+) create mode 100644 packages/adapt-dpi/LICENSE create mode 100644 packages/adapt-dpi/README.md create mode 100644 packages/adapt-dpi/api-extractor.json create mode 100644 packages/adapt-dpi/package.json create mode 100644 packages/adapt-dpi/src/index.ts create mode 100644 packages/adapt-dpi/test/index.ts create mode 100644 packages/adapt-dpi/test/tsconfig.json create mode 100644 packages/adapt-dpi/tpl.readme.md create mode 100644 packages/adapt-dpi/tsconfig.json diff --git a/packages/adapt-dpi/LICENSE b/packages/adapt-dpi/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/adapt-dpi/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/adapt-dpi/README.md b/packages/adapt-dpi/README.md new file mode 100644 index 0000000000..6d19717e40 --- /dev/null +++ b/packages/adapt-dpi/README.md @@ -0,0 +1,88 @@ + + +# ![adapt-dpi](https://media.thi.ng/umbrella/banners/thing-adapt-dpi.svg?bd63a9f3) + +[![npm version](https://img.shields.io/npm/v/@thi.ng/adapt-dpi.svg)](https://www.npmjs.com/package/@thi.ng/adapt-dpi) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/adapt-dpi.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +- [About](#about) + - [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) +- [API](#api) +- [Authors](#authors) +- [License](#license) + +## About + +HDPI canvas adapter / styling utility. + +Attempts to determine display pixel density via +`window.devicePixelRatio` (default 1.0) and resizes canvas accordingly. +I.e. If DPR != 1.0, attaches explicit `width` and `height` CSS +properties to force canvas to given pixel size, and resizes canvas pixel +buffer itself based on DPR (e.g. 2x size). + +### Status + +**STABLE** - used in production + +## Installation + +```bash +yarn add @thi.ng/adapt-dpi +``` + +```html +// ES module + + +// UMD + +``` + +Package sizes (gzipped, pre-treeshake): ESM: 146 bytes / CJS: 193 bytes / UMD: 294 bytes + +## Dependencies + +None + +## Usage examples + +Several demos in this repo's +[/examples](https://github.com/thi-ng/umbrella/tree/develop/examples) +directory are using this package. + +A selection: + +| Screenshot | Description | Live demo | Source | +| -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------- | +| | Entity Component System w/ 100k 3D particles | [Demo](https://demo.thi.ng/umbrella/soa-ecs/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/soa-ecs) | +| | WebGL cube maps with async texture loading | [Demo](https://demo.thi.ng/umbrella/webgl-cubemap/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/webgl-cubemap) | +| | WebGL instancing, animated grid | [Demo](https://demo.thi.ng/umbrella/webgl-grid/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/webgl-grid) | +| | WebGL MSDF text rendering & particle system | [Demo](https://demo.thi.ng/umbrella/webgl-msdf/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/webgl-msdf) | + +## API + +[Generated API docs](https://docs.thi.ng/umbrella/adapt-dpi/) + +```ts +import { adaptDPI } from "@thi.ng/adapt-dpi"; + +const canvas = document.createElement("canvas"); + +adaptDPI(canvas, 640, 480); +``` + +## Authors + +Karsten Schmidt + +## License + +© 2015 - 2020 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/adapt-dpi/api-extractor.json b/packages/adapt-dpi/api-extractor.json new file mode 100644 index 0000000000..94972e6bed --- /dev/null +++ b/packages/adapt-dpi/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "../../api-extractor.json" +} diff --git a/packages/adapt-dpi/package.json b/packages/adapt-dpi/package.json new file mode 100644 index 0000000000..51173c3ff6 --- /dev/null +++ b/packages/adapt-dpi/package.json @@ -0,0 +1,65 @@ +{ + "name": "@thi.ng/adapt-dpi", + "version": "0.0.1", + "description": "HDPI canvas adapter / styling utility", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/adapt-dpi#readme", + "funding": { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + }, + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module", + "build:release": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module all", + "build:es6": "tsc --declaration", + "build:test": "rimraf build && tsc -p test/tsconfig.json", + "test": "mocha test", + "cover": "nyc mocha test && nyc report --reporter=lcov", + "clean": "rimraf *.js *.d.ts *.map .nyc_output build coverage doc lib", + "doc:readme": "ts-node -P ../../tools/tsconfig.json ../../tools/src/readme.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && node_modules/.bin/api-extractor run --local --verbose", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn build:release && yarn publish --access public" + }, + "devDependencies": { + "@istanbuljs/nyc-config-typescript": "^1.0.1", + "@microsoft/api-extractor": "^7.8.0", + "@types/mocha": "^7.0.2", + "@types/node": "^14.0.1", + "mocha": "^7.1.2", + "nyc": "^15.0.1", + "ts-node": "^8.10.1", + "typedoc": "^0.17.6", + "typescript": "^3.9.2" + }, + "dependencies": {}, + "files": [ + "*.js", + "*.d.ts", + "lib" + ], + "keywords": [ + "canvas", + "DPI", + "ES6", + "HDPI", + "retina", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false, + "thi.ng": { + "year": 2015 + } +} diff --git a/packages/adapt-dpi/src/index.ts b/packages/adapt-dpi/src/index.ts new file mode 100644 index 0000000000..bc6b87c771 --- /dev/null +++ b/packages/adapt-dpi/src/index.ts @@ -0,0 +1,23 @@ +/** + * Sets the canvas size to given `width` & `height` and adjusts style to + * compensate for HDPI devices. Note: For 2D canvases, this will + * automatically clear any prior canvas content. + * + * @param canvas - + * @param width - uncompensated pixel width + * @param height - uncompensated pixel height + */ +export const adaptDPI = ( + canvas: HTMLCanvasElement, + width: number, + height: number +) => { + const dpr = window.devicePixelRatio || 1; + if (dpr != 1) { + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + } + canvas.width = width * dpr; + canvas.height = height * dpr; + return dpr; +}; diff --git a/packages/adapt-dpi/test/index.ts b/packages/adapt-dpi/test/index.ts new file mode 100644 index 0000000000..51bf6a8ddb --- /dev/null +++ b/packages/adapt-dpi/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import { } from "../src"; + +describe("adapt-dpi", () => { + it("tests pending"); +}); diff --git a/packages/adapt-dpi/test/tsconfig.json b/packages/adapt-dpi/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/adapt-dpi/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/adapt-dpi/tpl.readme.md b/packages/adapt-dpi/tpl.readme.md new file mode 100644 index 0000000000..71cf58c0ce --- /dev/null +++ b/packages/adapt-dpi/tpl.readme.md @@ -0,0 +1,60 @@ +# ${pkg.banner} + +[![npm version](https://img.shields.io/npm/v/${pkg.name}.svg)](https://www.npmjs.com/package/${pkg.name}) +![npm downloads](https://img.shields.io/npm/dm/${pkg.name}.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +## About + +${pkg.description} + +Attempts to determine display pixel density via +`window.devicePixelRatio` (default 1.0) and resizes canvas accordingly. +I.e. If DPR != 1.0, attaches explicit `width` and `height` CSS +properties to force canvas to given pixel size, and resizes canvas pixel +buffer itself based on DPR (e.g. 2x size). + +${status} + +${supportPackages} + +${relatedPackages} + +${blogPosts} + +## Installation + +${pkg.install} + +${pkg.size} + +## Dependencies + +${pkg.deps} + +${examples} + +## API + +${docLink} + +```ts +import { adaptDPI } from "@thi.ng/adapt-dpi"; + +const canvas = document.createElement("canvas"); + +adaptDPI(canvas, 640, 480); +``` + +## Authors + +${authors} + +## License + +© ${copyright} // ${license} diff --git a/packages/adapt-dpi/tsconfig.json b/packages/adapt-dpi/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/adapt-dpi/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +} From 2b89ad4135b9c765436fd4a496eecb080a9f59fa Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Jun 2020 15:23:14 +0100 Subject: [PATCH 21/25] refactor(hdom-components): remove adaptDPI() BREAKING CHANGE: re-use adaptDPI() from new @thi.ng/adapt-dpi pkg - update deps --- packages/hdom-components/package.json | 1 + packages/hdom-components/src/canvas.ts | 28 +++----------------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/packages/hdom-components/package.json b/packages/hdom-components/package.json index c5b5efb3d9..f5586d25a4 100644 --- a/packages/hdom-components/package.json +++ b/packages/hdom-components/package.json @@ -43,6 +43,7 @@ "typescript": "^3.9.2" }, "dependencies": { + "@thi.ng/adapt-dpi": "^0.0.1", "@thi.ng/api": "^6.11.0", "@thi.ng/checks": "^2.7.0", "@thi.ng/math": "^1.7.10", diff --git a/packages/hdom-components/src/canvas.ts b/packages/hdom-components/src/canvas.ts index e45f959b25..69af44582b 100644 --- a/packages/hdom-components/src/canvas.ts +++ b/packages/hdom-components/src/canvas.ts @@ -1,3 +1,5 @@ +import { adaptDPI } from "@thi.ng/adapt-dpi"; + export type CanvasContext = | CanvasRenderingContext2D | WebGLRenderingContext @@ -81,7 +83,7 @@ const _canvas = ( }, release(hctx: any, ...args: any[]) { handlers.release && handlers.release(el, ctx, hctx, ...args); - } + }, }; }; @@ -136,27 +138,3 @@ export const canvas2D = ( handlers: Partial>, opts?: Canvas2DContextAttributes ) => _canvas("2d", handlers, opts); - -/** - * Sets the canvas size to given `width` & `height` and adjusts style to - * compensate for HDPI devices. Note: For 2D canvases, this will - * automatically clear any prior canvas content. - * - * @param canvas - - * @param width - uncompensated pixel width - * @param height - uncompensated pixel height - */ -export const adaptDPI = ( - canvas: HTMLCanvasElement, - width: number, - height: number -) => { - const dpr = window.devicePixelRatio || 1; - if (dpr != 1) { - canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; - } - canvas.width = width * dpr; - canvas.height = height * dpr; - return dpr; -}; From 6d49da610bec87fef96c77a39f1181002872f2ba Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Jun 2020 15:25:57 +0100 Subject: [PATCH 22/25] refactor(webgl): remove adaptDPI() BREAKING CHANGE: re-use adaptDPI() from new @thi.ng/adapt-dpi pkg - update deps --- packages/webgl/package.json | 1 + packages/webgl/src/canvas.ts | 33 +++++---------------------------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/packages/webgl/package.json b/packages/webgl/package.json index bf9ae25468..2eca3f2e6f 100644 --- a/packages/webgl/package.json +++ b/packages/webgl/package.json @@ -43,6 +43,7 @@ "typescript": "^3.9.2" }, "dependencies": { + "@thi.ng/adapt-dpi": "^0.0.1", "@thi.ng/api": "^6.11.0", "@thi.ng/associative": "^4.0.11", "@thi.ng/binary": "^2.0.7", diff --git a/packages/webgl/src/canvas.ts b/packages/webgl/src/canvas.ts index de3dc3e476..92e7772bb1 100644 --- a/packages/webgl/src/canvas.ts +++ b/packages/webgl/src/canvas.ts @@ -1,7 +1,8 @@ +import { adaptDPI } from "@thi.ng/adapt-dpi"; import { isString } from "@thi.ng/checks"; -import { error } from "./error"; import type { WeblGLCanvasOpts } from "./api/canvas"; import type { WebGLExtensionMap } from "./api/ext"; +import { error } from "./error"; const defaultOpts: WebGLContextAttributes = { alpha: true, @@ -9,7 +10,7 @@ const defaultOpts: WebGLContextAttributes = { depth: true, premultipliedAlpha: true, preserveDrawingBuffer: false, - stencil: false + stencil: false, }; export const glCanvas = (opts: Partial = {}) => { @@ -26,7 +27,7 @@ export const glCanvas = (opts: Partial = {}) => { opts.version === 2 ? "webgl2" : "webgl", { ...defaultOpts, - ...opts.opts + ...opts.opts, } ); if (!gl) { @@ -37,7 +38,7 @@ export const glCanvas = (opts: Partial = {}) => { return { canvas, gl, - ext: getExtensions(gl, opts.ext!) + ext: getExtensions(gl, opts.ext!), }; }; @@ -55,27 +56,3 @@ export const getExtensions = ( } return ext; }; - -/** - * Sets the canvas size to given `width` & `height` and adjusts style to - * compensate for HDPI devices. Note: For 2D canvases, this will - * automatically clear any prior canvas content. - * - * @param canvas - - * @param width - uncompensated pixel width - * @param height - uncompensated pixel height - */ -export const adaptDPI = ( - canvas: HTMLCanvasElement, - width: number, - height: number -) => { - const dpr = window.devicePixelRatio || 1; - if (dpr != 1) { - canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; - } - canvas.width = width * dpr; - canvas.height = height * dpr; - return dpr; -}; From d15cb76b8e14d6f096ae5d0859b0fc809495653d Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Jun 2020 15:28:05 +0100 Subject: [PATCH 23/25] refactor(examples): update adaptDPI() callsites in demos --- examples/soa-ecs/package.json | 2 ++ examples/soa-ecs/src/index.ts | 45 +++++++++++++++-------------- examples/webgl-cubemap/package.json | 2 ++ examples/webgl-cubemap/src/index.ts | 3 +- examples/webgl-grid/package.json | 2 ++ examples/webgl-grid/src/index.ts | 3 +- examples/webgl-msdf/package.json | 2 ++ examples/webgl-msdf/src/index.ts | 2 +- 8 files changed, 36 insertions(+), 25 deletions(-) diff --git a/examples/soa-ecs/package.json b/examples/soa-ecs/package.json index b418893704..b29be73258 100644 --- a/examples/soa-ecs/package.json +++ b/examples/soa-ecs/package.json @@ -17,6 +17,7 @@ "typescript": "^3.9.2" }, "dependencies": { + "@thi.ng/adapt-dpi": "latest", "@thi.ng/api": "latest", "@thi.ng/ecs": "latest", "@thi.ng/hdom": "latest", @@ -35,6 +36,7 @@ }, "thi.ng": { "readme": [ + "adapt-dpi", "ecs", "hdom", "matrices", diff --git a/examples/soa-ecs/src/index.ts b/examples/soa-ecs/src/index.ts index 7f2d3f81d8..6413eba527 100644 --- a/examples/soa-ecs/src/index.ts +++ b/examples/soa-ecs/src/index.ts @@ -1,7 +1,8 @@ +import { adaptDPI } from "@thi.ng/adapt-dpi"; import { Type } from "@thi.ng/api"; import { ECS, GroupInfo, GroupTuple } from "@thi.ng/ecs"; import { start } from "@thi.ng/hdom"; -import { adaptDPI, canvasWebGL } from "@thi.ng/hdom-components"; +import { canvasWebGL } from "@thi.ng/hdom-components"; import { fract } from "@thi.ng/math"; import { ortho } from "@thi.ng/matrices"; import { @@ -13,7 +14,7 @@ import { mix, mul, Vec2Sym, - vec4 + vec4, } from "@thi.ng/shader-ast"; import { add2, @@ -30,18 +31,18 @@ import { randNormS2, rotate, rotateS2, - setVN4 + setVN4, } from "@thi.ng/vectors"; import { BLEND_ADD, compileModel, + defShader, draw, DrawMode, GLMat4, GLVec4, ModelSpec, - shader, - ShaderSpec + ShaderSpec, } from "@thi.ng/webgl"; const BATCH_UPDATE = true; @@ -65,14 +66,14 @@ const ecs = new ECS(NUM); const pos = ecs.defComponent({ id: "pos", type: Type.F32, - size: 2 + size: 2, }); const vel = ecs.defComponent({ id: "vel", type: Type.F32, size: 2, - default: () => randNormS2([0, 0]) + default: () => randNormS2([0, 0]), }); const group = ecs.defGroup([pos, vel]); @@ -143,28 +144,28 @@ const pointShader: ShaderSpec = { gl.gl_Position, mul(unis.proj, vec4(ins.position, 0, 1)) ), - assign(gl.gl_PointSize, float(2)) - ]) + assign(gl.gl_PointSize, float(2)), + ]), ], fs: (gl, unis, ins, outs) => [ - defMain(() => [assign(outs.fragColor, ins.vcol)]) + defMain(() => [assign(outs.fragColor, ins.vcol)]), ], attribs: { - position: "vec2" + position: "vec2", }, varying: { - vcol: "vec4" + vcol: "vec4", }, uniforms: { proj: "mat4", color: "vec4", - color2: "vec4" + color2: "vec4", }, state: { depth: false, blend: true, - blendFn: BLEND_ADD - } + blendFn: BLEND_ADD, + }, }; const app = () => { @@ -176,17 +177,17 @@ const app = () => { attribs: { position: { data: pos.packedValues(), - size: 2 - } + size: 2, + }, }, - shader: shader(gl, pointShader), + shader: defShader(gl, pointShader), uniforms: { proj: ortho([], -W2, W2, -W2, W2, 0, 1), color: COLOR, - color2: COLOR2 + color2: COLOR2, }, num: NUM, - mode: DrawMode.POINTS + mode: DrawMode.POINTS, }); }, update: (el, gl, __, time) => { @@ -215,11 +216,11 @@ const app = () => { gl.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); draw(model); - } + }, }); return () => [ "div.bg-black.vh-100.flex.flex-column.items-center.justify-center", - [canvas, { width: W, height: W }] + [canvas, { width: W, height: W }], ]; }; diff --git a/examples/webgl-cubemap/package.json b/examples/webgl-cubemap/package.json index 1029bf6241..82fffd3f4b 100644 --- a/examples/webgl-cubemap/package.json +++ b/examples/webgl-cubemap/package.json @@ -17,6 +17,7 @@ "typescript": "^3.9.2" }, "dependencies": { + "@thi.ng/adapt-dpi": "latest", "@thi.ng/dsp": "latest", "@thi.ng/hdom": "latest", "@thi.ng/hdom-components": "latest", @@ -34,6 +35,7 @@ }, "thi.ng": { "readme": [ + "adapt-dpi", "matrices", "shader-ast", "webgl" diff --git a/examples/webgl-cubemap/src/index.ts b/examples/webgl-cubemap/src/index.ts index 71162308a5..4f68cd847b 100644 --- a/examples/webgl-cubemap/src/index.ts +++ b/examples/webgl-cubemap/src/index.ts @@ -1,6 +1,7 @@ +import { adaptDPI } from "@thi.ng/adapt-dpi"; import { sin } from "@thi.ng/dsp"; import { start } from "@thi.ng/hdom"; -import { adaptDPI, canvasWebGL, dropdown } from "@thi.ng/hdom-components"; +import { canvasWebGL, dropdown } from "@thi.ng/hdom-components"; import { concat, lookAt, perspective, transform44 } from "@thi.ng/matrices"; import { fromPromise, metaStream, stream } from "@thi.ng/rstream"; import { diff --git a/examples/webgl-grid/package.json b/examples/webgl-grid/package.json index cdd678c1b5..58d3044b26 100644 --- a/examples/webgl-grid/package.json +++ b/examples/webgl-grid/package.json @@ -18,6 +18,7 @@ "typescript": "^3.9.2" }, "dependencies": { + "@thi.ng/adapt-dpi": "latest", "@thi.ng/hdom": "latest", "@thi.ng/hdom-components": "latest", "@thi.ng/math": "latest", @@ -35,6 +36,7 @@ }, "thi.ng": { "readme": [ + "adapt-dpi", "matrices", "shader-ast", "transducers", diff --git a/examples/webgl-grid/src/index.ts b/examples/webgl-grid/src/index.ts index fd7a9ed042..2a7b75c1a2 100644 --- a/examples/webgl-grid/src/index.ts +++ b/examples/webgl-grid/src/index.ts @@ -1,5 +1,6 @@ +import { adaptDPI } from "@thi.ng/adapt-dpi"; import { start } from "@thi.ng/hdom"; -import { adaptDPI, canvasWebGL } from "@thi.ng/hdom-components"; +import { canvasWebGL } from "@thi.ng/hdom-components"; import { PI } from "@thi.ng/math"; import { lookAt, ortho, scale44 } from "@thi.ng/matrices"; import { mapcat, range2d } from "@thi.ng/transducers"; diff --git a/examples/webgl-msdf/package.json b/examples/webgl-msdf/package.json index 503a2a1e32..5fe2f45be9 100644 --- a/examples/webgl-msdf/package.json +++ b/examples/webgl-msdf/package.json @@ -17,6 +17,7 @@ "typescript": "^3.9.2" }, "dependencies": { + "@thi.ng/adapt-dpi": "latest", "@thi.ng/api": "latest", "@thi.ng/hdom": "latest", "@thi.ng/hdom-components": "latest", @@ -39,6 +40,7 @@ }, "thi.ng": { "readme": [ + "adapt-dpi", "matrices", "shader-ast", "transducers", diff --git a/examples/webgl-msdf/src/index.ts b/examples/webgl-msdf/src/index.ts index 88145de47b..536f614a26 100644 --- a/examples/webgl-msdf/src/index.ts +++ b/examples/webgl-msdf/src/index.ts @@ -1,3 +1,4 @@ +import { adaptDPI } from "@thi.ng/adapt-dpi"; import { GLType } from "@thi.ng/api"; import { start } from "@thi.ng/hdom"; import { canvasWebGL } from "@thi.ng/hdom-components"; @@ -33,7 +34,6 @@ import { ZERO3, } from "@thi.ng/vectors"; import { - adaptDPI, BLEND_NORMAL, compileModel, defShader, From a115962f261dd78fe8a4ccb4e692e37e57c01ca3 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Jun 2020 16:43:14 +0100 Subject: [PATCH 24/25] docs: update readmes --- packages/hdom-components/README.md | 3 ++- packages/webgl/README.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/hdom-components/README.md b/packages/hdom-components/README.md index e43266c698..2ba3781d04 100644 --- a/packages/hdom-components/README.md +++ b/packages/hdom-components/README.md @@ -55,10 +55,11 @@ yarn add @thi.ng/hdom-components ``` -Package sizes (gzipped, pre-treeshake): ESM: 2.25 KB / CJS: 2.37 KB / UMD: 2.37 KB +Package sizes (gzipped, pre-treeshake): ESM: 2.19 KB / CJS: 2.31 KB / UMD: 2.33 KB ## Dependencies +- [@thi.ng/adapt-dpi](https://github.com/thi-ng/umbrella/tree/develop/packages/adapt-dpi) - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api) - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks) - [@thi.ng/math](https://github.com/thi-ng/umbrella/tree/develop/packages/math) diff --git a/packages/webgl/README.md b/packages/webgl/README.md index 5e2115dbc6..c0ac787f4a 100644 --- a/packages/webgl/README.md +++ b/packages/webgl/README.md @@ -89,10 +89,11 @@ yarn add @thi.ng/webgl ``` -Package sizes (gzipped, pre-treeshake): ESM: 11.27 KB / CJS: 11.44 KB / UMD: 11.27 KB +Package sizes (gzipped, pre-treeshake): ESM: 11.22 KB / CJS: 11.38 KB / UMD: 11.21 KB ## Dependencies +- [@thi.ng/adapt-dpi](https://github.com/thi-ng/umbrella/tree/develop/packages/adapt-dpi) - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api) - [@thi.ng/associative](https://github.com/thi-ng/umbrella/tree/develop/packages/associative) - [@thi.ng/binary](https://github.com/thi-ng/umbrella/tree/develop/packages/binary) From cca5410e44f3f173e8aeb6d700d6e1a61f6ee503 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Jun 2020 16:43:58 +0100 Subject: [PATCH 25/25] Publish - @thi.ng/adapt-dpi@1.0.0 - @thi.ng/hdom-components@4.0.0 - @thi.ng/webgl-msdf@0.1.36 - @thi.ng/webgl-shadertoy@0.2.23 - @thi.ng/webgl@2.0.0 --- packages/adapt-dpi/CHANGELOG.md | 16 ++++++++++++++++ packages/adapt-dpi/package.json | 3 +-- packages/hdom-components/CHANGELOG.md | 18 ++++++++++++++++++ packages/hdom-components/package.json | 4 ++-- packages/webgl-msdf/CHANGELOG.md | 8 ++++++++ packages/webgl-msdf/package.json | 4 ++-- packages/webgl-shadertoy/CHANGELOG.md | 8 ++++++++ packages/webgl-shadertoy/package.json | 4 ++-- packages/webgl/CHANGELOG.md | 18 ++++++++++++++++++ packages/webgl/package.json | 4 ++-- 10 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 packages/adapt-dpi/CHANGELOG.md diff --git a/packages/adapt-dpi/CHANGELOG.md b/packages/adapt-dpi/CHANGELOG.md new file mode 100644 index 0000000000..dba9fe0852 --- /dev/null +++ b/packages/adapt-dpi/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# 1.0.0 (2020-06-07) + + +### Features + +* **adapt-dpi:** extract as new pkg ([7250041](https://github.com/thi-ng/umbrella/commit/7250041e30995844ac20295bdb36b351f5b2ccc8)) + + +### BREAKING CHANGES + +* **adapt-dpi:** extracted from hdom-components pkg for better re-use diff --git a/packages/adapt-dpi/package.json b/packages/adapt-dpi/package.json index 51173c3ff6..ebee1f5341 100644 --- a/packages/adapt-dpi/package.json +++ b/packages/adapt-dpi/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/adapt-dpi", - "version": "0.0.1", + "version": "1.0.0", "description": "HDPI canvas adapter / styling utility", "module": "./index.js", "main": "./lib/index.js", @@ -41,7 +41,6 @@ "typedoc": "^0.17.6", "typescript": "^3.9.2" }, - "dependencies": {}, "files": [ "*.js", "*.d.ts", diff --git a/packages/hdom-components/CHANGELOG.md b/packages/hdom-components/CHANGELOG.md index f28babcb48..c15ca0e111 100644 --- a/packages/hdom-components/CHANGELOG.md +++ b/packages/hdom-components/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@3.2.12...@thi.ng/hdom-components@4.0.0) (2020-06-07) + + +### Code Refactoring + +* **hdom-components:** remove adaptDPI() ([2b89ad4](https://github.com/thi-ng/umbrella/commit/2b89ad4135b9c765436fd4a496eecb080a9f59fa)) + + +### BREAKING CHANGES + +* **hdom-components:** re-use adaptDPI() from new @thi.ng/adapt-dpi pkg + +- update deps + + + + + ## [3.2.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@3.2.11...@thi.ng/hdom-components@3.2.12) (2020-06-01) **Note:** Version bump only for package @thi.ng/hdom-components diff --git a/packages/hdom-components/package.json b/packages/hdom-components/package.json index f5586d25a4..683ef1c053 100644 --- a/packages/hdom-components/package.json +++ b/packages/hdom-components/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom-components", - "version": "3.2.12", + "version": "4.0.0", "description": "Raw, skinnable UI & SVG components for @thi.ng/hdom", "module": "./index.js", "main": "./lib/index.js", @@ -43,7 +43,7 @@ "typescript": "^3.9.2" }, "dependencies": { - "@thi.ng/adapt-dpi": "^0.0.1", + "@thi.ng/adapt-dpi": "^1.0.0", "@thi.ng/api": "^6.11.0", "@thi.ng/checks": "^2.7.0", "@thi.ng/math": "^1.7.10", diff --git a/packages/webgl-msdf/CHANGELOG.md b/packages/webgl-msdf/CHANGELOG.md index c5542818ce..07618e7724 100644 --- a/packages/webgl-msdf/CHANGELOG.md +++ b/packages/webgl-msdf/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.36](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.35...@thi.ng/webgl-msdf@0.1.36) (2020-06-07) + +**Note:** Version bump only for package @thi.ng/webgl-msdf + + + + + ## [0.1.35](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.34...@thi.ng/webgl-msdf@0.1.35) (2020-06-01) **Note:** Version bump only for package @thi.ng/webgl-msdf diff --git a/packages/webgl-msdf/package.json b/packages/webgl-msdf/package.json index 3c36e9c522..2be92b04eb 100644 --- a/packages/webgl-msdf/package.json +++ b/packages/webgl-msdf/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/webgl-msdf", - "version": "0.1.35", + "version": "0.1.36", "description": "Multi-channel SDF font rendering & basic text layout for WebGL", "module": "./index.js", "main": "./lib/index.js", @@ -48,7 +48,7 @@ "@thi.ng/transducers": "^7.0.0", "@thi.ng/vector-pools": "^1.0.31", "@thi.ng/vectors": "^4.4.3", - "@thi.ng/webgl": "^1.0.17", + "@thi.ng/webgl": "^2.0.0", "tslib": "^1.12.0" }, "files": [ diff --git a/packages/webgl-shadertoy/CHANGELOG.md b/packages/webgl-shadertoy/CHANGELOG.md index e9d3a9ae0c..5c3720be06 100644 --- a/packages/webgl-shadertoy/CHANGELOG.md +++ b/packages/webgl-shadertoy/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-shadertoy@0.2.22...@thi.ng/webgl-shadertoy@0.2.23) (2020-06-07) + +**Note:** Version bump only for package @thi.ng/webgl-shadertoy + + + + + ## [0.2.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-shadertoy@0.2.21...@thi.ng/webgl-shadertoy@0.2.22) (2020-06-01) **Note:** Version bump only for package @thi.ng/webgl-shadertoy diff --git a/packages/webgl-shadertoy/package.json b/packages/webgl-shadertoy/package.json index 328c57f8ff..2b1373e1fb 100644 --- a/packages/webgl-shadertoy/package.json +++ b/packages/webgl-shadertoy/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/webgl-shadertoy", - "version": "0.2.22", + "version": "0.2.23", "description": "Basic WebGL scaffolding for running interactive fragment shaders via @thi.ng/shader-ast", "module": "./index.js", "main": "./lib/index.js", @@ -47,7 +47,7 @@ "@thi.ng/shader-ast": "^0.3.23", "@thi.ng/shader-ast-glsl": "^0.1.29", "@thi.ng/transducers": "^7.0.0", - "@thi.ng/webgl": "^1.0.17", + "@thi.ng/webgl": "^2.0.0", "tslib": "^1.12.0" }, "files": [ diff --git a/packages/webgl/CHANGELOG.md b/packages/webgl/CHANGELOG.md index ad7a468174..5320e17525 100644 --- a/packages/webgl/CHANGELOG.md +++ b/packages/webgl/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@1.0.17...@thi.ng/webgl@2.0.0) (2020-06-07) + + +### Code Refactoring + +* **webgl:** remove adaptDPI() ([6d49da6](https://github.com/thi-ng/umbrella/commit/6d49da610bec87fef96c77a39f1181002872f2ba)) + + +### BREAKING CHANGES + +* **webgl:** re-use adaptDPI() from new @thi.ng/adapt-dpi pkg + +- update deps + + + + + ## [1.0.17](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@1.0.16...@thi.ng/webgl@1.0.17) (2020-06-01) **Note:** Version bump only for package @thi.ng/webgl diff --git a/packages/webgl/package.json b/packages/webgl/package.json index 2eca3f2e6f..5bc92252f1 100644 --- a/packages/webgl/package.json +++ b/packages/webgl/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/webgl", - "version": "1.0.17", + "version": "2.0.0", "description": "WebGL & GLSL abstraction layer", "module": "./index.js", "main": "./lib/index.js", @@ -43,7 +43,7 @@ "typescript": "^3.9.2" }, "dependencies": { - "@thi.ng/adapt-dpi": "^0.0.1", + "@thi.ng/adapt-dpi": "^1.0.0", "@thi.ng/api": "^6.11.0", "@thi.ng/associative": "^4.0.11", "@thi.ng/binary": "^2.0.7",