@dimension-mismatch/svgtools 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "@dimension-mismatch/svgtools",
3
+ "version": "0.0.1",
4
+ "description": "Tools for working with SVG images, based on my htmltools framework",
5
+ "main": "svgtools.js",
6
+ "author": "dimension-mismatch",
7
+ "license": "ISC"
8
+ }
package/svgtools.ts ADDED
@@ -0,0 +1,109 @@
1
+ import { FElement } from "htmltools";
2
+ import { Transform2d } from "./transformtools.js";
3
+
4
+ const ns = "http://www.w3.org/2000/svg";
5
+ export class SVGFElement extends FElement{
6
+ constructor(type: string){
7
+ super(document.createElementNS(ns, type));
8
+ }
9
+ withTransform(tf: Transform2d){
10
+ this.withAttributes({transform: tf.toSvgString()});
11
+ return this;
12
+ }
13
+ }
14
+ export class FESVG extends FElement{
15
+ constructor(...children: SVGFElement[]){
16
+ super(document.createElementNS(ns, "svg"), ...children);
17
+ }
18
+ }
19
+ export const SVG = (...children: SVGFElement[]) => new FESVG(...children);
20
+
21
+ export class FESVGGroup extends SVGFElement{
22
+ constructor(...children: SVGFElement[]){
23
+ super("g");
24
+ this.addChildren(...children);
25
+ }
26
+ }
27
+ export const GROUP = (...children: SVGFElement[]) => new FESVGGroup(...children);
28
+
29
+ export class FESVGCircle extends SVGFElement{
30
+ constructor(x: number, y: number, radius: number){
31
+ super("circle");
32
+ this.withAttributes({cx: x, cy: y, r: radius})
33
+ }
34
+ setPosition(x: number,y: number){
35
+ this.withAttributes({cx: x, cy: y});
36
+ }
37
+ }
38
+ export const CIRCLE = (x: number, y: number, radius: number) => new FESVGCircle(x,y,radius);
39
+
40
+
41
+ export class FESVGLine extends SVGFElement{
42
+ constructor(x1: number, y1: number, x2: number, y2: number){
43
+ super("line");
44
+ this.withAttributes({x1: x1,y1: y1,x2: x2,y2: y2});
45
+ }
46
+ setP1(x: number, y: number){
47
+ this.withAttributes({x1: x,y1: y});
48
+ }
49
+ setP2(x: number, y: number){
50
+ this.withAttributes({x2: x,y2: y});
51
+ }
52
+ }
53
+ export const LINE = (x1: number, y1: number, x2: number, y2: number) => new FESVGLine(x1,y1,x2,y2);
54
+
55
+ export class FESVGPath extends SVGFElement{
56
+ constructor(data: string){
57
+ super("path");
58
+ this.withAttributes({d: data});
59
+ }
60
+ setData(data: string){
61
+ this.withAttributes({d: data});
62
+ }
63
+ }
64
+ export const PATH = (data: string) => new FESVGPath(data);
65
+
66
+ export class FESVGRect extends SVGFElement{
67
+ constructor(x: number, y: number, width: number, height: number){
68
+ super("rect");
69
+ this.withAttributes({x: x,y: y,width: width,height: height});
70
+ }
71
+
72
+ setPosition(x: number, y: number){
73
+ this.withAttributes({x: x,y: y});
74
+ }
75
+ setSize(width: number, height: number){
76
+ this.withAttributes({width: width,height: height});
77
+ }
78
+ setBoundingBox(x1: number, y1: number, x2: number, y2: number){
79
+ this.withAttributes({x: x1, y: y1, width: x2- x1, height: y2 - y1});
80
+ }
81
+ }
82
+ export const RECT = (x: number, y: number, width: number, height: number) => new FESVGRect(x,y,width,height);
83
+
84
+
85
+ export interface GradientStop{
86
+ color: string;
87
+ offset: string;
88
+ }
89
+ class FESVGradientStop extends SVGFElement{
90
+ constructor(stop: GradientStop){
91
+ super("stop");
92
+ this.withAttributes({style: `stop-color: ${stop.color};`, offset: stop.offset})
93
+ }
94
+ }
95
+ export class FESVGLinearGradient extends SVGFElement{
96
+ constructor(id: string, gradientTransform: Transform2d, ...stops: GradientStop[]){
97
+ super("linearGradient");
98
+ this.withAttributes({id: id, gradientTransform: gradientTransform.toSvgString()});
99
+ this.addChildren(...stops.map((s) => new FESVGradientStop(s)));
100
+ }
101
+ }
102
+
103
+ export class FESVGPattern extends SVGFElement{
104
+ constructor(id: string, transform: Transform2d, ...content: SVGFElement[]){
105
+ super("pattern");
106
+ this.withAttributes({id: id, transform: transform.toSvgString()});
107
+ this.addChildren(...content);
108
+ }
109
+ }
@@ -0,0 +1,110 @@
1
+ export class Transform2d{
2
+ //represents a combination of 2d translation, rotation, and scaling
3
+ /*
4
+ [ a c e ][ x ] [ x' ] x' = ax + cy + e
5
+ [ b d f ][ y ] = [ y' ] y' = bx + dy + f
6
+ [ 0 0 1 ][ 1 ] [ 1 ]
7
+ */
8
+ a: number;
9
+ b: number;
10
+ c: number;
11
+ d: number;
12
+ e: number;
13
+ f: number;
14
+ constructor(a: number, b: number, c: number, d: number, e: number, f: number){
15
+ this.a = a;
16
+ this.b = b;
17
+ this.c = c;
18
+ this.d = d;
19
+ this.e = e;
20
+ this.f = f;
21
+ }
22
+ applyTo(vec: {x: number, y: number}){
23
+ return {
24
+ x: this.a * vec.x + this.c * vec.y + this.e,
25
+ y: this.b * vec.x + this.d * vec.y + this.f,
26
+ }
27
+ }
28
+ then(other: Transform2d): Transform2d{
29
+ /*
30
+ [ a c e ][ a c e ]
31
+ [ b d f ][ b d f ]
32
+ [ 0 0 1 ][ 0 0 1 ]
33
+ */
34
+ return new Transform2d(
35
+ other.a * this.a + other.c * this.b,
36
+ other.b * this.a + other.d * this.b,
37
+ other.a * this.c + other.c * this.d,
38
+ other.b * this.c + other.d * this.d,
39
+ other.a * this.e + other.c * this.f + other.e,
40
+ other.b * this.e + other.d * this.f + other.f)
41
+ }
42
+ composeWith(other: Transform2d){
43
+ this.a = other.a * this.a + other.c * this.b
44
+ this.b = other.b * this.a + other.d * this.b
45
+ this.c = other.a * this.c + other.c * this.d
46
+ this.d = other.b * this.c + other.d * this.d
47
+ this.e = other.a * this.e + other.c * this.f + other.e
48
+ this.f = other.b * this.e + other.d * this.f + other.f
49
+ return this;
50
+ }
51
+ inverse():Transform2d{
52
+ /*([ a c e ]
53
+ [ b d f ]
54
+ [ 0 0 1 ]*/
55
+ let det = this.a * this.d - this.b * this.c;
56
+ if(det == 0){
57
+ return this;
58
+ }
59
+ return new Transform2d(
60
+ this.d / det,
61
+ -this.b / det,
62
+ -this.c / det,
63
+ this.a / det,
64
+ (-this.d * this.e + this.c * this.f) / det,
65
+ (-this.a * this.f + this.b * this.e) / det
66
+ );
67
+ }
68
+ toSvgString(){
69
+ return `matrix(${this.a} ${this.b} ${this.c} ${this.d} ${this.e} ${this.f})`
70
+ }
71
+ copy(){
72
+ return new Transform2d(this.a, this.b, this.c, this.d, this.e, this.f);
73
+ }
74
+ static translation(x: number, y: number): Transform2d{
75
+ return new Transform2d(1,0,0,1,x,y);
76
+ }
77
+
78
+ static rotation(angle: number){
79
+ let cos = Math.cos(angle);
80
+ let sin = Math.sin(angle);
81
+ return new Transform2d(cos, sin, -sin, cos, 0, 0);
82
+ }
83
+ static scale(scale: number){
84
+ return new Transform2d(scale, 0,0, scale, 0, 0);
85
+ }
86
+ static scaleXY(scaleX: number, scaleY: number){
87
+ return new Transform2d(scaleX, 0, 0, scaleY, 0, 0);
88
+ }
89
+ static nothing(){
90
+ return new Transform2d(1,0,0,1,0,0);
91
+ }
92
+ }
93
+
94
+ export function transformBounds(bounds: any, tf: Transform2d){
95
+ let p1 = tf.applyTo(bounds.min);
96
+ let p2 = tf.applyTo({x: bounds.min.x, y: bounds.max.y});
97
+ let p3 = tf.applyTo(bounds.max);
98
+ let p4 = tf.applyTo({x: bounds.max.x, y: bounds.min.y});
99
+
100
+ return {
101
+ min: {
102
+ x: Math.min(p1.x, p2.x, p3.x, p4.x),
103
+ y: Math.min(p1.y, p2.y, p3.y, p4.y),
104
+ },
105
+ max: {
106
+ x: Math.max(p1.x, p2.x, p3.x, p4.x),
107
+ y: Math.max(p1.y, p2.y, p3.y, p4.y),
108
+ }
109
+ }
110
+ }