svg-path-simplify 0.4.4 → 0.4.6
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/CHANGELOG.md +19 -0
- package/README.md +9 -1
- package/dist/svg-path-simplify.esm.js +1649 -1205
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +1649 -1204
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +301 -637
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +8 -9
- package/drawing.svg +62 -0
- package/index.html +13 -8
- package/package.json +1 -1
- package/src/index.js +3 -1
- package/src/pathData_get_intersections.js +777 -0
- package/src/pathData_offset.js +201 -0
- package/src/pathData_simplify_cubic.js +19 -21
- package/src/pathData_simplify_cubic_extrapolate.js +147 -8
- package/src/pathData_simplify_cubicsToArcs.js +65 -21
- package/src/pathSimplify-main.js +267 -64
- package/src/pathSimplify-presets.js +1 -1
- package/src/poly-fit-curve-schneider.js +171 -132
- package/src/poly-fit-curve-schneider_check_bulge.js +131 -0
- package/src/poly-offset.js +133 -0
- package/src/simplify_poly_RC.js +29 -7
- package/src/svg-getAttributes.js +1 -1
- package/src/svgii/geometry.js +130 -27
- package/src/svgii/geometry_bbox.js +1 -1
- package/src/svgii/geometry_length.js +1 -1
- package/src/svgii/pathData_analyze.js +6 -28
- package/src/svgii/pathData_convert.js +63 -15
- package/src/svgii/pathData_remove_collinear.js +32 -36
- package/src/svgii/pathData_reorder.js +3 -2
- package/src/svgii/pathData_simplify_refineExtremes.js +1 -3
- package/src/svgii/pathData_stringify.js +132 -6
- package/src/svgii/pathData_toPolygon.js +33 -23
- package/src/svgii/poly_analyze.js +272 -125
- package/src/svgii/poly_analyze_cleanup.js +311 -0
- package/src/svgii/poly_analyze_getTangents.js +125 -0
- package/src/svgii/poly_analyze_get_chunks.js +3 -1
- package/src/svgii/poly_normalize.js +21 -8
- package/src/svgii/poly_to_pathdata.js +477 -8
- package/src/svgii/rounding.js +35 -14
- package/src/svgii/svg_cleanup.js +44 -51
- package/src/svgii/svg_cleanup_convertPathLength.js +5 -0
- package/src/svgii/svg_cleanup_normalize_transforms.js +2 -2
- package/src/svgii/visualize.js +33 -2
- package/v/0.4.5/index.html +1040 -0
- package/v/0.4.5/svg-path-simplify.js +13227 -0
|
@@ -18,66 +18,59 @@ export function pathDataRemoveColinear(pathData, {
|
|
|
18
18
|
let isClosed = pathData[pathData.length - 1].type.toLowerCase() === 'z'
|
|
19
19
|
|
|
20
20
|
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
21
|
-
//let comPrev = pathData[c - 1];
|
|
22
21
|
let com = pathData[c];
|
|
22
|
+
let { type, values } = com;
|
|
23
23
|
let comN = pathData[c + 1] || pathData[l - 1];
|
|
24
|
-
|
|
24
|
+
let valuesN = comN.values;
|
|
25
25
|
let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] }
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
let { type, values } = com;
|
|
29
28
|
let valsL = values.slice(-2)
|
|
30
29
|
p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
|
|
31
30
|
|
|
31
|
+
let nextBezier = type!==comN.type && (comN.type==='C' || comN.type==='Q');
|
|
32
|
+
|
|
32
33
|
|
|
33
34
|
let area = p1 ? getPolygonArea([p0, p, p1], true) : Infinity
|
|
34
35
|
let distSquare = getSquareDistance(p0, p1)
|
|
35
36
|
let distMax = distSquare ? distSquare / 333 * tolerance : 0
|
|
36
37
|
|
|
37
38
|
let isFlat = area < distMax;
|
|
38
|
-
//let isFlat = false;
|
|
39
39
|
let isFlatBez = false;
|
|
40
|
+
let cpts = [];
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
let dx0 = Math.abs(p1.x - p0.x)
|
|
45
|
-
let dy0 = Math.abs(p1.y - p0.y)
|
|
46
|
-
|
|
47
|
-
let dx1 = Math.abs(p.x - p0.x)
|
|
48
|
-
let dy1 = Math.abs(p.y - p0.y)
|
|
49
|
-
|
|
50
|
-
let dx2 = Math.abs(p1.x - p.x)
|
|
51
|
-
let dy2 = Math.abs(p1.y - p.y)
|
|
43
|
+
//if (comN.type === 'C') isFlat = false;
|
|
44
|
+
//if (!flatBezierToLinetos && type === 'C') isFlat = false;
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
/**
|
|
47
|
+
* type change
|
|
48
|
+
* check flatness
|
|
49
|
+
*/
|
|
50
|
+
if (nextBezier) {
|
|
51
|
+
cpts = comN.type === 'C' ?
|
|
52
|
+
[{ x: valuesN[0], y: valuesN[1] }, { x: valuesN[2], y: valuesN[3] }] :
|
|
53
|
+
(comN.type === 'Q' ? [{ x: valuesN[0], y: valuesN[1] }] : []);
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
if (!isFlat) {
|
|
55
|
+
//isFlatBez = commandIsFlat([p0, ...cpts, p], { tolerance });
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
//let crossDiff = Math.abs(cross0-cross1)
|
|
63
|
-
//let cross = Math.max(cross0, cross1)
|
|
64
|
-
let thresh = (dx0 + dy0) * 0.1
|
|
57
|
+
isFlat = commandIsFlat([p0, ...cpts, p], { tolerance });
|
|
58
|
+
//if(isFlat) continue;
|
|
65
59
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
/*
|
|
61
|
+
//console.log('isFlatBez', isFlatBez);
|
|
62
|
+
if(!isFlatBez){
|
|
63
|
+
pathDataN.push(com)
|
|
64
|
+
continue
|
|
70
65
|
}
|
|
71
|
-
|
|
72
|
-
*/
|
|
73
|
-
|
|
66
|
+
*/
|
|
74
67
|
|
|
75
|
-
|
|
68
|
+
}
|
|
76
69
|
|
|
77
70
|
// convert flat beziers to linetos
|
|
78
71
|
if (flatBezierToLinetos && (type === 'C' || type === 'Q')) {
|
|
79
72
|
|
|
80
|
-
|
|
73
|
+
cpts = type === 'C' ?
|
|
81
74
|
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
82
75
|
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
83
76
|
|
|
@@ -92,10 +85,13 @@ export function pathDataRemoveColinear(pathData, {
|
|
|
92
85
|
}
|
|
93
86
|
|
|
94
87
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
88
|
+
/**
|
|
89
|
+
* colinear = simplification success
|
|
90
|
+
* exclude arcs (as always =)
|
|
91
|
+
* as semicircles won't have an area
|
|
92
|
+
*/
|
|
98
93
|
|
|
94
|
+
if (isFlat && c < l - 1 && comN.type !== 'A' && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
|
|
99
95
|
continue;
|
|
100
96
|
}
|
|
101
97
|
|
|
@@ -29,7 +29,8 @@ export function pathDataToTopLeft(pathData) {
|
|
|
29
29
|
let { type, values } = com;
|
|
30
30
|
let valsLen = values.length
|
|
31
31
|
if (valsLen) {
|
|
32
|
-
|
|
32
|
+
// we need rounding otherwise sorting may crash due to e notation
|
|
33
|
+
let p = { type: type, x: +values[valsLen - 2].toFixed(8), y: +values[valsLen - 1].toFixed(8), index: 0 }
|
|
33
34
|
p.index = i
|
|
34
35
|
indices.push(p)
|
|
35
36
|
}
|
|
@@ -37,7 +38,7 @@ export function pathDataToTopLeft(pathData) {
|
|
|
37
38
|
|
|
38
39
|
// reorder to top left most
|
|
39
40
|
//|| a.x - b.x
|
|
40
|
-
indices = indices.sort((a, b) =>
|
|
41
|
+
indices = indices.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
41
42
|
newIndex = indices[0].index
|
|
42
43
|
|
|
43
44
|
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { findSplitT, getExtrapolatedCommand } from "../pathData_simplify_cubic";
|
|
2
|
-
import { getCombinedByDominant } from "../pathData_simplify_cubic_extrapolate";
|
|
2
|
+
import { getCombinedByDominant, getCombinedByDominant_back } from "../pathData_simplify_cubic_extrapolate";
|
|
3
3
|
import { bezierhasExtreme, checkLineIntersection, getDistAv, getDistManhattan, getSquareDistance, interpolate } from "./geometry";
|
|
4
4
|
import { getPathArea, getPolygonArea } from "./geometry_area";
|
|
5
5
|
import { getPathDataBBox } from "./geometry_bbox";
|
|
@@ -58,9 +58,7 @@ export function refineAdjacentExtremes(pathData, {
|
|
|
58
58
|
if (comEx.length === 1) {
|
|
59
59
|
|
|
60
60
|
comEx = comEx[0]
|
|
61
|
-
|
|
62
61
|
pathData[i + 1] = null;
|
|
63
|
-
|
|
64
62
|
pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y]
|
|
65
63
|
pathData[i + 2].cp1 = comEx.cp1
|
|
66
64
|
pathData[i + 2].cp2 = comEx.cp2
|
|
@@ -4,7 +4,129 @@
|
|
|
4
4
|
* d attribute string
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export function pathDataToD(pathData, mode = 0) {
|
|
7
|
+
export function pathDataToD(pathData = [], mode = 0) {
|
|
8
|
+
|
|
9
|
+
mode = parseFloat(mode)
|
|
10
|
+
/*
|
|
11
|
+
0 = max minification
|
|
12
|
+
0.5 = safe
|
|
13
|
+
1 = verbose
|
|
14
|
+
2 = beautify
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
let len = pathData.length;
|
|
18
|
+
let d = ''
|
|
19
|
+
|
|
20
|
+
// group same types
|
|
21
|
+
let pathDataGrouped = mode >0.5 ? JSON.parse(JSON.stringify(pathData)) : [];
|
|
22
|
+
let typePrev = 'M'
|
|
23
|
+
|
|
24
|
+
if (mode < 1) {
|
|
25
|
+
pathDataGrouped = [pathData[0]];
|
|
26
|
+
//pathDataGrouped = [{type:pathData[0].type, values:[...pathData[0].values]}];
|
|
27
|
+
let idx = 0;
|
|
28
|
+
|
|
29
|
+
for (let i = 1; i < len; i++) {
|
|
30
|
+
let com = pathData[i];
|
|
31
|
+
let { type } = com;
|
|
32
|
+
// decouple from object
|
|
33
|
+
let values = [...com.values]
|
|
34
|
+
|
|
35
|
+
// new type
|
|
36
|
+
if (type !== typePrev) {
|
|
37
|
+
pathDataGrouped.push({type, values})
|
|
38
|
+
idx++
|
|
39
|
+
} else {
|
|
40
|
+
pathDataGrouped[idx].values.push(...values)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// update type
|
|
44
|
+
typePrev = type
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// stringify grouped
|
|
49
|
+
len = pathDataGrouped.length;
|
|
50
|
+
let separator_type = mode < 1 ? '' : ' ';
|
|
51
|
+
let separator_command = mode < 1 ? '' : (mode === 1 ? ' ' : `\n`);
|
|
52
|
+
|
|
53
|
+
typePrev = 'M'
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < len; i++) {
|
|
56
|
+
let com = pathDataGrouped[i];
|
|
57
|
+
let { type, values } = com;
|
|
58
|
+
|
|
59
|
+
// we're always starting a path with absolute M!
|
|
60
|
+
let omitType = mode < 1 && ((typePrev === 'M' && type === 'L') || (typePrev === 'm' && type === 'l'))
|
|
61
|
+
|
|
62
|
+
// add type
|
|
63
|
+
if (!omitType) d += type + separator_type;
|
|
64
|
+
|
|
65
|
+
// add values
|
|
66
|
+
let wasSmallFloat = false;
|
|
67
|
+
let separatorVal = ' ';
|
|
68
|
+
|
|
69
|
+
for (let v = 0, vlen = values.length; vlen && v < vlen; v++) {
|
|
70
|
+
let val = values[v];
|
|
71
|
+
let valAbs = Math.abs(val);
|
|
72
|
+
let valStr = val.toString();
|
|
73
|
+
let isNegative = val < 0;
|
|
74
|
+
let sign = isNegative ? '-' : ''
|
|
75
|
+
let isSmallFloat = mode > 0.5 ? false : (val && valAbs < 1);
|
|
76
|
+
let idxSub = isSmallFloat ? (isNegative ? 2 : 1) : 0
|
|
77
|
+
|
|
78
|
+
// we don't need whitespace for first value
|
|
79
|
+
separatorVal = v === 0 || isNegative ? '' : ' '
|
|
80
|
+
|
|
81
|
+
if (mode < 1) {
|
|
82
|
+
// omit leading zero
|
|
83
|
+
if (isSmallFloat) valStr = sign + valStr.substring(idxSub)
|
|
84
|
+
|
|
85
|
+
// omit whitespace for subsequent small floats
|
|
86
|
+
separatorVal = (v === 0 && !omitType) || (wasSmallFloat && isSmallFloat) ?
|
|
87
|
+
(!mode ? '' : (isNegative ? '' : ' '))
|
|
88
|
+
: (isNegative ? '' : ' ');
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// omit separator between large Arc sweep and final x in minify mode
|
|
93
|
+
if (!mode && (type === 'a' || type === 'A')) {
|
|
94
|
+
let pos = (v % 7)
|
|
95
|
+
if (pos > 3 && pos < 6) separatorVal = ''
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
d += `${separatorVal}${valStr}`
|
|
99
|
+
wasSmallFloat = isSmallFloat;
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// add command separator
|
|
104
|
+
if (mode) d += separator_command;
|
|
105
|
+
|
|
106
|
+
// update previous type
|
|
107
|
+
typePrev = type
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//console.log('d', d, mode);
|
|
112
|
+
//console.log('pathDataGrouped', pathDataGrouped);
|
|
113
|
+
//console.log(pathData);
|
|
114
|
+
|
|
115
|
+
return d;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
export function pathDataToD__(pathData, mode = 0) {
|
|
8
130
|
|
|
9
131
|
mode = parseFloat(mode)
|
|
10
132
|
/*
|
|
@@ -79,7 +201,7 @@ export function pathDataToD(pathData, mode = 0) {
|
|
|
79
201
|
valsString = values.join(' ')
|
|
80
202
|
}
|
|
81
203
|
|
|
82
|
-
if(i===len-1) separator_command=''
|
|
204
|
+
if (i === len - 1) separator_command = ''
|
|
83
205
|
d += `${type}${separator_type}${valsString}${separator_command}`;
|
|
84
206
|
}
|
|
85
207
|
|
|
@@ -87,10 +209,14 @@ export function pathDataToD(pathData, mode = 0) {
|
|
|
87
209
|
if (mode < 1) {
|
|
88
210
|
d = d
|
|
89
211
|
.replace(/[A-Za-z]0(?=\.)/g, m => m[0])
|
|
90
|
-
|
|
91
|
-
.replace(/
|
|
92
|
-
|
|
93
|
-
.replace(/
|
|
212
|
+
//Space before small decimals
|
|
213
|
+
.replace(/ 0\./g, " .")
|
|
214
|
+
// Remove space before negatives
|
|
215
|
+
.replace(/ -/g, "-")
|
|
216
|
+
// Remove leading zero from negative decimals
|
|
217
|
+
.replace(/-0\./g, "-.")
|
|
218
|
+
// Convert uppercase 'Z' to lowercase
|
|
219
|
+
.replace(/Z/g, "z")
|
|
94
220
|
}
|
|
95
221
|
|
|
96
222
|
//console.log(`"${d}"`);
|
|
@@ -4,8 +4,8 @@ import { simplifyPolyRD } from "../simplify_poly_radial_distance";
|
|
|
4
4
|
import { getDistAv, getDistManhattan, getSquareDistance, getTatAngles, interpolate, pointAtT } from "./geometry";
|
|
5
5
|
import { getBezierArea, getPolygonArea } from "./geometry_area";
|
|
6
6
|
import { getPolyBBox } from "./geometry_bbox";
|
|
7
|
-
import { addDimensionData, analyzePathData } from "./pathData_analyze";
|
|
8
|
-
import { arcToBezier } from "./pathData_convert";
|
|
7
|
+
import { addDimensionData, analyzePathData, getPathDataVerbose } from "./pathData_analyze";
|
|
8
|
+
import { arcToBezier, convertPathData, pathDataToVerbose } from "./pathData_convert";
|
|
9
9
|
import { pathDataFromPoly } from "./pathData_fromPoly";
|
|
10
10
|
import { addExtremePoints } from "./pathData_split";
|
|
11
11
|
import { pathDataToD } from "./pathData_stringify";
|
|
@@ -23,15 +23,24 @@ import { renderPoint } from "./visualize";
|
|
|
23
23
|
*/
|
|
24
24
|
export function pathDataToPolygonOpt(pathData, {
|
|
25
25
|
precisionPoly = 1,
|
|
26
|
-
autoAccuracy=false,
|
|
27
|
-
polyFormat='
|
|
28
|
-
decimals
|
|
29
|
-
simplifyRD=1,
|
|
30
|
-
simplifyRDP=1,
|
|
26
|
+
autoAccuracy = false,
|
|
27
|
+
polyFormat = 'object',
|
|
28
|
+
decimals = -1,
|
|
29
|
+
simplifyRD = 1,
|
|
30
|
+
simplifyRDP = 1,
|
|
31
31
|
} = {}) {
|
|
32
32
|
|
|
33
33
|
//console.log(pathData);
|
|
34
34
|
|
|
35
|
+
pathData = convertPathData(pathData, {toAbsolute:true, toLonghands:true, arcToCubic:true});
|
|
36
|
+
pathData = addExtremePoints(pathData);
|
|
37
|
+
|
|
38
|
+
//let d2 = pathDataToD(pathData)
|
|
39
|
+
//console.log(d2);
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
pathData = getPathDataVerbose(pathData);
|
|
43
|
+
|
|
35
44
|
let l = pathData.length;
|
|
36
45
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
|
|
37
46
|
let p0 = M
|
|
@@ -59,11 +68,10 @@ simplifyRDP=1,
|
|
|
59
68
|
pts.push(p)
|
|
60
69
|
}
|
|
61
70
|
|
|
62
|
-
|
|
63
71
|
let pts2 = [pts[0]]
|
|
64
72
|
|
|
65
73
|
// adjustments for very small or large paths
|
|
66
|
-
dims = dims.filter(Boolean).sort()
|
|
74
|
+
dims = dims.filter(Boolean).sort((a,b)=>a-b)
|
|
67
75
|
let dimMax = dims[dims.length - 1]
|
|
68
76
|
|
|
69
77
|
let scale = dimMax > 2 && dimMax < 25 ? 1 : (20 / dimMax);
|
|
@@ -104,42 +112,44 @@ simplifyRDP=1,
|
|
|
104
112
|
|
|
105
113
|
|
|
106
114
|
// simplify polygon
|
|
107
|
-
if(simplifyRD>0){
|
|
108
|
-
pts2 = simplifyPolyRD(pts2, {quality:simplifyRD})
|
|
115
|
+
if (simplifyRD > 0) {
|
|
116
|
+
pts2 = simplifyPolyRD(pts2, { quality: simplifyRD })
|
|
109
117
|
}
|
|
110
118
|
|
|
111
119
|
|
|
112
|
-
if(simplifyRDP>0){
|
|
113
|
-
pts2 = simplifyPolyRDP(pts2, {quality:simplifyRDP})
|
|
120
|
+
if (simplifyRDP > 0) {
|
|
121
|
+
pts2 = simplifyPolyRDP(pts2, { quality: simplifyRDP })
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
|
|
117
125
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if(autoAccuracy){
|
|
126
|
+
|
|
127
|
+
if (autoAccuracy) {
|
|
122
128
|
decimals = detectAccuracyPoly(pts)
|
|
123
129
|
}
|
|
124
130
|
|
|
125
|
-
let poly = decimals
|
|
131
|
+
let poly = decimals > -1 ? pts2.map(pt => { return { x: roundTo(pt.x, decimals), y: roundTo(pt.y, decimals) } }) : pts2.map(pt => { return { x: pt.x, y: pt.y } })
|
|
126
132
|
|
|
127
|
-
|
|
133
|
+
pathDataPoly = pathDataFromPoly(poly)
|
|
134
|
+
pathData = pathDataPoly
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if (polyFormat === 'array') {
|
|
128
139
|
poly = poly.map(pt => { return [pt.x, pt.y] })
|
|
129
140
|
}
|
|
130
|
-
else if(polyFormat==='string'){
|
|
141
|
+
else if (polyFormat === 'string') {
|
|
131
142
|
poly = poly.map(pt => { return [pt.x, pt.y].join(',') }).flat().join(' ')
|
|
132
143
|
}
|
|
133
144
|
|
|
134
|
-
|
|
145
|
+
let d= pathDataToD(pathData)
|
|
135
146
|
|
|
136
|
-
return { pathData, poly }
|
|
147
|
+
return { pathData, poly, d }
|
|
137
148
|
|
|
138
149
|
}
|
|
139
150
|
|
|
140
151
|
|
|
141
152
|
|
|
142
|
-
|
|
143
153
|
/**
|
|
144
154
|
* creates precise polygon
|
|
145
155
|
* from command end points
|