svg-path-simplify 0.4.3 → 0.4.4

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +1 -0
  3. package/dist/svg-path-simplify.esm.js +1610 -495
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +1611 -494
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +893 -456
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/dist/svg-path-simplify.poly.cjs +9 -8
  10. package/index.html +58 -17
  11. package/package.json +1 -1
  12. package/src/constants.js +4 -0
  13. package/src/detect_input.js +47 -29
  14. package/src/index.js +8 -0
  15. package/src/pathData_simplify_cubic.js +26 -16
  16. package/src/pathData_simplify_revertToquadratics.js +0 -1
  17. package/src/pathSimplify-main.js +75 -20
  18. package/src/pathSimplify-only-pathdata.js +7 -2
  19. package/src/pathSimplify-presets.js +15 -4
  20. package/src/svg-getAttributes.js +4 -2
  21. package/src/svgii/convert_units.js +1 -1
  22. package/src/svgii/geometry.js +140 -2
  23. package/src/svgii/geometry_bbox_element.js +1 -1
  24. package/src/svgii/geometry_deduceRadius.js +116 -27
  25. package/src/svgii/geometry_length.js +17 -1
  26. package/src/svgii/pathData_analyze.js +18 -0
  27. package/src/svgii/pathData_convert.js +188 -88
  28. package/src/svgii/pathData_fix_directions.js +10 -18
  29. package/src/svgii/pathData_reorder.js +122 -16
  30. package/src/svgii/pathData_simplify_refineCorners.js +130 -35
  31. package/src/svgii/pathData_simplify_refine_round.js +420 -0
  32. package/src/svgii/rounding.js +79 -78
  33. package/src/svgii/svg_cleanup.js +68 -20
  34. package/src/svgii/svg_cleanup_convertPathLength.js +22 -15
  35. package/src/svgii/svg_cleanup_remove_els_and_atts.js +6 -1
  36. package/src/svgii/svg_el_parse_style_props.js +13 -10
  37. package/src/svgii/svg_validate.js +220 -0
  38. package/tests/testSVG.js +14 -1
  39. package/src/svgii/pathData_refine_round.js +0 -222
@@ -143,10 +143,10 @@ export function combineCubicPairs(com1, com2, {
143
143
  let comS = getExtrapolatedCommand(com1, com2, t)
144
144
 
145
145
  // test new point-at-t against original mid segment starting point
146
- let pt = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t)
146
+ let ptI = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t)
147
147
 
148
148
 
149
- let dist0 = getDistManhattan(com1.p, pt)
149
+ let dist0 = getDistManhattan(com1.p, ptI)
150
150
  let dist1 = 0, dist2 = 0;
151
151
  let close = dist0 < maxDist;
152
152
  let success = false;
@@ -162,32 +162,42 @@ export function combineCubicPairs(com1, com2, {
162
162
  * to prevent distortions
163
163
  */
164
164
 
165
- // 2nd segment mid
166
- let pt_2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5)
165
+ // 1st segment mid
166
+ let ptM_seg1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5)
167
+
168
+ let t2 = t * 0.5;
169
+ // combined interpolated mid point
170
+ let ptI_seg1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2)
171
+ dist1 = getDistManhattan(ptM_seg1, ptI_seg1)
167
172
 
168
- // simplified path
169
- let t3 = (1 + t) * 0.5;
170
- let ptS_2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3)
171
- dist1 = getDistManhattan(pt_2, ptS_2)
172
173
 
173
174
  error += dist1;
174
175
 
175
176
  if (dist1 < maxDist) {
176
177
 
177
- //renderPoint(markers, pt_2, 'magenta')
178
- //renderPoint(markers, ptS_2, 'green', '0.5%')
179
-
180
- // 1st segment mid
181
- let pt_1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5)
178
+ // 2nd segment mid
179
+ let ptM_seg2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5)
182
180
 
183
- let t2 = t * 0.5;
184
- let ptS_1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2)
185
- dist2 = getDistManhattan(pt_1, ptS_1)
181
+ // simplified path
182
+ let t3 = (1 + t) * 0.5;
183
+ let ptI_seg2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3)
184
+ dist2 = getDistManhattan(ptM_seg2, ptI_seg2)
186
185
 
