paperjs-offset 1.0.7 → 2.0.0

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.
@@ -1,5 +0,0 @@
1
- export declare type StrokeJoinType = 'miter' | 'bevel' | 'round';
2
- export declare type StrokeCapType = 'round' | 'butt';
3
- export declare type PathType = paper.Path | paper.CompoundPath;
4
- export declare function offsetPath(path: PathType, offset: number, join: StrokeJoinType, limit: number): PathType;
5
- export declare function offsetStroke(path: PathType, offset: number, join: StrokeJoinType, cap: StrokeCapType, limit: number): PathType;
Binary file
package/src/bundle.ts DELETED
@@ -1,18 +0,0 @@
1
- import paper from 'paper';
2
- import ExtendPaperJs, { PaperOffset } from './index';
3
-
4
- ExtendPaperJs(paper);
5
-
6
- declare global {
7
- interface Window {
8
- PaperOffset: {
9
- offset: typeof PaperOffset.offset;
10
- offsetStroke: typeof PaperOffset.offsetStroke;
11
- }
12
- }
13
- }
14
-
15
- window.PaperOffset = {
16
- offset: PaperOffset.offset,
17
- offsetStroke: PaperOffset.offsetStroke,
18
- };
package/src/index.ts DELETED
@@ -1,56 +0,0 @@
1
- import paper from 'paper';
2
- import { StrokeJoinType, PathType, StrokeCapType, offsetPath, offsetStroke } from './offset';
3
-
4
- export interface OffsetOptions {
5
- join?: StrokeJoinType;
6
- cap?: StrokeCapType;
7
- limit?: number;
8
- insert?: boolean;
9
- }
10
-
11
- export class PaperOffset {
12
- public static offset(path: PathType, offset: number, options?: OffsetOptions): PathType {
13
- options = options || {};
14
- const newPath = offsetPath(path, offset, options.join || 'miter', options.limit || 10);
15
- if (options.insert === undefined) {
16
- options.insert = true;
17
- }
18
- if (options.insert) {
19
- (path.parent || paper.project.activeLayer).addChild(newPath);
20
- }
21
- return newPath;
22
- }
23
-
24
- public static offsetStroke(path: PathType, offset: number, options?: OffsetOptions): PathType {
25
- options = options || {};
26
- const newStroke = offsetStroke(path, offset, options.join || 'miter', options.cap || 'butt', options.limit || 10);
27
- if (options.insert === undefined) {
28
- options.insert = true;
29
- }
30
- if (options.insert) {
31
- (path.parent || paper.project.activeLayer).addChild(newStroke);
32
- }
33
- return newStroke;
34
- }
35
- }
36
-
37
- /**
38
- * @deprecated EXTEND existing paper module is not recommend anymore
39
- */
40
- export default function ExtendPaperJs(paperNs: any) {
41
- paperNs.Path.prototype.offset = function(offset: number, options?: OffsetOptions) {
42
- return PaperOffset.offset(this, offset, options);
43
- };
44
-
45
- paperNs.Path.prototype.offsetStroke = function(offset: number, options?: OffsetOptions) {
46
- return PaperOffset.offsetStroke(this, offset, options);
47
- };
48
-
49
- paperNs.CompoundPath.prototype.offset = function(offset: number, options?: OffsetOptions) {
50
- return PaperOffset.offset(this, offset, options);
51
- };
52
-
53
- paperNs.CompoundPath.prototype.offsetStroke = function(offset: number, options?: OffsetOptions) {
54
- return PaperOffset.offsetStroke(this, offset, options);
55
- };
56
- }
package/src/offset.ts DELETED
@@ -1,356 +0,0 @@
1
- import paper from 'paper';
2
- import { Arrayex } from 'arrayex';
3
-
4
- export type StrokeJoinType = 'miter' | 'bevel' | 'round';
5
- export type StrokeCapType = 'round' | 'butt';
6
- export type PathType = paper.Path | paper.CompoundPath;
7
-
8
- type HandleType = 'handleIn' | 'handleOut';
9
-
10
- /**
11
- * Offset the start/terminal segment of a bezier curve
12
- * @param segment segment to offset
13
- * @param curve curve to offset
14
- * @param handleNormal the normal of the the line formed of two handles
15
- * @param offset offset value
16
- */
17
- function offsetSegment(segment: paper.Segment, curve: paper.Curve, handleNormal: paper.Point, offset: number) {
18
- const isFirst = segment.curve === curve;
19
- // get offset vector
20
- const offsetVector = (curve.getNormalAtTime(isFirst ? 0 : 1)).multiply(offset);
21
- // get offset point
22
- const point = segment.point.add(offsetVector);
23
- const newSegment = new paper.Segment(point);
24
- // handleOut for start segment & handleIn for terminal segment
25
- const handle = (isFirst ? 'handleOut' : 'handleIn') as HandleType;
26
- newSegment[handle] = segment[handle]!.add(handleNormal.subtract(offsetVector).divide(2));
27
- return newSegment;
28
- }
29
-
30
- /**
31
- * Adaptive offset a curve by repeatly apply the approximation proposed by Tiller and Hanson.
32
- * @param curve curve to offset
33
- * @param offset offset value
34
- */
35
- function adaptiveOffsetCurve(curve: paper.Curve, offset: number): paper.Segment[] {
36
- const hNormal = (new paper.Curve(curve.segment1.handleOut!.add(curve.segment1.point), new paper.Point(0, 0),
37
- new paper.Point(0, 0), curve.segment2.handleIn!.add(curve.segment2.point))).getNormalAtTime(0.5).multiply(offset);
38
- const segment1 = offsetSegment(curve.segment1, curve, hNormal, offset);
39
- const segment2 = offsetSegment(curve.segment2, curve, hNormal, offset);
40
- // divide && re-offset
41
- const offsetCurve = new paper.Curve(segment1, segment2);
42
- // if the offset curve is not self intersected, divide it
43
- if (offsetCurve.getIntersections(offsetCurve).length === 0) {
44
- const threshold = Math.min(Math.abs(offset) / 10, 1);
45
- const midOffset = offsetCurve.getPointAtTime(0.5).getDistance(curve.getPointAtTime(0.5));
46
- if (Math.abs(midOffset - Math.abs(offset)) > threshold) {
47
- const subCurve = curve.divideAtTime(0.5);
48
- if (subCurve != null) {
49
- return [...adaptiveOffsetCurve(curve, offset), ...adaptiveOffsetCurve(subCurve, offset)];
50
- }
51
- }
52
- }
53
- return [segment1, segment2];
54
- }
55
-
56
- /**
57
- * Create a round join segment between two adjacent segments.
58
- */
59
- function makeRoundJoin(segment1: paper.Segment, segment2: paper.Segment, originPoint: paper.Point, radius: number) {
60
- const through = segment1.point.subtract(originPoint).add(segment2.point.subtract(originPoint))
61
- .normalize(Math.abs(radius)).add(originPoint);
62
- const arc = new paper.Path.Arc({ from: segment1.point, to: segment2.point, through, insert: false });
63
- segment1.handleOut = arc.firstSegment.handleOut;
64
- segment2.handleIn = arc.lastSegment.handleIn;
65
- return arc.segments.length === 3 ? arc.segments[1] : null;
66
- }
67
-
68
- function det(p1: paper.Point, p2: paper.Point) {
69
- return p1.x * p2.y - p1.y * p2.x;
70
- }
71
-
72
- /**
73
- * Get the intersection point of point based lines
74
- */
75
- function getPointLineIntersections(p1: paper.Point, p2: paper.Point, p3: paper.Point, p4: paper.Point) {
76
- const l1 = p1.subtract(p2);
77
- const l2 = p3.subtract(p4);
78
- const dl1 = det(p1, p2);
79
- const dl2 = det(p3, p4);
80
- return new paper.Point(dl1 * l2.x - l1.x * dl2, dl1 * l2.y - l1.y * dl2).divide(det(l1, l2));
81
- }
82
-
83
- /**
84
- * Connect two adjacent bezier curve, each curve is represented by two segments,
85
- * create different types of joins or simply removal redundant segment.
86
- */
87
- function connectAdjacentBezier(segments1: paper.Segment[], segments2: paper.Segment[], origin: paper.Segment, joinType: StrokeJoinType, offset: number, limit: number) {
88
- const curve1 = new paper.Curve(segments1[0], segments1[1]);
89
- const curve2 = new paper.Curve(segments2[0], segments2[1]);
90
- const intersection = curve1.getIntersections(curve2);
91
- const distance = segments1[1].point.getDistance(segments2[0].point);
92
- if (origin.isSmooth()) {
93
- segments2[0].handleOut = segments2[0].handleOut!.project(origin.handleOut!);
94
- segments2[0].handleIn = segments1[1].handleIn!.project(origin.handleIn!);
95
- segments2[0].point = segments1[1].point.add(segments2[0].point).divide(2);
96
- segments1.pop();
97
- } else {
98
- if (intersection.length === 0) {
99
- if (distance > Math.abs(offset) * 0.1) {
100
- // connect
101
- switch (joinType) {
102
- case 'miter':
103
- const join = getPointLineIntersections(curve1.point2, curve1.point2.add(curve1.getTangentAtTime(1)),
104
- curve2.point1, curve2.point1.add(curve2.getTangentAtTime(0)));
105
- // prevent sharp angle
106
- const joinOffset = Math.max(join.getDistance(curve1.point2), join.getDistance(curve2.point1));
107
- if (joinOffset < Math.abs(offset) * limit) {
108
- segments1.push(new paper.Segment(join));
109
- }
110
- break;
111
- case 'round':
112
- const mid = makeRoundJoin(segments1[1], segments2[0], origin.point, offset);
113
- if (mid) {
114
- segments1.push(mid);
115
- }
116
- break;
117
- default: break;
118
- }
119
- } else {
120
- segments2[0].handleIn = segments1[1].handleIn;
121
- segments1.pop();
122
- }
123
- } else {
124
- const second1 = curve1.divideAt(intersection[0]);
125
- if (second1) {
126
- const join = second1.segment1;
127
- const second2 = curve2.divideAt(curve2.getIntersections(curve1)[0]);
128
- join.handleOut = second2 ? second2.segment1.handleOut : segments2[0].handleOut;
129
- segments1.pop();
130
- segments2[0] = join;
131
- } else {
132
- segments2[0].handleIn = segments1[1].handleIn;
133
- segments1.pop();
134
- }
135
- }
136
- }
137
- }
138
-
139
- /**
140
- * Connect all the segments together.
141
- */
142
- function connectBeziers(rawSegments: paper.Segment[][], join: StrokeJoinType, source: paper.Path, offset: number, limit: number) {
143
- const originSegments = source.segments;
144
- const first = rawSegments[0].slice();
145
- for (let i = 0; i < rawSegments.length - 1; ++i) {
146
- connectAdjacentBezier(rawSegments[i], rawSegments[i + 1], originSegments[i + 1], join, offset, limit);
147
- }
148
- if (source.closed) {
149
- connectAdjacentBezier(rawSegments[rawSegments.length - 1], first, originSegments[0], join, offset, limit);
150
- rawSegments[0][0] = first[0];
151
- }
152
- return rawSegments;
153
- }
154
-
155
- function reduceSingleChildCompoundPath(path: PathType) {
156
- if (path.children.length === 1) {
157
- path = path.children[0] as paper.Path;
158
- path.remove(); // remove from parent, this is critical, or the style attributes will be ignored
159
- }
160
- return path;
161
- }
162
-
163
- /** Normalize a path, always clockwise, non-self-intersection, ignore really small components, and no one-component compound path. */
164
- function normalize(path: PathType, areaThreshold = 0.01) {
165
- if (path.closed) {
166
- const ignoreArea = Math.abs(path.area * areaThreshold);
167
- if (!path.clockwise) {
168
- path.reverse();
169
- }
170
- path = path.unite(path, { insert: false }) as PathType;
171
- if (path instanceof paper.CompoundPath) {
172
- path.children.filter((c) => Math.abs((c as PathType).area) < ignoreArea).forEach((c) => c.remove());
173
- if (path.children.length === 1) {
174
- return reduceSingleChildCompoundPath(path);
175
- }
176
- }
177
- }
178
- return path;
179
- }
180
-
181
- function isSameDirection(partialPath: paper.Path, fullPath: PathType) {
182
- const offset1 = partialPath.segments[0].location.offset;
183
- const offset2 = partialPath.segments[Math.max(1, Math.floor(partialPath.segments.length / 2))].location.offset;
184
- const sampleOffset = (offset1 + offset2) / 3;
185
- const originOffset1 = fullPath.getNearestLocation(partialPath.getPointAt(sampleOffset)).offset;
186
- const originOffset2 = fullPath.getNearestLocation(partialPath.getPointAt(2 * sampleOffset)).offset;
187
- return originOffset1 < originOffset2;
188
- }
189
-
190
- /** Remove self intersection when offset is negative by point direction dectection. */
191
- function removeIntersection(path: PathType) {
192
- const newPath = path.unite(path, { insert: false }) as PathType;
193
- if (newPath instanceof paper.CompoundPath) {
194
- (newPath.children as paper.Path[]).filter((c) => {
195
- if (c.segments.length > 1) {
196
- return !isSameDirection(c, path);
197
- } else {
198
- return true;
199
- }
200
- }).forEach((c) => c.remove());
201
- return reduceSingleChildCompoundPath(newPath);
202
- }
203
- return path;
204
- }
205
-
206
- function getSegments(path: PathType) {
207
- if (path instanceof paper.CompoundPath) {
208
- return Arrayex.Flat<paper.Segment>(path.children.map((c) => (c as paper.Path).segments));
209
- } else {
210
- return (path as paper.Path).segments;
211
- }
212
- }
213
-
214
- /**
215
- * Remove impossible segments in negative offset condition.
216
- */
217
- function removeOutsiders(newPath: PathType, path: PathType) {
218
- const segments = getSegments(newPath).slice();
219
- segments.forEach((segment) => {
220
- if (!path.contains(segment.point)) {
221
- segment.remove();
222
- }
223
- });
224
- }
225
-
226
- function preparePath(path: paper.Path, offset: number): [paper.Path, number] {
227
- const source = path.clone({ insert: false }) as paper.Path;
228
- source.reduce({});
229
- if (!path.clockwise) {
230
- source.reverse();
231
- offset = -offset;
232
- }
233
- return [source, offset];
234
- }
235
-
236
- function offsetSimpleShape(path: paper.Path, offset: number, join: StrokeJoinType, limit: number): PathType {
237
- let source: paper.Path;
238
- [source, offset] = preparePath(path, offset);
239
- const curves = source.curves.slice();
240
- const raws = Arrayex.Divide(Arrayex.Flat<paper.Segment>(curves.map((curve) => adaptiveOffsetCurve(curve, offset))), 2);
241
- const segments = Arrayex.Flat(connectBeziers(raws, join, source, offset, limit));
242
- const newPath = removeIntersection(new paper.Path({ segments, insert: false, closed: path.closed }));
243
- newPath.reduce({});
244
- if (source.closed && ((source.clockwise && offset < 0) || (!source.clockwise && offset > 0))) {
245
- removeOutsiders(newPath, path);
246
- }
247
- // recovery path
248
- if (source.clockwise !== path.clockwise) {
249
- newPath.reverse();
250
- }
251
- return normalize(newPath);
252
- }
253
-
254
- function makeRoundCap(from: paper.Segment, to: paper.Segment, offset: number) {
255
- const origin = from.point.add(to.point).divide(2);
256
- const normal = to.point.subtract(from.point).rotate(-90, new paper.Point(0, 0)).normalize(offset);
257
- const through = origin.add(normal);
258
- const arc = new paper.Path.Arc({ from: from.point, to: to.point, through, insert: false });
259
- return arc.segments;
260
- }
261
-
262
- function connectSide(outer: PathType, inner: paper.Path, offset: number, cap: StrokeCapType): paper.Path {
263
- if (outer instanceof paper.CompoundPath) {
264
- let cs = outer.children.map((c) => ({ c, a: Math.abs((c as paper.Path).area) }));
265
- cs = cs.sort((c1, c2) => c2.a - c1.a);
266
- outer = cs[0].c as paper.Path;
267
- }
268
- const oSegments = (outer as paper.Path).segments.slice();
269
- const iSegments = inner.segments.slice();
270
- switch (cap) {
271
- case 'round':
272
- const heads = makeRoundCap(iSegments[iSegments.length - 1], oSegments[0], offset);
273
- const tails = makeRoundCap(oSegments[oSegments.length - 1], iSegments[0], offset);
274
- const result = new paper.Path({ segments: [...heads, ...oSegments, ...tails, ...iSegments], closed: true, insert: false });
275
- result.reduce({});
276
- return result;
277
- default: return new paper.Path({ segments: [...oSegments, ...iSegments], closed: true, insert: false });
278
- }
279
- }
280
-
281
- function offsetSimpleStroke(path: paper.Path, offset: number, join: StrokeJoinType, cap: StrokeCapType, limit: number): PathType {
282
- offset = path.clockwise ? offset : -offset;
283
- const positiveOffset = offsetSimpleShape(path, offset, join, limit);
284
- const negativeOffset = offsetSimpleShape(path, -offset, join, limit);
285
- if (path.closed) {
286
- return positiveOffset.subtract(negativeOffset, { insert: false }) as PathType;
287
- } else {
288
- let inner = negativeOffset;
289
- let holes = new Array<paper.Path>();
290
- if (negativeOffset instanceof paper.CompoundPath) {
291
- holes = negativeOffset.children.filter((c) => (c as paper.Path).closed) as paper.Path[];
292
- holes.forEach((h) => h.remove());
293
- inner = negativeOffset.children[0] as paper.Path;
294
- }
295
- inner.reverse();
296
- let final = connectSide(positiveOffset, inner as paper.Path, offset, cap) as PathType;
297
- if (holes.length > 0) {
298
- for (const hole of holes) {
299
- final = final.subtract(hole, { insert: false }) as PathType;
300
- }
301
- }
302
- return final;
303
- }
304
- }
305
-
306
- export function offsetPath(path: PathType, offset: number, join: StrokeJoinType, limit: number): PathType {
307
- const nonSIPath = path.unite(path, { insert: false }) as PathType;
308
- let result = nonSIPath;
309
- if (nonSIPath instanceof paper.Path) {
310
- result = offsetSimpleShape(nonSIPath, offset, join, limit);
311
- } else {
312
- const children = Arrayex.Flat((nonSIPath.children as paper.Path[]).map((c) => {
313
- if (c.segments.length > 1) {
314
- if (!isSameDirection(c, path)) {
315
- c.reverse();
316
- }
317
- let offseted = offsetSimpleShape(c, offset, join, limit);
318
- offseted = normalize(offseted);
319
- if (offseted.clockwise !== c.clockwise) {
320
- offseted.reverse();
321
- }
322
- if (offseted instanceof paper.CompoundPath) {
323
- offseted.applyMatrix = true;
324
- return offseted.children;
325
- } else {
326
- return offseted;
327
- }
328
- } else {
329
- return null;
330
- }
331
- }), false);
332
- result = new paper.CompoundPath({ children, insert: false });
333
- }
334
- result.copyAttributes(nonSIPath, false);
335
- result.remove();
336
- return result;
337
- }
338
-
339
- export function offsetStroke(path: PathType, offset: number, join: StrokeJoinType, cap: StrokeCapType, limit: number): PathType {
340
- const nonSIPath = path.unite(path, { insert: false }) as PathType;
341
- let result = nonSIPath as PathType;
342
- if (nonSIPath instanceof paper.Path) {
343
- result = offsetSimpleStroke(nonSIPath, offset, join, cap, limit);
344
- } else {
345
- const children = Arrayex.Flat((nonSIPath.children as paper.Path[]).map((c) => {
346
- return offsetSimpleStroke(c, offset, join, cap, limit);
347
- }));
348
- result = children.reduce((c1, c2) => c1.unite(c2, { insert: false }) as PathType);
349
- }
350
- result.strokeWidth = 0;
351
- result.fillColor = nonSIPath.strokeColor;
352
- result.shadowBlur = nonSIPath.shadowBlur;
353
- result.shadowColor = nonSIPath.shadowColor;
354
- result.shadowOffset = nonSIPath.shadowOffset;
355
- return result;
356
- }
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "moduleResolution": "node",
4
- "target": "es5",
5
- "module":"es2015",
6
- "lib": ["es2015", "es2016", "es2017", "dom"],
7
- "strict": true,
8
- "sourceMap": true,
9
- "declaration": true,
10
- "allowSyntheticDefaultImports": true,
11
- "experimentalDecorators": true,
12
- "emitDecoratorMetadata": true,
13
- "declarationDir": "dist/types",
14
- "outDir": "dist/lib",
15
- "typeRoots": [
16
- "node_modules/@types"
17
- ]
18
- },
19
- "include": ["src"]
20
- }
package/tslint.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "defaultSeverity": "warning",
3
- "extends": [
4
- "tslint:recommended"
5
- ],
6
- "linterOptions": {
7
- "exclude": [
8
- "node_modules/**"
9
- ]
10
- },
11
- "rules": {
12
- "quotemark": [true, "single"],
13
- "interface-name": false,
14
- "ordered-imports": false,
15
- "max-line-length": [true, 180]
16
- }
17
- }