Skip to content

Commit

Permalink
feat(hiccup-svg): add support for precision attribute
Browse files Browse the repository at this point in the history
- update convertTree() to allow dynamic floating point precision
  handling via `__prec` control attrib
- update docs
- add tests
  • Loading branch information
postspectacular committed Apr 7, 2023
1 parent 0736f0c commit f81d0d8
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 33 deletions.
89 changes: 62 additions & 27 deletions packages/hiccup-svg/src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { implementsFunction } from "@thi.ng/checks/implements-function";
import { isArray } from "@thi.ng/checks/is-array";
import { circle } from "./circle.js";
import { ellipse } from "./ellipse.js";
import { fattribs } from "./format.js";
import { PRECISION, fattribs, setPrecision } from "./format.js";
import { linearGradient, radialGradient } from "./gradients.js";
import { image } from "./image.js";
import { hline, line, vline } from "./line.js";
Expand Down Expand Up @@ -36,13 +36,25 @@ const BASE_LINE: Record<string, string> = {
bottom: "text-bottom",
};

const precisionStack: number[] = [];

/**
* Takes a normalized hiccup tree of [`thi.ng/geom`](https://thi.ng/geom) or
* Takes a normalized hiccup tree of [`thi.ng/geom`](https://thi.ng/geom) and/or
* [thi.ng/hdom-canvas](https://thi.ng/hdom-canvas) shape definitions and
* recursively converts it into an hiccup flavor which is compatible for SVG
* serialization. This conversion also involves translation & reorg of various
* attributes. Returns new tree. The original remains untouched, as will any
* unrecognized tree/shape nodes.
* recursively converts it into an hiccup flavor which is compatible for direct
* SVG serialization. This conversion also involves translation, stringification
* & reorg of various attributes. The function returns new tree. The original
* remains untouched, as will any unrecognized tree/shape nodes.
*
* @remarks
* The `__prec` control attribute can be used (on a per-shape basis) to control
* the formatting used for various floating point values (except color
* conversions). See {@link setPrecision}. Child shapes (of a group) inherit the
* precision setting of their parent.
*
* To control the formatting precision for colors, use [the relevant function in
* the thi.ng/color
* package](https://docs.thi.ng/umbrella/color/functions/setPrecision.html).
*
* @param tree - shape tree
*/
Expand All @@ -56,20 +68,24 @@ export const convertTree = (tree: any): any[] | null => {
return tree.map(convertTree);
}
let attribs = convertAttribs(tree[1]);
if (attribs.__prec) {
precisionStack.push(PRECISION);
setPrecision(attribs.__prec);
}
let result: any[];
switch (tree[0]) {
case "svg":
case "defs":
case "a":
case "g": {
const res: any[] = [type, fattribs(attribs)];
case "g":
result = [type, fattribs(attribs)];
for (let i = 2, n = tree.length; i < n; i++) {
const c = convertTree(tree[i]);
c != null && res.push(c);
c != null && result.push(c);
}
return res;
}
break;
case "linearGradient":
return linearGradient(
result = linearGradient(
attribs.id,
attribs.from,
attribs.to,
Expand All @@ -81,8 +97,9 @@ export const convertTree = (tree: any): any[] | null => {
: null),
}
);
break;
case "radialGradient":
return radialGradient(
result = radialGradient(
attribs.id,
attribs.from,
attribs.to,
Expand All @@ -96,19 +113,22 @@ export const convertTree = (tree: any): any[] | null => {
: null),
}
);
break;
case "circle":
return circle(tree[2], tree[3], attribs, ...tree.slice(4));
result = circle(tree[2], tree[3], attribs, ...tree.slice(4));
break;
case "ellipse":
return ellipse(
result = ellipse(
tree[2],
tree[3][0],
tree[3][1],
attribs,
...tree.slice(4)
);
break;
case "rect": {
const r = tree[5] || 0;
return roundedRect(
result = roundedRect(
tree[2],
tree[3],
tree[4],
Expand All @@ -117,42 +137,57 @@ export const convertTree = (tree: any): any[] | null => {
attribs,
...tree.slice(6)
);
break;
}
case "line":
return line(tree[2], tree[3], attribs, ...tree.slice(4));
result = line(tree[2], tree[3], attribs, ...tree.slice(4));
break;
case "hline":
return hline(tree[2], attribs);
result = hline(tree[2], attribs);
break;
case "vline":
return vline(tree[2], attribs);
result = vline(tree[2], attribs);
break;
case "polyline":
return polyline(tree[2], attribs, ...tree.slice(3));
result = polyline(tree[2], attribs, ...tree.slice(3));
break;
case "polygon":
return polygon(tree[2], attribs, ...tree.slice(3));
result = polygon(tree[2], attribs, ...tree.slice(3));
break;
case "path":
return path(tree[2], attribs, ...tree.slice(3));
result = path(tree[2], attribs, ...tree.slice(3));
break;
case "text":
return text(tree[2], tree[3], attribs, ...tree.slice(4));
result = text(tree[2], tree[3], attribs, ...tree.slice(4));
break;
case "img":
return image(tree[3], tree[2].src, attribs, ...tree.slice(4));
result = image(tree[3], tree[2].src, attribs, ...tree.slice(4));
break;
case "points":
return points(
result = points(
tree[2],
attribs.shape,
attribs.size,
attribs,
...tree.slice(3)
);
break;
case "packedPoints":
return packedPoints(
result = packedPoints(
tree[2],
attribs.shape,
attribs.size,
attribs,
...tree.slice(3)
);
break;
default:
return tree;
result = tree;
}
if (attribs.__prec) {
setPrecision(precisionStack.pop()!);
}
return result;
};

const convertAttribs = (attribs: any) => {
Expand Down
15 changes: 12 additions & 3 deletions packages/hiccup-svg/src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ import { isString } from "@thi.ng/checks/is-string";
import { css } from "@thi.ng/color/css/css";
import type { Vec2Like } from "./api.js";

let PRECISION = 3;
export let PRECISION = 3;

/**
* Sets the number of fractional digits used for formatting various floating
* point values in the serialized SVG. The current value can be read via
* {@link PRECISION}.
*
* @param n
*/
export const setPrecision = (n: number) => (PRECISION = n);

/** @internal */
Expand Down Expand Up @@ -62,7 +69,7 @@ const numericAttribs = (attribs: any, ids: string[]) => {
* number or an
* [`IColor`](https://docs.thi.ng/umbrella/color/interfaces/IColor.html)
* instance, it will be converted into a CSS color string using
* [`asCSS()`](https://docs.thi.ng/umbrella/color/functions/asCSS.html).
* [`css()`](https://docs.thi.ng/umbrella/color/functions/css.html).
*
* String color attribs prefixed with `$` are replaced with `url(#...)` refs
* (used for referencing gradients).
Expand Down Expand Up @@ -147,7 +154,9 @@ const buildTransform = (attribs: any) => {

/**
* Attempts to convert a single color attrib value. If `col` is prefixed with
* `$`, the value will be converted into a `url(#...)` reference.
* `$`, the value will be converted into a `url(#...)` reference. If not a
* string already, it will be converted into a CSS color string using
* [`css()`](https://docs.thi.ng/umbrella/color/functions/css.html)
*
* {@link fattribs}
*
Expand Down
52 changes: 49 additions & 3 deletions packages/hiccup-svg/test/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
import { hsv } from "@thi.ng/color";
import { group } from "@thi.ng/testament";
// import * as assert from "assert";
// import * as svg from "../src/index.js"
import * as assert from "assert";
import { convertTree } from "../src/index.js";

group("hiccup-svg", {});
group("hiccup-svg", {
convertTree: () => {
const res = convertTree([
"g",
{ __prec: 1 },
[
"circle",
{ stroke: [1, 0, 0], __prec: 3 },
[1.2345, -1.2345],
99.9994,
],
[
"line",
{ stroke: hsv("blue") },
[1.2345, -1.2345],
[99.999, -99.999],
],
]);
assert.deepStrictEqual(res, [
"g",
{
__prec: 1,
},
[
"circle",
{
__prec: 3,
cx: "1.234",
cy: "-1.234",
r: "99.999",
stroke: "#ff0000",
},
],
[
"line",
{
stroke: "hsl(240.000,100.000%,50.000%)",
x1: "1.2",
x2: "100.0",
y1: "-1.2",
y2: "-100.0",
},
],
]);
},
});

0 comments on commit f81d0d8

Please sign in to comment.