xl-public-utils 1.0.11 → 1.0.13

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.
@@ -0,0 +1,114 @@
1
+ import { ShapePath } from './ShapePath.js';
2
+
3
+ class Font {
4
+ constructor(data) {
5
+ this.isFont = true;
6
+
7
+ this.type = 'Font';
8
+
9
+ this.data = data;
10
+ }
11
+
12
+ generateShapes(text, size = 100) {
13
+ const shapes = [];
14
+ const paths = createPaths(text, size, this.data);
15
+
16
+ for (let p = 0, pl = paths.length; p < pl; p++) {
17
+ shapes.push(...paths[p].toShapes());
18
+ }
19
+
20
+ return shapes;
21
+ }
22
+ }
23
+
24
+ function createPaths(text, size, data) {
25
+ const chars = Array.from(text);
26
+ const scale = size / data.resolution;
27
+ const line_height = (data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness) * scale;
28
+
29
+ const paths = [];
30
+
31
+ let offsetX = 0,
32
+ offsetY = 0;
33
+
34
+ for (let i = 0; i < chars.length; i++) {
35
+ const char = chars[i];
36
+
37
+ if (char === '\n') {
38
+ offsetX = 0;
39
+ offsetY -= line_height;
40
+ } else {
41
+ const ret = createPath(char, scale, offsetX, offsetY, data);
42
+ offsetX += ret.offsetX;
43
+ paths.push(ret.path);
44
+ }
45
+ }
46
+
47
+ return paths;
48
+ }
49
+
50
+ function createPath(char, scale, offsetX, offsetY, data) {
51
+ const glyph = data.glyphs[char] || data.glyphs['?'];
52
+
53
+ if (!glyph) {
54
+ console.error('THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.');
55
+
56
+ return;
57
+ }
58
+
59
+ const path = new ShapePath();
60
+
61
+ let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
62
+
63
+ if (glyph.o) {
64
+ const outline = glyph._cachedOutline || (glyph._cachedOutline = glyph.o.split(' '));
65
+
66
+ for (let i = 0, l = outline.length; i < l; ) {
67
+ const action = outline[i++];
68
+
69
+ switch (action) {
70
+ case 'm': // moveTo
71
+ x = outline[i++] * scale + offsetX;
72
+ y = outline[i++] * scale + offsetY;
73
+
74
+ path.moveTo(x, y);
75
+
76
+ break;
77
+
78
+ case 'l': // lineTo
79
+ x = outline[i++] * scale + offsetX;
80
+ y = outline[i++] * scale + offsetY;
81
+
82
+ path.lineTo(x, y);
83
+
84
+ break;
85
+
86
+ case 'q': // quadraticCurveTo
87
+ cpx = outline[i++] * scale + offsetX;
88
+ cpy = outline[i++] * scale + offsetY;
89
+ cpx1 = outline[i++] * scale + offsetX;
90
+ cpy1 = outline[i++] * scale + offsetY;
91
+
92
+ path.quadraticCurveTo(cpx1, cpy1, cpx, cpy);
93
+
94
+ break;
95
+
96
+ case 'b': // bezierCurveTo
97
+ cpx = outline[i++] * scale + offsetX;
98
+ cpy = outline[i++] * scale + offsetY;
99
+ cpx1 = outline[i++] * scale + offsetX;
100
+ cpy1 = outline[i++] * scale + offsetY;
101
+ cpx2 = outline[i++] * scale + offsetX;
102
+ cpy2 = outline[i++] * scale + offsetY;
103
+
104
+ path.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, cpx, cpy);
105
+
106
+ break;
107
+ }
108
+ }
109
+ }
110
+
111
+ return { offsetX: glyph.ha * scale, path: path };
112
+ }
113
+
114
+ export { Font };
@@ -0,0 +1,146 @@
1
+ import { Vector2 } from '../Math/Vector2.js';
2
+ import { CurvePath } from '../Curve/CurvePath.js';
3
+ import { EllipseCurve } from '../curves/EllipseCurve.js';
4
+ import { SplineCurve } from '../curves/SplineCurve.js';
5
+ import { CubicBezierCurve } from '../curves/CubicBezierCurve.js';
6
+ import { QuadraticBezierCurve } from '../curves/QuadraticBezierCurve.js';
7
+ import { LineCurve } from '../curves/LineCurve.js';
8
+
9
+ class Path extends CurvePath {
10
+ constructor(points) {
11
+ super();
12
+
13
+ this.type = 'Path';
14
+ this.currentPoint = new Vector2();
15
+
16
+ if (points) {
17
+ this.setFromPoints(points);
18
+ }
19
+ }
20
+
21
+ setFromPoints(points) {
22
+ this.moveTo(points[0].x, points[0].y);
23
+
24
+ for (let i = 1, l = points.length; i < l; i++) {
25
+ this.lineTo(points[i].x, points[i].y);
26
+ }
27
+
28
+ return this;
29
+ }
30
+
31
+ moveTo(x, y) {
32
+ this.currentPoint.set(x, y); // TODO consider referencing vectors instead of copying?
33
+
34
+ return this;
35
+ }
36
+
37
+ lineTo(x, y) {
38
+ const curve = new LineCurve(this.currentPoint.clone(), new Vector2(x, y));
39
+ this.curves.push(curve);
40
+
41
+ this.currentPoint.set(x, y);
42
+
43
+ return this;
44
+ }
45
+
46
+ quadraticCurveTo(aCPx, aCPy, aX, aY) {
47
+ const curve = new QuadraticBezierCurve(this.currentPoint.clone(), new Vector2(aCPx, aCPy), new Vector2(aX, aY));
48
+
49
+ this.curves.push(curve);
50
+
51
+ this.currentPoint.set(aX, aY);
52
+
53
+ return this;
54
+ }
55
+
56
+ bezierCurveTo(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {
57
+ const curve = new CubicBezierCurve(this.currentPoint.clone(), new Vector2(aCP1x, aCP1y), new Vector2(aCP2x, aCP2y), new Vector2(aX, aY));
58
+
59
+ this.curves.push(curve);
60
+
61
+ this.currentPoint.set(aX, aY);
62
+
63
+ return this;
64
+ }
65
+
66
+ splineThru(pts /*Array of Vector*/) {
67
+ const npts = [this.currentPoint.clone()].concat(pts);
68
+
69
+ const curve = new SplineCurve(npts);
70
+ this.curves.push(curve);
71
+
72
+ this.currentPoint.copy(pts[pts.length - 1]);
73
+
74
+ return this;
75
+ }
76
+
77
+ arc(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {
78
+ const x0 = this.currentPoint.x;
79
+ const y0 = this.currentPoint.y;
80
+
81
+ this.absarc(aX + x0, aY + y0, aRadius, aStartAngle, aEndAngle, aClockwise);
82
+
83
+ return this;
84
+ }
85
+
86
+ absarc(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {
87
+ this.absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise);
88
+
89
+ return this;
90
+ }
91
+
92
+ ellipse(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation) {
93
+ const x0 = this.currentPoint.x;
94
+ const y0 = this.currentPoint.y;
95
+
96
+ this.absellipse(aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation);
97
+
98
+ return this;
99
+ }
100
+
101
+ absellipse(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation) {
102
+ const curve = new EllipseCurve(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation);
103
+
104
+ if (this.curves.length > 0) {
105
+ // if a previous curve is present, attempt to join
106
+ const firstPoint = curve.getPoint(0);
107
+
108
+ if (!firstPoint.equals(this.currentPoint)) {
109
+ this.lineTo(firstPoint.x, firstPoint.y);
110
+ }
111
+ }
112
+
113
+ this.curves.push(curve);
114
+
115
+ const lastPoint = curve.getPoint(1);
116
+ this.currentPoint.copy(lastPoint);
117
+
118
+ return this;
119
+ }
120
+
121
+ copy(source) {
122
+ super.copy(source);
123
+
124
+ this.currentPoint.copy(source.currentPoint);
125
+
126
+ return this;
127
+ }
128
+
129
+ toJSON() {
130
+ const data = super.toJSON();
131
+
132
+ data.currentPoint = this.currentPoint.toArray();
133
+
134
+ return data;
135
+ }
136
+
137
+ fromJSON(json) {
138
+ super.fromJSON(json);
139
+
140
+ this.currentPoint.fromArray(json.currentPoint);
141
+
142
+ return this;
143
+ }
144
+ }
145
+
146
+ export { Path };
@@ -0,0 +1,222 @@
1
+ import { Path } from './Path.js';
2
+ import { Shape } from '../Curve/Shape.js';
3
+ import { ShapeUtils } from '../TextGeometry/ShapeUtils.js';
4
+
5
+ class ShapePath {
6
+ constructor() {
7
+ this.type = 'ShapePath';
8
+ this.color = [1.0, 1.0, 1.0];
9
+
10
+ this.subPaths = [];
11
+ this.currentPath = null;
12
+ }
13
+
14
+ moveTo(x, y) {
15
+ this.currentPath = new Path();
16
+ this.subPaths.push(this.currentPath);
17
+ this.currentPath.moveTo(x, y);
18
+
19
+ return this;
20
+ }
21
+
22
+ lineTo(x, y) {
23
+ this.currentPath.lineTo(x, y);
24
+
25
+ return this;
26
+ }
27
+
28
+ quadraticCurveTo(aCPx, aCPy, aX, aY) {
29
+ this.currentPath.quadraticCurveTo(aCPx, aCPy, aX, aY);
30
+
31
+ return this;
32
+ }
33
+
34
+ bezierCurveTo(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {
35
+ this.currentPath.bezierCurveTo(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY);
36
+
37
+ return this;
38
+ }
39
+
40
+ splineThru(pts) {
41
+ this.currentPath.splineThru(pts);
42
+
43
+ return this;
44
+ }
45
+
46
+ toShapes(isCCW) {
47
+ function toShapesNoHoles(inSubpaths) {
48
+ const shapes = [];
49
+
50
+ for (let i = 0, l = inSubpaths.length; i < l; i++) {
51
+ const tmpPath = inSubpaths[i];
52
+
53
+ const tmpShape = new Shape();
54
+ tmpShape.curves = tmpPath.curves;
55
+
56
+ shapes.push(tmpShape);
57
+ }
58
+
59
+ return shapes;
60
+ }
61
+
62
+ function isPointInsidePolygon(inPt, inPolygon) {
63
+ const polyLen = inPolygon.length;
64
+
65
+ // inPt on polygon contour => immediate success or
66
+ // toggling of inside/outside at every single! intersection point of an edge
67
+ // with the horizontal line through inPt, left of inPt
68
+ // not counting lowerY endpoints of edges and whole edges on that line
69
+ let inside = false;
70
+ for (let p = polyLen - 1, q = 0; q < polyLen; p = q++) {
71
+ let edgeLowPt = inPolygon[p];
72
+ let edgeHighPt = inPolygon[q];
73
+
74
+ let edgeDx = edgeHighPt.x - edgeLowPt.x;
75
+ let edgeDy = edgeHighPt.y - edgeLowPt.y;
76
+
77
+ if (Math.abs(edgeDy) > Number.EPSILON) {
78
+ // not parallel
79
+ if (edgeDy < 0) {
80
+ edgeLowPt = inPolygon[q];
81
+ edgeDx = -edgeDx;
82
+ edgeHighPt = inPolygon[p];
83
+ edgeDy = -edgeDy;
84
+ }
85
+
86
+ if (inPt.y < edgeLowPt.y || inPt.y > edgeHighPt.y) continue;
87
+
88
+ if (inPt.y === edgeLowPt.y) {
89
+ if (inPt.x === edgeLowPt.x) return true; // inPt is on contour ?
90
+ // continue; // no intersection or edgeLowPt => doesn't count !!!
91
+ } else {
92
+ const perpEdge = edgeDy * (inPt.x - edgeLowPt.x) - edgeDx * (inPt.y - edgeLowPt.y);
93
+ if (perpEdge === 0) return true; // inPt is on contour ?
94
+ if (perpEdge < 0) continue;
95
+ inside = !inside; // true intersection left of inPt
96
+ }
97
+ } else {
98
+ // parallel or collinear
99
+ if (inPt.y !== edgeLowPt.y) continue; // parallel
100
+ // edge lies on the same horizontal line as inPt
101
+ if ((edgeHighPt.x <= inPt.x && inPt.x <= edgeLowPt.x) || (edgeLowPt.x <= inPt.x && inPt.x <= edgeHighPt.x)) return true; // inPt: Point on contour !
102
+ // continue;
103
+ }
104
+ }
105
+
106
+ return inside;
107
+ }
108
+
109
+ const isClockWise = ShapeUtils.isClockWise;
110
+
111
+ const subPaths = this.subPaths;
112
+ if (subPaths.length === 0) return [];
113
+
114
+ let solid, tmpPath, tmpShape;
115
+ const shapes = [];
116
+
117
+ if (subPaths.length === 1) {
118
+ tmpPath = subPaths[0];
119
+ tmpShape = new Shape();
120
+ tmpShape.curves = tmpPath.curves;
121
+ shapes.push(tmpShape);
122
+ return shapes;
123
+ }
124
+
125
+ let holesFirst = !isClockWise(subPaths[0].getPoints());
126
+ holesFirst = isCCW ? !holesFirst : holesFirst;
127
+
128
+ // console.log("Holes first", holesFirst);
129
+
130
+ const betterShapeHoles = [];
131
+ const newShapes = [];
132
+ let newShapeHoles = [];
133
+ let mainIdx = 0;
134
+ let tmpPoints;
135
+
136
+ newShapes[mainIdx] = undefined;
137
+ newShapeHoles[mainIdx] = [];
138
+
139
+ for (let i = 0, l = subPaths.length; i < l; i++) {
140
+ tmpPath = subPaths[i];
141
+ tmpPoints = tmpPath.getPoints();
142
+ solid = isClockWise(tmpPoints);
143
+ solid = isCCW ? !solid : solid;
144
+
145
+ if (solid) {
146
+ if (!holesFirst && newShapes[mainIdx]) mainIdx++;
147
+
148
+ newShapes[mainIdx] = { s: new Shape(), p: tmpPoints };
149
+ newShapes[mainIdx].s.curves = tmpPath.curves;
150
+
151
+ if (holesFirst) mainIdx++;
152
+ newShapeHoles[mainIdx] = [];
153
+
154
+ //console.log('cw', i);
155
+ } else {
156
+ newShapeHoles[mainIdx].push({ h: tmpPath, p: tmpPoints[0] });
157
+
158
+ //console.log('ccw', i);
159
+ }
160
+ }
161
+
162
+ // only Holes? -> probably all Shapes with wrong orientation
163
+ if (!newShapes[0]) return toShapesNoHoles(subPaths);
164
+
165
+ if (newShapes.length > 1) {
166
+ let ambiguous = false;
167
+ let toChange = 0;
168
+
169
+ for (let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++) {
170
+ betterShapeHoles[sIdx] = [];
171
+ }
172
+
173
+ for (let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++) {
174
+ const sho = newShapeHoles[sIdx];
175
+
176
+ for (let hIdx = 0; hIdx < sho.length; hIdx++) {
177
+ const ho = sho[hIdx];
178
+ let hole_unassigned = true;
179
+
180
+ for (let s2Idx = 0; s2Idx < newShapes.length; s2Idx++) {
181
+ if (isPointInsidePolygon(ho.p, newShapes[s2Idx].p)) {
182
+ if (sIdx !== s2Idx) toChange++;
183
+
184
+ if (hole_unassigned) {
185
+ hole_unassigned = false;
186
+ betterShapeHoles[s2Idx].push(ho);
187
+ } else {
188
+ ambiguous = true;
189
+ }
190
+ }
191
+ }
192
+
193
+ if (hole_unassigned) {
194
+ betterShapeHoles[sIdx].push(ho);
195
+ }
196
+ }
197
+ }
198
+
199
+ if (toChange > 0 && ambiguous === false) {
200
+ newShapeHoles = betterShapeHoles;
201
+ }
202
+ }
203
+
204
+ let tmpHoles;
205
+
206
+ for (let i = 0, il = newShapes.length; i < il; i++) {
207
+ tmpShape = newShapes[i].s;
208
+ shapes.push(tmpShape);
209
+ tmpHoles = newShapeHoles[i];
210
+
211
+ for (let j = 0, jl = tmpHoles.length; j < jl; j++) {
212
+ tmpShape.holes.push(tmpHoles[j].h);
213
+ }
214
+ }
215
+
216
+ //console.log("shape", shapes);
217
+
218
+ return shapes;
219
+ }
220
+ }
221
+
222
+ export { ShapePath };
@@ -0,0 +1,70 @@
1
+ import { Earcut } from './Earcut.js';
2
+
3
+ class ShapeUtils {
4
+ // calculate area of the contour polygon
5
+
6
+ static area(contour) {
7
+ const n = contour.length;
8
+ let a = 0.0;
9
+
10
+ for (let p = n - 1, q = 0; q < n; p = q++) {
11
+ a += contour[p].x * contour[q].y - contour[q].x * contour[p].y;
12
+ }
13
+
14
+ return a * 0.5;
15
+ }
16
+
17
+ static isClockWise(pts) {
18
+ return ShapeUtils.area(pts) < 0;
19
+ }
20
+
21
+ static triangulateShape(contour, holes) {
22
+ const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ]
23
+ const holeIndices = []; // array of hole indices
24
+ const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ]
25
+
26
+ removeDupEndPts(contour);
27
+ addContour(vertices, contour);
28
+
29
+ //
30
+
31
+ let holeIndex = contour.length;
32
+
33
+ holes.forEach(removeDupEndPts);
34
+
35
+ for (let i = 0; i < holes.length; i++) {
36
+ holeIndices.push(holeIndex);
37
+ holeIndex += holes[i].length;
38
+ addContour(vertices, holes[i]);
39
+ }
40
+
41
+ //
42
+
43
+ const triangles = Earcut.triangulate(vertices, holeIndices);
44
+
45
+ //
46
+
47
+ for (let i = 0; i < triangles.length; i += 3) {
48
+ faces.push(triangles.slice(i, i + 3));
49
+ }
50
+
51
+ return faces;
52
+ }
53
+ }
54
+
55
+ function removeDupEndPts(points) {
56
+ const l = points.length;
57
+
58
+ if (l > 2 && points[l - 1].equals(points[0])) {
59
+ points.pop();
60
+ }
61
+ }
62
+
63
+ function addContour(vertices, contour) {
64
+ for (let i = 0; i < contour.length; i++) {
65
+ vertices.push(contour[i].x);
66
+ vertices.push(contour[i].y);
67
+ }
68
+ }
69
+
70
+ export { ShapeUtils };
@@ -0,0 +1,66 @@
1
+ import { Curve } from '../Curve/Curve.js';
2
+ import { Vector2 } from '../Math/Vector2.js';
3
+ import { CubicBezier } from '../Curve/Interpolations.js';
4
+
5
+ class CubicBezierCurve extends Curve {
6
+ constructor(v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2()) {
7
+ super();
8
+
9
+ this.isCubicBezierCurve = true;
10
+
11
+ this.type = 'CubicBezierCurve';
12
+
13
+ this.v0 = v0;
14
+ this.v1 = v1;
15
+ this.v2 = v2;
16
+ this.v3 = v3;
17
+ }
18
+
19
+ getPoint(t, optionalTarget = new Vector2()) {
20
+ const point = optionalTarget;
21
+
22
+ const v0 = this.v0,
23
+ v1 = this.v1,
24
+ v2 = this.v2,
25
+ v3 = this.v3;
26
+
27
+ point.set(CubicBezier(t, v0.x, v1.x, v2.x, v3.x), CubicBezier(t, v0.y, v1.y, v2.y, v3.y));
28
+
29
+ return point;
30
+ }
31
+
32
+ copy(source) {
33
+ super.copy(source);
34
+
35
+ this.v0.copy(source.v0);
36
+ this.v1.copy(source.v1);
37
+ this.v2.copy(source.v2);
38
+ this.v3.copy(source.v3);
39
+
40
+ return this;
41
+ }
42
+
43
+ toJSON() {
44
+ const data = super.toJSON();
45
+
46
+ data.v0 = this.v0.toArray();
47
+ data.v1 = this.v1.toArray();
48
+ data.v2 = this.v2.toArray();
49
+ data.v3 = this.v3.toArray();
50
+
51
+ return data;
52
+ }
53
+
54
+ fromJSON(json) {
55
+ super.fromJSON(json);
56
+
57
+ this.v0.fromArray(json.v0);
58
+ this.v1.fromArray(json.v1);
59
+ this.v2.fromArray(json.v2);
60
+ this.v3.fromArray(json.v3);
61
+
62
+ return this;
63
+ }
64
+ }
65
+
66
+ export { CubicBezierCurve };
@@ -0,0 +1,10 @@
1
+ // export { ArcCurve } from './ArcCurve.js';
2
+ // export { CatmullRomCurve3 } from './CatmullRomCurve3.js';
3
+ // export { CubicBezierCurve } from './CubicBezierCurve.js';
4
+ // export { CubicBezierCurve3 } from './CubicBezierCurve3.js';
5
+ // export { EllipseCurve } from './EllipseCurve.js';
6
+ export { LineCurve } from './LineCurve.js';
7
+ // export { LineCurve3 } from './LineCurve3.js';
8
+ // export { QuadraticBezierCurve } from './QuadraticBezierCurve.js';
9
+ // export { QuadraticBezierCurve3 } from './QuadraticBezierCurve3.js';
10
+ // export { SplineCurve } from './SplineCurve.js';