187
186
  error += dist2;
188
187
 
189
188
  if (error < maxDist) success = true;
190
189
 
190
+
191
+ /*
192
+ renderPoint(markers, ptM_seg1, 'cyan')
193
+ renderPoint(markers, pt, 'orange', '1.5%', '1')
194
+ renderPoint(markers, ptM_seg2, 'orange')
195
+
196
+ renderPoint(markers, com1.p, 'green')
197
+ //renderPoint(markers, com2.p, 'green')
198
+ renderPoint(markers, ptI_seg1, 'purple')
199
+ */
200
+
191
201
  }
192
202
 
193
203
  } // end 1st try
@@ -6,7 +6,6 @@ export function pathDataRevertCubicToQuadratic(pathData, tolerance=1) {
6
6
  let com = pathData[c]
7
7
  let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
8
8
  if (type === 'C') {
9
- //console.log(com);
10
9
  let comQ = revertCubicQuadratic(p0, cp1, cp2, p, tolerance)
11
10
  if (comQ.type === 'Q') {
12
11
  comQ.extreme = com.extreme
@@ -1,6 +1,6 @@
1
1
  import { detectInputType } from './detect_input';
2
2
  import { simplifyPathDataCubic } from './pathData_simplify_cubic';
3
- import { getDistManhattan, getDistance, getPathDataVertices, interpolate, pointAtT, reducePoints, svgArcToCenterParam, toParametricAngle } from './svgii/geometry';
3
+ import { getDistManhattan, getDistance, getPathDataVertices, getSquareDistance, interpolate, pointAtT, reducePoints, svgArcToCenterParam, toParametricAngle } from './svgii/geometry';
4
4
  import { getPolyBBox } from './svgii/geometry_bbox';
5
5
  import { analyzePathData, getPathDataVerbose } from './svgii/pathData_analyze';
6
6
  import { normalizePathData, parsePathDataNormalized, convertPathData } from './svgii/pathData_convert';
@@ -16,7 +16,7 @@ import { detectAccuracy, roundPathData, roundTo } from './svgii/rounding';
16
16
  import { refineAdjacentExtremes } from './svgii/pathData_simplify_refineExtremes';
17
17
  import { cleanUpSVG, removeEmptySVGEls } from './svgii/svg_cleanup';
18
18
  import { refineRoundedCorners } from './svgii/pathData_simplify_refineCorners';
19
- import { refineRoundSegments } from './svgii/pathData_refine_round';
19
+ import { refineRoundSegments, simplifyAdjacentRound } from './svgii/pathData_simplify_refine_round';
20
20
  import { refineClosingCommand } from './svgii/pathData_remove_short';
21
21
  import { scalePathData } from './svgii/pathData_transform_scale';
22
22
  import { getViewBox } from './svg_getViewbox';
@@ -32,7 +32,7 @@ import { normalizePoly, polyPtsToArray } from './svgii/poly_normalize';
32
32
  import { simplifyPolyRD } from './simplify_poly_radial_distance';
33
33
  import { simplifyPolyRDP, simplifyPolyRDP__, simplifyRDP_rel } from './simplify_poly_RDP';
34
34
  import { getEllipseLengthLG, getLegendreGaussValues, getLength, waArr_global } from './svgii/geometry_length';
35
- import { deg2rad } from './constants';
35
+ import { deg2rad, dummySVG } from './constants';
36
36
  import { getPathDataLength } from './svgii/pathData_getLength';
37
37
  import { stringifySVG } from './string_helpers';
38
38
  import { presetSettings, settingsDefaults } from './pathSimplify-presets';
@@ -55,21 +55,54 @@ export function svgPathSimplify(input = '', settings = {}) {
55
55
  }
56
56
 
57
57
 
58
- let { getObject = false, removeComments, removeOffCanvas, unGroup, mergePaths, removeElements, removeDimensions, removeIds, removeClassNames, omitNamespace, cleanUpStrokes, addViewBox, addDimensions, removePrologue, removeHidden, removeUnused, cleanupDefs, cleanupClip, cleanupSVGAtts, removeNameSpaced, removeNameSpacedAtts, attributesToGroup, minifyRgbColors, stylesToAttributes, fixHref, legacyHref, allowMeta, allowDataAtts, allowAriaAtts, convertPathLength, removeSVGAttributes, removeElAttributes, shapesToPaths, shapeConvert, convertShapes, simplifyBezier, optimizeOrder, autoClose, removeZeroLength, refineClosing, removeColinear, flatBezierToLinetos, revertToQuadratics, refineExtremes, simplifyCorners, fixDirections, keepExtremes, keepCorners, keepInflections, addExtremes, reversePath, toAbsolute, toRelative, toMixed, toShorthands, toLonghands, quadraticToCubic, arcToCubic, cubicToArc, lineToCubic, decimals, autoAccuracy, minifyD, tolerance, toPolygon, smoothPoly, polyFormat, precisionPoly, simplifyRD, simplifyRDP, harmonizeCpts, removeOrphanSubpaths, simplifyRound, scale, scaleTo, crop, alignToOrigin, convertTransforms, keepSmaller, splitCompound } = settings;
58
+ let { getObject = false, removeComments, removeOffCanvas, unGroup, mergePaths, removeElements, removeDimensions, removeIds, removeClassNames, omitNamespace, cleanUpStrokes, addViewBox, addDimensions, removePrologue, removeHidden, removeUnused, cleanupDefs, cleanupClip, cleanupSVGAtts, removeNameSpaced, removeNameSpacedAtts, attributesToGroup, minifyRgbColors, stylesToAttributes, fixHref, legacyHref, allowMeta, allowDataAtts, allowAriaAtts, removeSVGAttributes, removeElAttributes, shapesToPaths, shapeConvert, convertShapes, simplifyBezier, optimizeOrder, autoClose, removeZeroLength, refineClosing, removeColinear, flatBezierToLinetos, revertToQuadratics, refineExtremes, simplifyCorners, fixDirections, keepExtremes, keepCorners, keepInflections, addExtremes, reversePath, toAbsolute, toRelative, toMixed, toShorthands, toLonghands, quadraticToCubic, arcToCubic, cubicToArc, lineToCubic, decimals, autoAccuracy, minifyD, tolerance, toPolygon, smoothPoly, polyFormat, precisionPoly, simplifyRD, simplifyRDP, harmonizeCpts, removeOrphanSubpaths, simplifyRound, simplifyQuadraticCorners, scale, scaleTo, crop, alignToOrigin, convertTransforms, keepSmaller, splitCompound, convertPathLength, toAbsoluteUnits } = settings;
59
59
 
60
60
  //toAbsolute = !toRelative;
61
61
 
62
62
  // clamp tolerance and scale
63
63
  tolerance = Math.max(0.1, tolerance);
64
64
  scale = Math.max(0.001, scale)
65
- if(fixDirections) keepSmaller = false;
65
+ if (fixDirections) keepSmaller = false;
66
66
  if (scale !== 1 || scaleTo || crop || alignToOrigin) {
67
67
  convertTransforms = true;
68
68
  settings.convertTransforms = true
69
69
  }
70
70
 
71
71
 
72
- let inputType = detectInputType(input);
72
+ /**
73
+ * intercept
74
+ * invalid inputs
75
+ */
76
+
77
+ let inputDetection = detectInputType(input);
78
+ let { inputType, log } = inputDetection
79
+
80
+ // invalid file
81
+ if (inputType === 'invalid' || input === dummySVG) {
82
+ // return dummy SVG to continue processing
83
+ //input = dummySVG;
84
+ //inputType = 'invalid';
85
+
86
+ //console.warn(`Input is not valid!\n ${log}`);
87
+ //console.log(input);
88
+ //return false
89
+
90
+ let report = {
91
+ original: 0,
92
+ new: 0,
93
+ saved: 0,
94
+ svgSize:0,
95
+ svgSizeOpt:0,
96
+ compression:0,
97
+ decimals:0,
98
+ invalid:true
99
+ }
100
+
101
+ return { svg: dummySVG, d: '', polys: [], report, pathDataPlusArr: [], pathDataPlusArr_global: [], inputType: 'invalid', dOriginal: '' };
102
+
103
+ }
104
+
105
+
73
106
  let svg = '';
74
107
  let svgSize = 0;
75
108
  let svgSizeOpt = 0;
@@ -81,12 +114,12 @@ export function svgPathSimplify(input = '', settings = {}) {
81
114
 
82
115
  // pathdata superset array - containing additional data
83
116
  let pathDataPlusArr_global = []
84
- let paths = []
117
+ let paths = [];
85
118
  let isPoly = false;
86
- let polys = []
87
- let poly = []
119
+ let polys = [];
120
+ let poly = [];
88
121
  let dStr = '';
89
- let dOriginal = ''
122
+ let dOriginal = '';
90
123
 
91
124
  /**
92
125
  * normalize input
@@ -204,10 +237,7 @@ export function svgPathSimplify(input = '', settings = {}) {
204
237
  // convert all shapes to paths
205
238
  if (shapesToPaths) {
206
239
  shapeConvert = 'toPaths'
207
- convert_rects = true
208
- convert_ellipses = true
209
- convert_poly = true
210
- convert_lines = true
240
+ convertShapes = ['rect', 'polygon', 'polyline', 'line', 'circle', 'ellipse']
211
241
  }
212
242
 
213
243
  //console.log('shapesToPaths', shapesToPaths, 'shapeConvert', shapeConvert, convert_rects, convert_ellipses, convert_poly);
@@ -255,6 +285,9 @@ export function svgPathSimplify(input = '', settings = {}) {
255
285
  }
256
286
  //console.log('pathOptions', pathOptions);
257
287
 
288
+ let comCount = 0
289
+ let comCountS = 0
290
+
258
291
 
259
292
  for (let i = 0, l = paths.length; l && i < l; i++) {
260
293
 
@@ -306,7 +339,7 @@ export function svgPathSimplify(input = '', settings = {}) {
306
339
  }
307
340
 
308
341
  // count commands for evaluation
309
- let comCount = pathData.length
342
+ comCount += pathData.length
310
343
 
311
344
  if (!isPoly && removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
312
345
 
@@ -463,7 +496,6 @@ export function svgPathSimplify(input = '', settings = {}) {
463
496
 
464
497
 
465
498
 
466
-
467
499
  // simplify beziers
468
500
  let { pathData, bb, dimA } = pathDataPlus;
469
501
 
@@ -498,12 +530,14 @@ export function svgPathSimplify(input = '', settings = {}) {
498
530
  //pathData = removeZeroLengthLinetos(pathData);
499
531
 
500
532
  let threshold = (bb.width + bb.height) * 0.1
501
- pathData = refineRoundedCorners(pathData, { threshold, tolerance })
533
+ pathData = refineRoundedCorners(pathData, { threshold, tolerance, simplifyQuadraticCorners })
502
534
  }
503
535
 
504
536
  // refine round segment sequences
505
- if (simplifyRound) pathData = refineRoundSegments(pathData);
506
-
537
+ if (simplifyRound) {
538
+ pathData = refineRoundSegments(pathData);
539
+ pathData = simplifyAdjacentRound(pathData);
540
+ }
507
541
 
508
542
  // simplify to quadratics
509
543
  if (revertToQuadratics) pathData = pathDataRevertCubicToQuadratic(pathData, tolerance);
@@ -550,7 +584,25 @@ export function svgPathSimplify(input = '', settings = {}) {
550
584
 
551
585
  // prefer top to bottom priority for portrait aspect ratios
552
586
  if (optimizeOrder) {
587
+ /*
553
588
  pathDataPlusArr = isPortrait ? pathDataPlusArr.sort((a, b) => a.bb.y - b.bb.y || a.bb.x - b.bb.x) : pathDataPlusArr.sort((a, b) => a.bb.x - b.bb.x || a.bb.y - b.bb.y)
589
+ */
590
+
591
+ // add missin bbox
592
+ pathDataPlusArr.forEach(p => {
593
+ if (p.bb.x === undefined) {
594
+ p.bb = getPolyBBox(getPathDataVertices(p.pathData))
595
+ }
596
+ })
597
+
598
+ try {
599
+ pathDataPlusArr = pathDataPlusArr.sort((a, b) => +a.bb.x.toFixed(2) - (+b.bb.x.toFixed(2)) || a.bb.y - b.bb.y);
600
+ //console.log(pathDataPlusArr);
601
+
602
+ } catch {
603
+ }
604
+
605
+
554
606
  }
555
607
 
556
608
 
@@ -623,7 +675,7 @@ export function svgPathSimplify(input = '', settings = {}) {
623
675
 
624
676
 
625
677
  // compare command count
626
- let comCountS = pathData.length
678
+ comCountS += pathData.length
627
679
 
628
680
  let dOpt = pathDataToD(pathData, minifyD)
629
681
  //svgSizeOpt = new Blob([dOpt]).size;
@@ -706,6 +758,9 @@ export function svgPathSimplify(input = '', settings = {}) {
706
758
 
707
759
 
708
760
  report = {
761
+ original: comCount,
762
+ new: comCountS,
763
+ saved: comCount - comCountS,
709
764
  svgSize,
710
765
  svgSizeOpt,
711
766
  compression,
@@ -13,10 +13,11 @@ import { pathDataToD } from './svgii/pathData_stringify';
13
13
  import { detectAccuracy } from './svgii/rounding';
14
14
  import { refineAdjacentExtremes } from './svgii/pathData_simplify_refineExtremes';
15
15
  import { refineRoundedCorners } from './svgii/pathData_simplify_refineCorners';
16
- import { refineRoundSegments } from './svgii/pathData_refine_round';
16
+ //import { refineRoundSegments } from './svgii/pathData_refine_round';
17
17
  import { refineClosingCommand } from './svgii/pathData_remove_short';
18
18
  import { pathDataRevertCubicToQuadratic } from './pathData_simplify_revertToquadratics';
19
19
  import { pathDataLineToCubic } from './svgii/pathData_line_to_cubic';
20
+ import { refineRoundSegments } from './svgii/pathData_simplify_refine_round';
20
21
 
21
22
  //import { installDOMPolyfills } from './dom-polyfill';
22
23
 
@@ -97,7 +98,11 @@ export function simplifyPathData(input = '', {
97
98
  let yArr = []
98
99
 
99
100
  // mode:0 – single path
100
- let inputType = detectInputType(input)
101
+ //let inputType = detectInputType(input)
102
+ let inputDetection = detectInputType(input);
103
+ let {inputType, log} = inputDetection
104
+
105
+
101
106
  if (inputType === 'pathDataString') {
102
107
  d = input
103
108
  } else if (inputType === 'polyString') {
@@ -30,6 +30,7 @@ export let settingsDefaults = {
30
30
  allowAriaAtts: true,
31
31
  //pathlength conversion
32
32
  convertPathLength: false,
33
+ toAbsoluteUnits: false,
33
34
 
34
35
  // custom removal
35
36
  removeElements: [],
@@ -61,6 +62,7 @@ export let settingsDefaults = {
61
62
  revertToQuadratics: true,
62
63
  refineExtremes: false,
63
64
  simplifyCorners: false,
65
+ simplifyQuadraticCorners: false,
64
66
  keepExtremes: true,
65
67
  keepCorners: true,
66
68
  keepInflections: false,
@@ -121,7 +123,7 @@ for (let prop in settingsDefaults) {
121
123
  let isArray = Array.isArray(val)
122
124
 
123
125
  if (isBoolean) val = false
124
- else if (!isArray && isNum) val = val===1 ? 1 : (prop==='decimals'? -1 : 0);
126
+ else if (!isArray && isNum) val = val === 1 ? 1 : (prop === 'decimals' ? -1 : 0);
125
127
  else if (isArray) val = []
126
128
  settingsNull[prop] = val;
127
129
  }
@@ -153,10 +155,14 @@ export const presetSettings = {
153
155
  ...settingsDefaults,
154
156
  ...{
155
157
  keepSmaller: false,
158
+ convertPathLength:true,
156
159
  toRelative: true,
157
160
  toMixed: true,
158
161
  toShorthands: true,
159
162
  //fixHref: true,
163
+ allowMeta:true,
164
+ allowDataAtts:true,
165
+ allowAriaAtts:true,
160
166
  legacyHref: true,
161
167
  addViewBox: true,
162
168
  addDimensions: true,
@@ -224,19 +230,24 @@ export const presetSettings = {
224
230
  high: {
225
231
  ...settingsDefaults,
226
232
  ...{
227
- tolerance: 1.2,
233
+ tolerance: 1.1,
228
234
  toMixed: true,
229
235
  refineExtremes: true,
230
236
  simplifyCorners: true,
237
+ simplifyQuadraticCorners: true,
238
+ removeOrphanSubpaths: true,
231
239
  simplifyRound: true,
232
240
  removeClassNames: true,
233
241
  cubicToArc: true,
242
+ minifyD: 0,
234
243
  removeComments: true,
235
244
  removeHidden: true,
236
- removeOffCanvas: true,
237
245
  addViewBox: true,
238
246
  removeDimensions: true,
239
- minifyD: 0
247
+ removeOffCanvas: true,
248
+
249
+ /*
250
+ */
240
251
  }
241
252
  }
242
253
 
@@ -1,11 +1,13 @@
1
1
  import { normalizeUnits } from "./svgii/convert_units";
2
2
 
3
3
  export function getElementAtts(el, {x=0, y=0, width=0, height=0}={}){
4
- let attributes = [...el.attributes];
4
+ //let attributes = [...el.attributes];
5
+ let attributes = [...el.attributes].map(att=>att.name);
5
6
 
6
7
  let atts={};
7
8
  attributes.forEach(att=>{
8
- let value = normalizeUnits(att.nodeValue, {x, y, width, height});
9
+ //let value = normalizeUnits(att.nodeValue, {x, y, width, height});
10
+ let value = normalizeUnits(el.getAttribute(att), {x, y, width, height});
9
11
  atts[att.name] = value
10
12
  })
11
13
 
@@ -94,7 +94,7 @@ export function normalizeUnits(value = null, {
94
94
  scale = height / 100;
95
95
  }
96
96
  else {
97
- scale = normalizedDiagonal ? scaleRoot / 100 : 1;
97
+ scale = normalizedDiagonal ? scaleRoot / 100 : width / 100;
98
98
  }
99
99
  break;
100
100
 
@@ -321,6 +321,7 @@ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, retu
321
321
  let t1 = 1 - t;
322
322
 
323
323
  // cubic beziers
324
+ /*
324
325
  if (isCubic) {
325
326
  pt = {
326
327
  x:
@@ -336,11 +337,30 @@ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, retu
336
337
  };
337
338
 
338
339
  }
340
+ */
341
+
342
+ if (isCubic) {
343
+ pt = {
344
+ x:
345
+ t1 * t1 * t1 * p0.x +
346
+ 3 * t1 * t1 * t * cp1.x +
347
+ 3 * t1 * t * t * cp2.x +
348
+ t * t * t * p.x,
349
+ y:
350
+ t1 * t1 * t1 * p0.y +
351
+ 3 * t1 * t1 * t * cp1.y +
352
+ 3 * t1 * t * t * cp2.y +
353
+ t * t * t * p.y,
354
+ };
355
+
356
+ }
357
+
358
+
339
359
  // quadratic beziers
340
360
  else {
341
361
  pt = {
342
- x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t ** 2 * p.x,
343
- y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t ** 2 * p.y,
362
+ x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t * t * p.x,
363
+ y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t * t * p.y,
344
364
  };
345
365
  }
346
366
 
@@ -963,6 +983,124 @@ export function getBezierExtremeT(pts, { addExtremes = true, addSemiExtremes = f
963
983
  * https://stackoverflow.com/questions/87734/#75031511
964
984
  * See also: https://github.com/foo123/Geometrize
965
985
  */
986
+
987
+ export function getArcExtemesParam({
988
+ cx=0, cy=0, rx=0, ry=0,
989
+ p=null,
990
+ p0=null,
991
+ endAngle=0,
992
+ deltaAngle=0,
993
+
994
+
995
+ }={}) {
996
+ // compute point on ellipse from angle around ellipse (theta)
997
+ const arc = (theta, cx, cy, rx, ry, alpha) => {
998
+ // theta is angle in radians around arc
999
+ // alpha is angle of rotation of ellipse in radians
1000
+ var cos = Math.cos(alpha),
1001
+ sin = Math.sin(alpha),
1002
+ x = rx * Math.cos(theta),
1003
+ y = ry * Math.sin(theta);
1004
+
1005
+ return {
1006
+ x: cx + cos * x - sin * y,
1007
+ y: cy + sin * x + cos * y
1008
+ };
1009
+ }
1010
+
1011
+ //parametrize arcto data
1012
+ //let arcData = svgArcToCenterParam(p0.x, p0.y, values[0], values[1], values[2], values[3], values[4], values[5], values[6]);
1013
+ //let { rx, ry, cx, cy, endAngle, deltaAngle } = arcData;
1014
+
1015
+ // arc rotation
1016
+ let deg = values[2];
1017
+
1018
+ // final on path point
1019
+ //let p = { x: values[5], y: values[6] }
1020
+
1021
+ // collect extreme points – add end point
1022
+ let extremes = [p]
1023
+
1024
+ // rotation to radians
1025
+ let alpha = deg * Math.PI / 180;
1026
+ let tan = Math.tan(alpha),
1027
+ p1, p2, p3, p4, theta;
1028
+
1029
+ /**
1030
+ * find min/max from zeroes of directional derivative along x and y
1031
+ * along x axis
1032
+ */
1033
+ theta = Math.atan2(-ry * tan, rx);
1034
+
1035
+ let angle1 = theta;
1036
+ let angle2 = theta + Math.PI;
1037
+ let angle3 = Math.atan2(ry, rx * tan);
1038
+ let angle4 = angle3 + Math.PI;
1039
+
1040
+
1041
+ // inner bounding box
1042
+ let xArr = [p0.x, p.x]
1043
+ let yArr = [p0.y, p.y]
1044
+ let xMin = Math.min(...xArr)
1045
+ let xMax = Math.max(...xArr)
1046
+ let yMin = Math.min(...yArr)
1047
+ let yMax = Math.max(...yArr)
1048
+
1049
+
1050
+ // on path point close after start
1051
+ let angleAfterStart = endAngle - deltaAngle * 0.001
1052
+ let pP2 = arc(angleAfterStart, cx, cy, rx, ry, alpha);
1053
+
1054
+ // on path point close before end
1055
+ let angleBeforeEnd = endAngle - deltaAngle * 0.999
1056
+ let pP3 = arc(angleBeforeEnd, cx, cy, rx, ry, alpha);
1057
+
1058
+
1059
+ /**
1060
+ * expected extremes
1061
+ * if leaving inner bounding box
1062
+ * (between segment start and end point)
1063
+ * otherwise exclude elliptic extreme points
1064
+ */
1065
+
1066
+ // right
1067
+ if (pP2.x > xMax || pP3.x > xMax) {
1068
+ // get point for this theta
1069
+ p1 = arc(angle1, cx, cy, rx, ry, alpha);
1070
+ extremes.push(p1)
1071
+ }
1072
+
1073
+ // left
1074
+ if (pP2.x < xMin || pP3.x < xMin) {
1075
+ // get anti-symmetric point
1076
+ p2 = arc(angle2, cx, cy, rx, ry, alpha);
1077
+ extremes.push(p2)
1078
+ }
1079
+
1080
+ // top
1081
+ if (pP2.y < yMin || pP3.y < yMin) {
1082
+ // get anti-symmetric point
1083
+ p4 = arc(angle4, cx, cy, rx, ry, alpha);
1084
+ extremes.push(p4)
1085
+ }
1086
+
1087
+ // bottom
1088
+ if (pP2.y > yMax || pP3.y > yMax) {
1089
+ // get point for this theta
1090
+ p3 = arc(angle3, cx, cy, rx, ry, alpha);
1091
+ extremes.push(p3)
1092
+ }
1093
+
1094
+ return extremes;
1095
+ }
1096
+
1097
+
1098
+
1099
+
1100
+
1101
+
1102
+
1103
+
966
1104
  export function getArcExtemes(p0, values) {
967
1105
  // compute point on ellipse from angle around ellipse (theta)
968
1106
  const arc = (theta, cx, cy, rx, ry, alpha) => {
@@ -12,7 +12,7 @@ export function getElBBox(el){
12
12
 
13
13
  switch(type){
14
14
  case 'path':
15
- let pathData = parsePathDataNormalized(atts.d)
15
+ let pathData = parsePathDataNormalized(el.getAttribute('d'))
16
16
  bb=getPolyBBox(getPathDataPoly(pathData))
17
17
 
18
18
  break;