@jscad/svg-deserializer 2.2.4 → 2.4.2

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 CHANGED
@@ -3,6 +3,49 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.4.2](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/svg-deserializer@2.4.1...@jscad/svg-deserializer@2.4.2) (2021-10-17)
7
+
8
+ **Note:** Version bump only for package @jscad/svg-deserializer
9
+
10
+
11
+
12
+
13
+
14
+ ## [2.4.1](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/svg-deserializer@2.4.0...@jscad/svg-deserializer@2.4.1) (2021-10-04)
15
+
16
+ **Note:** Version bump only for package @jscad/svg-deserializer
17
+
18
+
19
+
20
+
21
+
22
+ # [2.4.0](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/svg-deserializer@2.3.0...@jscad/svg-deserializer@2.4.0) (2021-09-27)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **modeling:** corrected concat to ignore duplicate points ([#913](https://github.com/jscad/OpenJSCAD.org/issues/913))corrected appendArc to maintain last point ([3eea3ef](https://github.com/jscad/OpenJSCAD.org/commit/3eea3efed3b3d4bacb49c1ee4691bfc159b08261))
28
+
29
+
30
+ ### Features
31
+
32
+ * **svg-deserializer:** reworked to use saxes library, and standard module layout ([56f5725](https://github.com/jscad/OpenJSCAD.org/commit/56f572518c85d767ac680aa2da1af46c4847e63c))
33
+
34
+
35
+
36
+
37
+
38
+ # [2.3.0](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/svg-deserializer@2.2.4...@jscad/svg-deserializer@2.3.0) (2021-09-09)
39
+
40
+
41
+ ### Features
42
+
43
+ * **svg-deserializer:** added pathSelfClosed option to control construction of overlapping paths ([043f59d](https://github.com/jscad/OpenJSCAD.org/commit/043f59d928fcf397b0bb23e744e35d7eda5be626))
44
+
45
+
46
+
47
+
48
+
6
49
  ## [2.2.4](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/svg-deserializer@2.2.3...@jscad/svg-deserializer@2.2.4) (2021-06-20)
7
50
 
8
51
  **Note:** Version bump only for package @jscad/svg-deserializer
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@jscad/svg-deserializer",
3
- "version": "2.2.4",
3
+ "version": "2.4.2",
4
4
  "description": "SVG Deserializer for JSCAD",
5
5
  "repository": "https://github.com/jscad/OpenJSCAD.org",
6
- "main": "index.js",
6
+ "main": "src/index.js",
7
7
  "scripts": {
8
8
  "coverage": "nyc --all --reporter=html --reporter=text npm test",
9
9
  "test": "ava --verbose --timeout 2m './tests/*.test.js'"
@@ -32,12 +32,12 @@
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
34
  "@jscad/array-utils": "2.1.0",
35
- "@jscad/modeling": "2.5.0",
36
- "sax": "1.2.4"
35
+ "@jscad/modeling": "2.6.0",
36
+ "saxes": "5.0.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "ava": "3.15.0",
40
40
  "nyc": "15.1.0"
41
41
  },
42
- "gitHead": "18d1d0b53cf676c6349fa24be2253fd7c21e676b"
42
+ "gitHead": "3d1d02f2863c65fd95406c7098c09297b3be1c10"
43
43
  }
File without changes
@@ -124,8 +124,8 @@ const cagColor = (value) => {
124
124
  }
125
125
 
126
126
  const cssStyle = (element, name) => {
127
- if ('STYLE' in element) {
128
- const list = element.STYLE + ';'
127
+ if ('style' in element) {
128
+ const list = element.style + ';'
129
129
  const pat = name + '\\s*:\\s*(\\S+);'
130
130
  const exp = new RegExp(pat, 'i')
131
131
  let v = exp.exec(list)
@@ -10,12 +10,12 @@ have been very kindly sponsored by [Copenhagen Fabrication / Stykka](https://www
10
10
  All code released under MIT license
11
11
  */
12
12
 
13
- const sax = require('sax')
13
+ const saxes = require('saxes')
14
14
 
15
15
  const { colors, transforms } = require('@jscad/modeling')
16
16
  const { toArray } = require('@jscad/array-utils')
17
17
 
18
- const version = require('./package.json').version
18
+ const version = require('../package.json').version
19
19
 
20
20
  const { cagLengthX, cagLengthY, svgColorForTarget } = require('./helpers')
21
21
  const { svgSvg, svgRect, svgCircle, svgGroup, svgLine, svgPath, svgEllipse, svgPolygon, svgPolyline, svgUse } = require('./svgElementHelpers')
@@ -23,24 +23,26 @@ const shapesMapGeometry = require('./shapesMapGeometry')
23
23
  const shapesMapJscad = require('./shapesMapJscad')
24
24
 
25
25
  /**
26
- * Deserializer of STL data to JSCAD geometries.
27
- * @module io/stl-deserializer
26
+ * Deserializer of SVG source data to JSCAD geometries.
27
+ * @module io/svg-deserializer
28
28
  * @example
29
- * const { deserializer, extension } = require('@jscad/stl-deserializer')
29
+ * const { deserializer, extension } = require('@jscad/svg-deserializer')
30
30
  */
31
31
 
32
32
  /**
33
- * Parse the given SVG data and return either a JSCAD script or a set of geometries
33
+ * Deserialize the given SVG source into either a script or an array of geometries
34
+ * @see {@link https://www.w3.org/TR/SVG/intro.html|SVG Specification}
34
35
  * @param {Object} options - options used during deserializing, REQUIRED
35
36
  * @param {boolean} [options.addMetadata=true] - toggle injection of metadata at the start of the script
36
37
  * @param {string} [options.filename='svg'] - filename of original SVG source
37
38
  * @param {string} [options.output='script'] - either 'script' or 'geometry' to set desired output
38
39
  * @param {float} [options.pxPmm] - custom pixels per mm unit
39
40
  * @param {integer} [options.segments] - number of segments for rounded shapes
40
- * @param {string} [options.target] - target 2D geometry; geom2 or path2
41
+ * @param {string} [options.target] - target 2D geometry; 'geom2' or 'path2'
41
42
  * @param {string} [options.version='0.0.0'] - version number to add to the metadata
42
- * @param {string} input - SVG data
43
- * @return {string|[object]} either a string (script) or a set of objects (geometry)
43
+ * @param {string} [options.pathSelfClosed='error'] - [error||trim||split] if path self-closes with one of commands without stop command right after
44
+ * @param {string} input - SVG source data
45
+ * @returns {(Array|String)} either an array of objects (geometry) or a string (script)
44
46
  * @alias module:io/svg-deserializer.deserialize
45
47
  */
46
48
  const deserialize = (options, input) => {
@@ -51,16 +53,17 @@ const deserialize = (options, input) => {
51
53
  pxPmm: require('./constants').pxPmm,
52
54
  segments: 32,
53
55
  target: 'path', // target - 'geom2' or 'path'
56
+ pathSelfClosed: 'error',
54
57
  version
55
58
  }
56
59
  options = Object.assign({}, defaults, options)
57
60
  return options.output === 'script' ? translate(input, options) : instantiate(input, options)
58
61
  }
59
62
 
60
- /**
63
+ /*
61
64
  * Parse the given SVG source and return a set of geometries.
62
- * @param {string} src svg data as text
63
- * @param {object} options options (optional) anonymous object with:
65
+ * @param {string} src - svg data as text
66
+ * @param {object} options - options (optional) anonymous object with:
64
67
  * pxPmm {number} pixels per milimeter for calcuations
65
68
  * version: {string} version number to add to the metadata
66
69
  * addMetadata: {boolean} flag to enable/disable injection of metadata (producer, date, source)
@@ -75,7 +78,7 @@ const instantiate = (src, options) => {
75
78
  // parse the SVG source
76
79
  createSvgParser(src, pxPmm)
77
80
  if (!svgObj) {
78
- throw new Error('SVG parsing failed, no valid svg data retrieved')
81
+ throw new Error('SVG parsing failed, no valid SVG data retrieved')
79
82
  }
80
83
 
81
84
  options && options.statusCallback && options.statusCallback({ progress: 50 })
@@ -86,7 +89,7 @@ const instantiate = (src, options) => {
86
89
  return result
87
90
  }
88
91
 
89
- /**
92
+ /*
90
93
  * Parse the given SVG source and return a JSCAD script
91
94
  * @param {string} src svg data as text
92
95
  * @param {object} options options (optional) anonymous object with:
@@ -104,7 +107,7 @@ const translate = (src, options) => {
104
107
  // parse the SVG source
105
108
  createSvgParser(src, pxPmm)
106
109
  if (!svgObj) {
107
- throw new Error('SVG parsing failed, no valid svg data retrieved')
110
+ throw new Error('SVG parsing failed, no valid SVG data retrieved')
108
111
  }
109
112
 
110
113
  // convert the internal objects to JSCAD code
@@ -144,7 +147,7 @@ let svgUnitsPmm = [1, 1]
144
147
  * Convert the given group (of objects) into geometries
145
148
  */
146
149
  const objectify = (options, group) => {
147
- const { target, segments } = options
150
+ const { target, segments, pathSelfClosed } = options
148
151
  const level = svgGroups.length
149
152
  // add this group to the heiarchy
150
153
  svgGroups.push(group)
@@ -164,7 +167,8 @@ const objectify = (options, group) => {
164
167
  level,
165
168
  target,
166
169
  svgGroups,
167
- segments
170
+ segments,
171
+ pathSelfClosed
168
172
  }
169
173
  // apply base level attributes to all shapes
170
174
  for (i = 0; i < group.objects.length; i++) {
@@ -322,14 +326,17 @@ const codify = (options, group) => {
322
326
 
323
327
  const createSvgParser = (src, pxPmm) => {
324
328
  // create a parser for the XML
325
- const parser = sax.parser(false, { trim: true, lowercase: false, position: true })
329
+ const parser = new saxes.SaxesParser()
326
330
  if (pxPmm !== undefined && pxPmm > parser.pxPmm) {
327
331
  parser.pxPmm = pxPmm
328
332
  }
329
333
  // extend the parser with functions
330
- parser.onerror = (e) => console.log('error: line ' + e.line + ', column ' + e.column + ', bad character [' + e.c + ']')
334
+ parser.on('error', (e) => {
335
+ console.log(`ERROR: SVG file, line ${parser.line}, column ${parser.column}`)
336
+ console.log(e)
337
+ })
331
338
 
332
- parser.onopentag = function (node) {
339
+ parser.on('opentag', (node) => {
333
340
  const objMap = {
334
341
  SVG: svgSvg,
335
342
  G: svgGroup,
@@ -345,10 +352,12 @@ const createSvgParser = (src, pxPmm) => {
345
352
  DESC: () => undefined, // ignored by design
346
353
  TITLE: () => undefined, // ignored by design
347
354
  STYLE: () => undefined, // ignored by design
348
- undefined: () => console.log('Warning: Unsupported SVG element: ' + node.name)
355
+ undefined: () => console.log('WARNING: unsupported SVG element: ' + node.name)
349
356
  }
350
357
  node.attributes.position = [parser.line + 1, parser.column + 1]
351
- const obj = objMap[node.name] ? objMap[node.name](node.attributes, { svgObjects, customPxPmm: pxPmm }) : undefined
358
+
359
+ const elementName = node.name.toUpperCase()
360
+ const obj = objMap[elementName] ? objMap[elementName](node.attributes, { svgObjects, customPxPmm: pxPmm }) : undefined
352
361
 
353
362
  // case 'SYMBOL':
354
363
  // this is just like an embedded SVG but does NOT render directly, only named
@@ -396,9 +405,9 @@ const createSvgParser = (src, pxPmm) => {
396
405
  }
397
406
  }
398
407
  }
399
- }
408
+ })
400
409
 
401
- parser.onclosetag = function (node) {
410
+ parser.on('closetag', (node) => {
402
411
  const popGroup = () => {
403
412
  if (svgInDefs === true) {
404
413
  return svgDefs.pop()
@@ -414,19 +423,19 @@ const createSvgParser = (src, pxPmm) => {
414
423
  G: popGroup,
415
424
  undefined: () => {}
416
425
  }
417
- const obj = objMap[node] ? objMap[node]() : undefined
426
+ const elementName = node.name.toUpperCase()
427
+ const obj = objMap[elementName] ? objMap[elementName]() : undefined
418
428
 
419
429
  // check for completeness
420
430
  if (svgGroups.length === 0) {
421
431
  svgObj = obj
422
432
  }
423
- }
433
+ })
424
434
 
425
- // parser.onattribute = function (attr) {};
426
- // parser.ontext = function (t) {};
435
+ parser.on('end', () => {
436
+ // console.log('SVG parsing completed')
437
+ })
427
438
 
428
- parser.onend = function () {
429
- }
430
439
  // start the parser
431
440
  parser.write(src).close()
432
441
  return parser
@@ -4,7 +4,7 @@ const { svg2cagX, svg2cagY, cagLengthX, cagLengthY, cagLengthP, reflect } = requ
4
4
  // const { cssPxUnit } = require('./constants')
5
5
 
6
6
  const shapesMapGeometry = (obj, objectify, params) => {
7
- const { svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, target, segments } = params
7
+ const { svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, target, segments, pathSelfClosed } = params
8
8
 
9
9
  const types = {
10
10
  group: (obj) => objectify({ target, segments }, obj),
@@ -131,7 +131,7 @@ const shapesMapGeometry = (obj, objectify, params) => {
131
131
  },
132
132
 
133
133
  path: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments) => {
134
- const listofpaths = expandPath(obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments)
134
+ const listofpaths = expandPath(obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments, pathSelfClosed)
135
135
  // order is important
136
136
  const listofentries = Object.entries(listofpaths).sort((a, b) => a[0].localeCompare(b[0]))
137
137
  const shapes = listofentries.map((entry) => {
@@ -152,7 +152,12 @@ const shapesMapGeometry = (obj, objectify, params) => {
152
152
 
153
153
  module.exports = shapesMapGeometry
154
154
 
155
- const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments) => {
155
+ const appendPoints = (points, geometry) => {
156
+ if (geometry) return geometries.path2.appendPoints(points, geometry)
157
+ return geometries.path2.fromPoints({ }, points)
158
+ }
159
+
160
+ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments, pathSelfClosed) => {
156
161
  const paths = {}
157
162
  const on = 'path'
158
163
 
@@ -178,10 +183,18 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
178
183
  let qx = 0 // 2nd control point from previous Q command
179
184
  let qy = 0 // 2nd control point from previous Q command
180
185
 
186
+ const newPath = () => {
187
+ pi++
188
+ pathName = on + pi
189
+ pc = false
190
+ }
191
+ const ensurePath = () => {
192
+ if (!paths[pathName]) paths[pathName] = geometries.path2.fromPoints({}, [])
193
+ }
181
194
  for (let j = 0; j < obj.commands.length; j++) {
182
195
  const co = obj.commands[j]
183
196
  const pts = co.p
184
- // console.log('postion: ['+cx+','+cy+'] before '+co.c);
197
+ let i = 0
185
198
  switch (co.c) {
186
199
  case 'm': // relative move to X,Y
187
200
  // special case, if at beginning of path then treat like absolute M
@@ -193,20 +206,18 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
193
206
  // FIXME paths[pathName] = paths[pathName]
194
207
  }
195
208
  // open a new path
196
- if (pts.length >= 2) {
197
- cx = cx + parseFloat(pts.shift())
198
- cy = cy + parseFloat(pts.shift())
199
- pi++
200
- pathName = on + pi
201
- pc = false
202
- paths[pathName] = geometries.path2.fromPoints({ }, [[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]])
209
+ if (pts.length >= i + 2) {
210
+ cx = cx + parseFloat(pts[i++])
211
+ cy = cy + parseFloat(pts[i++])
212
+ newPath()
213
+ paths[pathName] = appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]])
203
214
  sx = cx; sy = cy
204
215
  }
205
216
  // optional implicit relative lineTo (cf SVG spec 8.3.2)
206
- while (pts.length >= 2) {
207
- cx = cx + parseFloat(pts.shift())
208
- cy = cy + parseFloat(pts.shift())
209
- paths[pathName] = geometries.path2.appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
217
+ while (pts.length >= i + 2) {
218
+ cx = cx + parseFloat(pts[i++])
219
+ cy = cy + parseFloat(pts[i++])
220
+ paths[pathName] = appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
210
221
  }
211
222
  break
212
223
  case 'M': // absolute move to X,Y
@@ -215,54 +226,55 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
215
226
  // FIXME paths[pathName] = paths[pathName]
216
227
  }
217
228
  // open a new path
218
- if (pts.length >= 2) {
219
- cx = parseFloat(pts.shift())
220
- cy = parseFloat(pts.shift())
221
- pi++
222
- pathName = on + pi
223
- pc = false
224
- paths[pathName] = geometries.path2.fromPoints({ }, [[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]])
229
+ if (pts.length >= i + 2) {
230
+ cx = parseFloat(pts[i++])
231
+ cy = parseFloat(pts[i++])
232
+ newPath()
233
+ paths[pathName] = appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]])
225
234
  sx = cx; sy = cy
226
235
  }
227
236
  // optional implicit absolute lineTo (cf SVG spec 8.3.2)
228
- while (pts.length >= 2) {
229
- cx = parseFloat(pts.shift())
230
- cy = parseFloat(pts.shift())
231
- paths[pathName] = geometries.path2.appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
237
+ while (pts.length >= i + 2) {
238
+ cx = parseFloat(pts[i++])
239
+ cy = parseFloat(pts[i++])
240
+ paths[pathName] = appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
232
241
  }
233
242
  break
234
243
  case 'a': // relative elliptical arc
235
- while (pts.length >= 7) {
236
- const rx = parseFloat(pts.shift())
237
- const ry = parseFloat(pts.shift())
238
- const ro = 0 - parseFloat(pts.shift()) * 0.017453292519943295 // radians
239
- const lf = (pts.shift() === '1')
240
- const sf = (pts.shift() === '1')
241
- cx = cx + parseFloat(pts.shift())
242
- cy = cy + parseFloat(pts.shift())
244
+ while (pts.length >= i + 7) {
245
+ const rx = parseFloat(pts[i++])
246
+ const ry = parseFloat(pts[i++])
247
+ const ro = 0 - parseFloat(pts[i++]) * 0.017453292519943295 // radians
248
+ const lf = (pts[i++] === '1')
249
+ const sf = (pts[i++] === '1')
250
+ cx = cx + parseFloat(pts[i++])
251
+ cy = cy + parseFloat(pts[i++])
252
+ ensurePath()
243
253
  paths[pathName] = geometries.path2.appendArc({ segments, endpoint: [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)], radius: [svg2cagX(rx, svgUnitsPmm), svg2cagY(ry, svgUnitsPmm)], xaxisrotation: ro, clockwise: sf, large: lf }, paths[pathName])
244
254
  }
245
255
  break
246
256
  case 'A': // absolute elliptical arc
247
- while (pts.length >= 7) {
248
- const rx = parseFloat(pts.shift())
249
- const ry = parseFloat(pts.shift())
250
- const ro = 0 - parseFloat(pts.shift()) * 0.017453292519943295 // radians
251
- const lf = (pts.shift() === '1')
252
- const sf = (pts.shift() === '1')
253
- cx = parseFloat(pts.shift())
254
- cy = parseFloat(pts.shift())
257
+ while (pts.length >= i + 7) {
258
+ const rx = parseFloat(pts[i++])
259
+ const ry = parseFloat(pts[i++])
260
+ const ro = 0 - parseFloat(pts[i++]) * 0.017453292519943295 // radians
261
+ const lf = (pts[i++] === '1')
262
+ const sf = (pts[i++] === '1')
263
+ cx = parseFloat(pts[i++])
264
+ cy = parseFloat(pts[i++])
265
+ ensurePath()
255
266
  paths[pathName] = geometries.path2.appendArc({ segments, endpoint: [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)], radius: [svg2cagX(rx, svgUnitsPmm), svg2cagY(ry, svgUnitsPmm)], xaxisrotation: ro, clockwise: sf, large: lf }, paths[pathName])
256
267
  }
257
268
  break
258
269
  case 'c': // relative cubic Bézier
259
- while (pts.length >= 6) {
260
- const x1 = cx + parseFloat(pts.shift())
261
- const y1 = cy + parseFloat(pts.shift())
262
- bx = cx + parseFloat(pts.shift())
263
- by = cy + parseFloat(pts.shift())
264
- cx = cx + parseFloat(pts.shift())
265
- cy = cy + parseFloat(pts.shift())
270
+ while (pts.length >= i + 6) {
271
+ const x1 = cx + parseFloat(pts[i++])
272
+ const y1 = cy + parseFloat(pts[i++])
273
+ bx = cx + parseFloat(pts[i++])
274
+ by = cy + parseFloat(pts[i++])
275
+ cx = cx + parseFloat(pts[i++])
276
+ cy = cy + parseFloat(pts[i++])
277
+ ensurePath()
266
278
  paths[pathName] = geometries.path2.appendBezier({ segments, controlPoints: [[svg2cagX(x1, svgUnitsPmm), svg2cagY(y1, svgUnitsPmm)], [svg2cagX(bx, svgUnitsPmm), svg2cagY(by, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]] }, paths[pathName])
267
279
  const rf = reflect(bx, by, cx, cy)
268
280
  bx = rf[0]
@@ -270,13 +282,14 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
270
282
  }
271
283
  break
272
284
  case 'C': // absolute cubic Bézier
273
- while (pts.length >= 6) {
274
- const x1 = parseFloat(pts.shift())
275
- const y1 = parseFloat(pts.shift())
276
- bx = parseFloat(pts.shift())
277
- by = parseFloat(pts.shift())
278
- cx = parseFloat(pts.shift())
279
- cy = parseFloat(pts.shift())
285
+ while (pts.length >= i + 6) {
286
+ const x1 = parseFloat(pts[i++])
287
+ const y1 = parseFloat(pts[i++])
288
+ bx = parseFloat(pts[i++])
289
+ by = parseFloat(pts[i++])
290
+ cx = parseFloat(pts[i++])
291
+ cy = parseFloat(pts[i++])
292
+ ensurePath()
280
293
  paths[pathName] = geometries.path2.appendBezier({ segments, controlPoints: [[svg2cagX(x1, svgUnitsPmm), svg2cagY(y1, svgUnitsPmm)], [svg2cagX(bx, svgUnitsPmm), svg2cagY(by, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]] }, paths[pathName])
281
294
  const rf = reflect(bx, by, cx, cy)
282
295
  bx = rf[0]
@@ -284,11 +297,12 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
284
297
  }
285
298
  break
286
299
  case 'q': // relative quadratic Bézier
287
- while (pts.length >= 4) {
288
- qx = cx + parseFloat(pts.shift())
289
- qy = cy + parseFloat(pts.shift())
290
- cx = cx + parseFloat(pts.shift())
291
- cy = cy + parseFloat(pts.shift())
300
+ while (pts.length >= i + 4) {
301
+ qx = cx + parseFloat(pts[i++])
302
+ qy = cy + parseFloat(pts[i++])
303
+ cx = cx + parseFloat(pts[i++])
304
+ cy = cy + parseFloat(pts[i++])
305
+ ensurePath()
292
306
  paths[pathName] = geometries.path2.appendBezier({ segments, controlPoints: [[svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]] }, paths[pathName])
293
307
  const rf = reflect(qx, qy, cx, cy)
294
308
  qx = rf[0]
@@ -296,11 +310,12 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
296
310
  }
297
311
  break
298
312
  case 'Q': // absolute quadratic Bézier
299
- while (pts.length >= 4) {
300
- qx = parseFloat(pts.shift())
301
- qy = parseFloat(pts.shift())
302
- cx = parseFloat(pts.shift())
303
- cy = parseFloat(pts.shift())
313
+ while (pts.length >= i + 4) {
314
+ qx = parseFloat(pts[i++])
315
+ qy = parseFloat(pts[i++])
316
+ cx = parseFloat(pts[i++])
317
+ cy = parseFloat(pts[i++])
318
+ ensurePath()
304
319
  paths[pathName] = geometries.path2.appendBezier({ segments, controlPoints: [[svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]] }, paths[pathName])
305
320
  const rf = reflect(qx, qy, cx, cy)
306
321
  qx = rf[0]
@@ -308,9 +323,10 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
308
323
  }
309
324
  break
310
325
  case 't': // relative quadratic Bézier shorthand
311
- while (pts.length >= 2) {
312
- cx = cx + parseFloat(pts.shift())
313
- cy = cy + parseFloat(pts.shift())
326
+ while (pts.length >= i + 2) {
327
+ cx = cx + parseFloat(pts[i++])
328
+ cy = cy + parseFloat(pts[i++])
329
+ ensurePath()
314
330
  paths[pathName] = geometries.path2.appendBezier({ segments, controlPoints: [[svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [cx, cy]] }, paths[pathName])
315
331
  const rf = reflect(qx, qy, cx, cy)
316
332
  qx = rf[0]
@@ -318,9 +334,10 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
318
334
  }
319
335
  break
320
336
  case 'T': // absolute quadratic Bézier shorthand
321
- while (pts.length >= 2) {
322
- cx = parseFloat(pts.shift())
323
- cy = parseFloat(pts.shift())
337
+ while (pts.length >= i + 2) {
338
+ cx = parseFloat(pts[i++])
339
+ cy = parseFloat(pts[i++])
340
+ ensurePath()
324
341
  paths[pathName] = geometries.path2.appendBezier({ segments, controlPoints: [[svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]] }, paths[pathName])
325
342
  const rf = reflect(qx, qy, cx, cy)
326
343
  qx = rf[0]
@@ -328,13 +345,14 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
328
345
  }
329
346
  break
330
347
  case 's': // relative cubic Bézier shorthand
331
- while (pts.length >= 4) {
348
+ while (pts.length >= i + 4) {
332
349
  const x1 = bx // reflection of 2nd control point from previous C
333
350
  const y1 = by // reflection of 2nd control point from previous C
334
- bx = cx + parseFloat(pts.shift())
335
- by = cy + parseFloat(pts.shift())
336
- cx = cx + parseFloat(pts.shift())
337
- cy = cy + parseFloat(pts.shift())
351
+ bx = cx + parseFloat(pts[i++])
352
+ by = cy + parseFloat(pts[i++])
353
+ cx = cx + parseFloat(pts[i++])
354
+ cy = cy + parseFloat(pts[i++])
355
+ ensurePath()
338
356
  paths[pathName] = geometries.path2.appendBezier({ segments, controlPoints: [[svg2cagX(x1, svgUnitsPmm), svg2cagY(y1, svgUnitsPmm)], [svg2cagX(bx, svgUnitsPmm), svg2cagY(by, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]] }, paths[pathName])
339
357
  const rf = reflect(bx, by, cx, cy)
340
358
  bx = rf[0]
@@ -342,13 +360,14 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
342
360
  }
343
361
  break
344
362
  case 'S': // absolute cubic Bézier shorthand
345
- while (pts.length >= 4) {
363
+ while (pts.length >= i + 4) {
346
364
  const x1 = bx // reflection of 2nd control point from previous C
347
365
  const y1 = by // reflection of 2nd control point from previous C
348
- bx = parseFloat(pts.shift())
349
- by = parseFloat(pts.shift())
350
- cx = parseFloat(pts.shift())
351
- cy = parseFloat(pts.shift())
366
+ bx = parseFloat(pts[i++])
367
+ by = parseFloat(pts[i++])
368
+ cx = parseFloat(pts[i++])
369
+ cy = parseFloat(pts[i++])
370
+ ensurePath()
352
371
  paths[pathName] = geometries.path2.appendBezier({ segments, controlPoints: [[svg2cagX(x1, svgUnitsPmm), svg2cagY(y1, svgUnitsPmm)], [svg2cagX(bx, svgUnitsPmm), svg2cagY(by, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]] }, paths[pathName])
353
372
  const rf = reflect(bx, by, cx, cy)
354
373
  bx = rf[0]
@@ -356,41 +375,41 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
356
375
  }
357
376
  break
358
377
  case 'h': // relative Horzontal line to
359
- while (pts.length >= 1) {
360
- cx = cx + parseFloat(pts.shift())
361
- paths[pathName] = geometries.path2.appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
378
+ while (pts.length >= i + 1) {
379
+ cx = cx + parseFloat(pts[i++])
380
+ paths[pathName] = appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
362
381
  }
363
382
  break
364
383
  case 'H': // absolute Horzontal line to
365
- while (pts.length >= 1) {
366
- cx = parseFloat(pts.shift())
367
- paths[pathName] = geometries.path2.appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
384
+ while (pts.length >= i + 1) {
385
+ cx = parseFloat(pts[i++])
386
+ paths[pathName] = appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
368
387
  }
369
388
  break
370
389
  case 'l': // relative line to
371
- while (pts.length >= 2) {
372
- cx = cx + parseFloat(pts.shift())
373
- cy = cy + parseFloat(pts.shift())
374
- paths[pathName] = geometries.path2.appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
390
+ while (pts.length >= i + 2) {
391
+ cx = cx + parseFloat(pts[i++])
392
+ cy = cy + parseFloat(pts[i++])
393
+ paths[pathName] = appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
375
394
  }
376
395
  break
377
396
  case 'L': // absolute line to
378
- while (pts.length >= 2) {
379
- cx = parseFloat(pts.shift())
380
- cy = parseFloat(pts.shift())
381
- paths[pathName] = geometries.path2.appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
397
+ while (pts.length >= i + 2) {
398
+ cx = parseFloat(pts[i++])
399
+ cy = parseFloat(pts[i++])
400
+ paths[pathName] = appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
382
401
  }
383
402
  break
384
403
  case 'v': // relative Vertical line to
385
- while (pts.length >= 1) {
386
- cy = cy + parseFloat(pts.shift())
387
- paths[pathName] = geometries.path2.appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
404
+ while (pts.length >= i + 1) {
405
+ cy = cy + parseFloat(pts[i++])
406
+ paths[pathName] = appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
388
407
  }
389
408
  break
390
409
  case 'V': // absolute Vertical line to
391
- while (pts.length >= 1) {
392
- cy = parseFloat(pts.shift())
393
- paths[pathName] = geometries.path2.appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
410
+ while (pts.length >= i + 1) {
411
+ cy = parseFloat(pts[i++])
412
+ paths[pathName] = appendPoints([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], paths[pathName])
394
413
  }
395
414
  break
396
415
  case 'z': // close current line
@@ -404,7 +423,25 @@ const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups
404
423
  console.log('Warning: Unknow PATH command [' + co.c + ']')
405
424
  break
406
425
  }
407
- // console.log('postion: ['+cx+','+cy+'] after '+co.c);
426
+
427
+ const isCloseCmd = (cmd) => cmd === 'z' || cmd === 'Z'
428
+
429
+ if (pc !== true && paths[pathName] && paths[pathName].isClosed) {
430
+ let coNext = obj.commands[j + 1]
431
+
432
+ if (!coNext || !isCloseCmd(coNext.c)) {
433
+ if (pathSelfClosed === 'trim') {
434
+ while (coNext && !isCloseCmd(coNext.c)) {
435
+ j++
436
+ coNext = obj.commands[j + 1]
437
+ }
438
+ } else if (pathSelfClosed === 'split') {
439
+ newPath()
440
+ } else {
441
+ throw new Error(`Malformed svg path at ${obj.position[0]}:${co.pos}. Path closed itself with command #${j} ${co.c}${pts.join(' ')}`)
442
+ }
443
+ }
444
+ }
408
445
  }
409
446
  return paths
410
447
  }
@@ -1,7 +1,6 @@
1
1
  const { svg2cagX, svg2cagY, cagLengthX, cagLengthY, cagLengthP, reflect } = require('./helpers')
2
- // const { cssPxUnit } = require('./constants')
3
2
 
4
- const shapesMap = function (obj, codify, params) {
3
+ const shapesMap = (obj, codify, params) => {
5
4
  const { level, indent, on, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, target, segments } = params
6
5
 
7
6
  const types = {
@@ -1,60 +1,61 @@
1
1
  const { cagColor, cssStyle, css2cag } = require('./helpers')
2
2
  const { pxPmm } = require('./constants')
3
3
 
4
- const svgCore = function (obj, element) {
5
- if ('ID' in element) { obj.id = element.ID }
4
+ const svgCore = (obj, element) => {
5
+ if ('id' in element) { obj.id = element.id }
6
6
  if ('position' in element) { obj.position = element.position }
7
7
  }
8
8
 
9
- const svgPresentation = function (obj, element) {
9
+ const svgPresentation = (obj, element) => {
10
10
  // presentation attributes for all
11
- if ('DISPLAY' in element) { obj.visible = element.DISPLAY }
11
+ if ('display' in element) { obj.visible = element.display }
12
12
  // presentation attributes for solids
13
- if ('COLOR' in element) { obj.fill = cagColor(element.COLOR); obj.stroke = obj.fill }
14
- if ('OPACITY' in element) { obj.opacity = element.OPACITY }
15
- if ('FILL' in element) {
16
- obj.fill = cagColor(element.FILL)
13
+ if ('color' in element) { obj.fill = cagColor(element.color); obj.stroke = obj.fill }
14
+ if ('opacity' in element) { obj.opacity = element.opacity }
15
+ if ('fill' in element) {
16
+ obj.fill = cagColor(element.fill)
17
17
  } else {
18
18
  const s = cssStyle(element, 'fill')
19
19
  if (s) {
20
20
  obj.fill = cagColor(s)
21
21
  }
22
22
  }
23
- if ('FILL-OPACITY' in element) { obj.opacity = element['FILL-OPACITY'] }
23
+ if ('fill-opacity' in element) { obj.opacity = element['fill-opacity'] }
24
24
  // presentation attributes for lines
25
- if ('STROKE-WIDTH' in element) {
26
- obj.strokeWidth = element['STROKE-WIDTH']
25
+ if ('stroke-width' in element) {
26
+ obj.strokeWidth = element['stroke-width']
27
27
  } else {
28
28
  const sw = cssStyle(element, 'stroke-width')
29
29
  if (sw) {
30
30
  obj.strokeWidth = sw
31
31
  }
32
32
  }
33
- if ('STROKE' in element) {
34
- obj.stroke = cagColor(element.STROKE)
33
+ if ('stroke' in element) {
34
+ obj.stroke = cagColor(element.stroke)
35
35
  } else {
36
36
  const s = cssStyle(element, 'stroke')
37
37
  if (s) {
38
38
  obj.stroke = cagColor(s)
39
39
  }
40
40
  }
41
- if ('STROKE-OPACITY' in element) { obj.strokeOpacity = element['STROKE-OPACITY'] }
41
+ if ('stroke-opacity' in element) { obj.strokeOpacity = element['stroke-opacity'] }
42
42
  }
43
43
 
44
- const svgTransforms = function (cag, element) {
44
+ const svgTransformsRegExp = /\w+\(.+\)/i
45
+
46
+ const svgTransforms = (cag, element) => {
45
47
  let list = null
46
- if ('TRANSFORM' in element) {
47
- list = element.TRANSFORM
48
+ if ('transform' in element) {
49
+ list = element.transform
48
50
  } else {
49
51
  const s = cssStyle(element, 'transform')
50
52
  if (s) { list = s }
51
53
  }
52
54
  if (list !== null) {
53
55
  cag.transforms = []
54
- const exp = new RegExp('\\w+\\(.+\\)', 'i')
55
- let v = exp.exec(list)
56
+ let v = svgTransformsRegExp.exec(list)
56
57
  while (v !== null) {
57
- const s = exp.lastIndex
58
+ const s = svgTransformsRegExp.lastIndex
58
59
  const e = list.indexOf(')') + 1
59
60
  let t = list.slice(s, e) // the transform
60
61
  t = t.trim()
@@ -87,29 +88,30 @@ const svgTransforms = function (cag, element) {
87
88
  }
88
89
  // shorten the list and continue
89
90
  list = list.slice(e, list.length)
90
- v = exp.exec(list)
91
+ v = svgTransformsRegExp.exec(list)
91
92
  }
92
93
  }
93
94
  }
94
95
 
95
- const svgSvg = function (element, { customPxPmm }) {
96
+ const viewBoxRegExp = /([\d.-]+)[\s,]+([\d.-]+)[\s,]+([\d.-]+)[\s,]+([\d.-]+)/i
97
+
98
+ const svgSvg = (element, { customPxPmm }) => {
96
99
  // default SVG with no viewport
97
100
  const obj = { type: 'svg', x: 0, y: 0, width: '100%', height: '100%', strokeWidth: '1' }
98
101
 
99
102
  // default units per mm
100
103
  obj.unitsPmm = [pxPmm, pxPmm]
101
104
 
102
- if ('PXPMM' in element) {
105
+ if ('pxpmm' in element) {
103
106
  // WOW! a supplied value for pixels per milimeter!!!
104
- obj.pxPmm = element.PXPMM
107
+ obj.pxPmm = element.pxpmm
105
108
  obj.unitsPmm = [obj.pxPmm, obj.pxPmm]
106
109
  }
107
- if ('WIDTH' in element) { obj.width = element.WIDTH }
108
- if ('HEIGHT' in element) { obj.height = element.HEIGHT }
109
- if ('VIEWBOX' in element) {
110
- const list = element.VIEWBOX.trim()
111
- const exp = new RegExp('([\\d\\.\\-]+)[\\s,]+([\\d\\.\\-]+)[\\s,]+([\\d\\.\\-]+)[\\s,]+([\\d\\.\\-]+)', 'i')
112
- const v = exp.exec(list)
110
+ if ('width' in element) { obj.width = element.width }
111
+ if ('height' in element) { obj.height = element.height }
112
+ if ('viewBox' in element) {
113
+ const list = element.viewBox.trim()
114
+ const v = viewBoxRegExp.exec(list)
113
115
  if (v !== null) {
114
116
  obj.viewX = parseFloat(v[1])
115
117
  obj.viewY = parseFloat(v[2])
@@ -158,12 +160,12 @@ const svgSvg = function (element, { customPxPmm }) {
158
160
  return obj
159
161
  }
160
162
 
161
- const svgEllipse = function (element) {
163
+ const svgEllipse = (element) => {
162
164
  const obj = { type: 'ellipse', cx: '0', cy: '0', rx: '0', ry: '0' }
163
- if ('CX' in element) { obj.cx = element.CX }
164
- if ('CY' in element) { obj.cy = element.CY }
165
- if ('RX' in element) { obj.rx = element.RX }
166
- if ('RY' in element) { obj.ry = element.RY }
165
+ if ('cx' in element) { obj.cx = element.cx }
166
+ if ('cy' in element) { obj.cy = element.cy }
167
+ if ('rx' in element) { obj.rx = element.rx }
168
+ if ('ry' in element) { obj.ry = element.ry }
167
169
  // transforms
168
170
  svgTransforms(obj, element)
169
171
  // core attributes
@@ -173,12 +175,12 @@ const svgEllipse = function (element) {
173
175
  return obj
174
176
  }
175
177
 
176
- const svgLine = function (element) {
178
+ const svgLine = (element) => {
177
179
  const obj = { type: 'line', x1: '0', y1: '0', x2: '0', y2: '0' }
178
- if ('X1' in element) { obj.x1 = element.X1 }
179
- if ('Y1' in element) { obj.y1 = element.Y1 }
180
- if ('X2' in element) { obj.x2 = element.X2 }
181
- if ('Y2' in element) { obj.y2 = element.Y2 }
180
+ if ('x1' in element) { obj.x1 = element.x1 }
181
+ if ('y1' in element) { obj.y1 = element.y1 }
182
+ if ('x2' in element) { obj.x2 = element.x2 }
183
+ if ('y2' in element) { obj.y2 = element.y2 }
182
184
  // transforms
183
185
  svgTransforms(obj, element)
184
186
  // core attributes
@@ -188,9 +190,9 @@ const svgLine = function (element) {
188
190
  return obj
189
191
  }
190
192
 
191
- const svgListOfPoints = function (list) {
193
+ const svgListOfPoints = (list) => {
192
194
  const points = []
193
- const exp = new RegExp('([\\d\\-\\+\\.]+)[\\s,]+([\\d\\-\\+\\.]+)[\\s,]*', 'i')
195
+ const exp = /([\d\-+.]+)[\s,]+([\d\-+.]+)[\s,]*/i
194
196
  list = list.trim()
195
197
  let v = exp.exec(list)
196
198
  while (v !== null) {
@@ -204,7 +206,7 @@ const svgListOfPoints = function (list) {
204
206
  return points
205
207
  }
206
208
 
207
- const svgPolyline = function (element) {
209
+ const svgPolyline = (element) => {
208
210
  const obj = { type: 'polyline' }
209
211
  // transforms
210
212
  svgTransforms(obj, element)
@@ -213,13 +215,13 @@ const svgPolyline = function (element) {
213
215
  // presentation attributes
214
216
  svgPresentation(obj, element)
215
217
 
216
- if ('POINTS' in element) {
217
- obj.points = svgListOfPoints(element.POINTS)
218
+ if ('points' in element) {
219
+ obj.points = svgListOfPoints(element.points)
218
220
  }
219
221
  return obj
220
222
  }
221
223
 
222
- const svgPolygon = function (element) {
224
+ const svgPolygon = (element) => {
223
225
  const obj = { type: 'polygon' }
224
226
  // transforms
225
227
  svgTransforms(obj, element)
@@ -228,30 +230,30 @@ const svgPolygon = function (element) {
228
230
  // presentation attributes
229
231
  svgPresentation(obj, element)
230
232
 
231
- if ('POINTS' in element) {
232
- obj.points = svgListOfPoints(element.POINTS)
233
+ if ('points' in element) {
234
+ obj.points = svgListOfPoints(element.points)
233
235
  }
234
236
  return obj
235
237
  }
236
238
 
237
- const svgRect = function (element) {
239
+ const svgRect = (element) => {
238
240
  const obj = { type: 'rect', x: '0', y: '0', rx: '0', ry: '0', width: '0', height: '0' }
239
241
 
240
- if ('X' in element) { obj.x = element.X }
241
- if ('Y' in element) { obj.y = element.Y }
242
- if ('RX' in element) {
243
- obj.rx = element.RX
244
- if (!('RY' in element)) { obj.ry = obj.rx } // by SVG specification
242
+ if ('x' in element) { obj.x = element.x }
243
+ if ('y' in element) { obj.y = element.y }
244
+ if ('rx' in element) {
245
+ obj.rx = element.rx
246
+ if (!('ry' in element)) { obj.ry = obj.rx } // by SVG specification
245
247
  }
246
- if ('RY' in element) {
247
- obj.ry = element.RY
248
- if (!('RX' in element)) { obj.rx = obj.ry } // by SVG specification
248
+ if ('ry' in element) {
249
+ obj.ry = element.ry
250
+ if (!('rx' in element)) { obj.rx = obj.ry } // by SVG specification
249
251
  }
250
252
  if (obj.rx !== obj.ry) {
251
- console.log('Warning: Unsupported RECT with RX and RY radius')
253
+ console.log('Warning: Unsupported RECT with rx and ry radius')
252
254
  }
253
- if ('WIDTH' in element) { obj.width = element.WIDTH }
254
- if ('HEIGHT' in element) { obj.height = element.HEIGHT }
255
+ if ('width' in element) { obj.width = element.width }
256
+ if ('height' in element) { obj.height = element.height }
255
257
  // transforms
256
258
  svgTransforms(obj, element)
257
259
  // core attributes
@@ -261,12 +263,12 @@ const svgRect = function (element) {
261
263
  return obj
262
264
  }
263
265
 
264
- const svgCircle = function (element) {
266
+ const svgCircle = (element) => {
265
267
  const obj = { type: 'circle', x: '0', y: '0', radius: '0' }
266
268
 
267
- if ('CX' in element) { obj.x = element.CX }
268
- if ('CY' in element) { obj.y = element.CY }
269
- if ('R' in element) { obj.radius = element.R }
269
+ if ('cx' in element) { obj.x = element.cx }
270
+ if ('cy' in element) { obj.y = element.cy }
271
+ if ('r' in element) { obj.radius = element.r }
270
272
  // transforms
271
273
  svgTransforms(obj, element)
272
274
  // core attributes
@@ -276,7 +278,7 @@ const svgCircle = function (element) {
276
278
  return obj
277
279
  }
278
280
 
279
- const svgGroup = function (element) {
281
+ const svgGroup = (element) => {
280
282
  const obj = { type: 'group' }
281
283
  // transforms
282
284
  svgTransforms(obj, element)
@@ -285,11 +287,11 @@ const svgGroup = function (element) {
285
287
  // presentation attributes
286
288
  svgPresentation(obj, element)
287
289
 
288
- if ('X' in element || 'Y' in element) {
290
+ if ('x' in element || 'y' in element) {
289
291
  let x = '0'
290
292
  let y = '0'
291
- if ('X' in element) x = element.X
292
- if ('Y' in element) y = element.Y
293
+ if ('x' in element) x = element.x
294
+ if ('y' in element) y = element.y
293
295
  if (!('transforms' in obj)) obj.transforms = []
294
296
  const o = { translate: [x, y] }
295
297
  obj.transforms.push(o)
@@ -302,7 +304,7 @@ const svgGroup = function (element) {
302
304
  //
303
305
  // Convert the PATH element into object representation
304
306
  //
305
- const svgPath = function (element) {
307
+ const svgPath = (element) => {
306
308
  const obj = { type: 'path' }
307
309
  // transforms
308
310
  svgTransforms(obj, element)
@@ -312,14 +314,15 @@ const svgPath = function (element) {
312
314
  svgPresentation(obj, element)
313
315
 
314
316
  obj.commands = []
315
- if ('D' in element) {
317
+ if ('d' in element) {
316
318
  let co = null // current command
317
319
  let bf = ''
318
320
 
319
321
  let i = 0
320
- const l = element.D.length
322
+ const l = element.d.length
323
+ const offset = element.position[1] - l - 2
321
324
  while (i < l) {
322
- const c = element.D[i]
325
+ const c = element.d[i]
323
326
  switch (c) {
324
327
  // numbers
325
328
  // FIXME support E notation numbers
@@ -379,7 +382,7 @@ const svgPath = function (element) {
379
382
  }
380
383
  obj.commands.push(co)
381
384
  }
382
- co = { c: c, p: [] }
385
+ co = { c: c, p: [], pos: i + offset }
383
386
  break
384
387
  // white space
385
388
  case ',':
@@ -408,11 +411,11 @@ const svgPath = function (element) {
408
411
  }
409
412
 
410
413
  // generate GROUP with attributes from USE element
411
- // - expect X,Y,HEIGHT,WIDTH,XLINK:HREF
412
- // - append translate(x,y) if X,Y available
414
+ // - expect x,y,height,width,XLINK:HREF
415
+ // - append translate(x,y) if x,y available
413
416
  // deep clone the referenced OBJECT and add to group
414
417
  // - clone using JSON.parse(JSON.stringify(obj))
415
- const svgUse = function (element, { svgObjects }) {
418
+ const svgUse = (element, { svgObjects }) => {
416
419
  const obj = { type: 'group' }
417
420
  // transforms
418
421
  svgTransforms(obj, element)
@@ -421,20 +424,20 @@ const svgUse = function (element, { svgObjects }) {
421
424
  // presentation attributes
422
425
  svgPresentation(obj, element)
423
426
 
424
- if ('X' in element || 'Y' in element) {
427
+ if ('x' in element || 'y' in element) {
425
428
  let x = '0'
426
429
  let y = '0'
427
- if ('X' in element) x = element.X
428
- if ('Y' in element) y = element.Y
430
+ if ('x' in element) x = element.x
431
+ if ('y' in element) y = element.y
429
432
  if (!('transforms' in obj)) obj.transforms = []
430
433
  const o = { translate: [x, y] }
431
434
  obj.transforms.push(o)
432
435
  }
433
436
 
434
437
  obj.objects = []
435
- if ('XLINK:HREF' in element) {
438
+ if ('xlink:href' in element) {
436
439
  // lookup the named object
437
- let ref = element['XLINK:HREF']
440
+ let ref = element['xlink:href']
438
441
  if (ref[0] === '#') { ref = ref.slice(1, ref.length) }
439
442
  if (svgObjects[ref] !== undefined) {
440
443
  ref = svgObjects[ref]
@@ -2,13 +2,12 @@ const test = require('ava')
2
2
 
3
3
  const countOf = require('../../test/helpers/countOf')
4
4
 
5
- const deserializer = require('../index.js')
5
+ const deserializer = require('../src/index.js')
6
6
 
7
7
  // deserializer
8
8
 
9
9
  test('deserialize : translate svg produced by inkscape to script', (t) => {
10
- const sourceSvg = `
11
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
10
+ const sourceSvg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
12
11
  <svg
13
12
  xmlns:dc="http://purl.org/dc/elements/1.1/"
14
13
  xmlns:cc="http://creativecommons.org/ns#"
@@ -1,12 +1,13 @@
1
1
  const test = require('ava')
2
2
 
3
- const deserializer = require('../index.js')
3
+ const deserializer = require('../src/index.js')
4
+
4
5
  const { measurements } = require('@jscad/modeling')
5
6
 
6
7
  // deserializer
7
8
 
8
9
  test('deserialize : instantiate svg (rect) to objects', (t) => {
9
- const sourceSvg = `<svg PXPMM="10" width="500" height="500">
10
+ const sourceSvg = `<svg pxpmm="10" width="500" height="500">
10
11
  <rect x="80" y="60" width="250" height="250" color="red"/>
11
12
  <rect x="140" y="120" width="250" height="250" rx="40" color="rgb(0,255,0)"/>
12
13
  <rect x="140" y="120" width="250" height="250" ry="40" color="blue"/>
@@ -149,7 +150,7 @@ test('deserialize : instantiate svg (line) to objects', (t) => {
149
150
  t.is(shape.points.length, 2)
150
151
 
151
152
  // test getting stroke width from group
152
- sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120"">
153
+ sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120">
153
154
  <g stroke-width="2">
154
155
  <line x1="20" y1="100" x2="100" y2="20" stroke="black"/>
155
156
  </g>
@@ -180,7 +181,7 @@ test('deserialize : instantiate svg (path: simple) to objects', (t) => {
180
181
  t.is(shape.points.length, 3)
181
182
 
182
183
  // test getting stroke width from group
183
- sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120"">
184
+ sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120">
184
185
  <g stroke-width="2">
185
186
  <path d="M150 0 L75 200 L225 200 Z" />
186
187
  </g>
@@ -210,7 +211,7 @@ test('deserialize : instantiate svg (path: simple) to objects', (t) => {
210
211
  shape = observed[0]
211
212
  t.is(shape.points.length, 3)
212
213
 
213
- sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120"">
214
+ sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120">
214
215
  <path d="M 240.00000 56.00000 H 270.00000 V 86.00000 H 300.00000 V 116.00000 H 330.00000 V 146.00000 H 240.00000 V 56.00000 Z"/>
215
216
  </svg>`
216
217
 
@@ -247,10 +248,10 @@ test('deserialize : instantiate svg (path: arc) to objects', (t) => {
247
248
  t.is(observed.length, 2)
248
249
  shape = observed[0]
249
250
  t.is(shape.points.length, 15) // segments double on a 3/4 circle
250
- t.deepEqual(measurements.measureBoundingBox(shape), [[64.91110599999999, -77.611103, 0], [90.21850570104527, -52.30370029895471, 0]])
251
+ t.deepEqual(measurements.measureBoundingBox(shape), [[64.91110599999999, -77.611105, 0], [90.21850570104527, -52.30370029895471, 0]])
251
252
  shape = observed[1]
252
253
  t.is(shape.points.length, 15) // segments double on a 3/4 circle
253
- t.deepEqual(measurements.measureBoundingBox(shape), [[50.79999599999999, -136.03302387090216, 0], [72.27222493929787, -110.6793647936299, 0]])
254
+ t.deepEqual(measurements.measureBoundingBox(shape), [[50.799996, -136.03302387090216, 0], [72.27222493929787, -110.6793647936299, 0]])
254
255
  })
255
256
 
256
257
  // ################################
@@ -360,8 +361,7 @@ test('deserialize : instantiate svg (path: with bezier) to objects', (t) => {
360
361
  // ################################
361
362
 
362
363
  test('deserialize : instantiate svg produced by inkscape to objects', (t) => {
363
- const sourceSvg = `
364
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
364
+ const sourceSvg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
365
365
  <svg
366
366
  xmlns:dc="http://purl.org/dc/elements/1.1/"
367
367
  xmlns:cc="http://creativecommons.org/ns#"
@@ -434,16 +434,16 @@ test('deserialize : instantiate shape with a hole to objects', (t) => {
434
434
  let observed = deserializer.deserialize({ output: 'geometry', target: 'geom2', addMetaData: false }, sourceSvg)
435
435
  t.is(observed.length, 2)
436
436
  let shape = observed[0]
437
- t.is(shape.sides.length, 39)
437
+ t.is(shape.sides.length, 38)
438
438
  shape = observed[1]
439
- t.is(shape.sides.length, 39)
439
+ t.is(shape.sides.length, 38)
440
440
 
441
441
  observed = deserializer.deserialize({ output: 'geometry', target: 'path', addMetaData: false }, sourceSvg)
442
442
  t.is(observed.length, 2)
443
443
  shape = observed[0]
444
- t.is(shape.points.length, 39)
444
+ t.is(shape.points.length, 38)
445
445
  shape = observed[1]
446
- t.is(shape.points.length, 39)
446
+ t.is(shape.points.length, 38)
447
447
  })
448
448
 
449
449
  // ################################
@@ -458,12 +458,12 @@ test('deserialize : instantiate shape with a nested hole to objects', (t) => {
458
458
  let observed = deserializer.deserialize({ output: 'geometry', target: 'geom2', addMetaData: false }, sourceSvg)
459
459
  t.is(observed.length, 4)
460
460
  let shape = observed[0]
461
- t.is(shape.sides.length, 39)
461
+ t.is(shape.sides.length, 38)
462
462
 
463
463
  observed = deserializer.deserialize({ output: 'geometry', target: 'path', addMetaData: false }, sourceSvg)
464
464
  t.is(observed.length, 4)
465
465
  shape = observed[0]
466
- t.is(shape.points.length, 39)
466
+ t.is(shape.points.length, 38)
467
467
  })
468
468
 
469
469
  // ################################
@@ -0,0 +1,23 @@
1
+ const test = require('ava')
2
+
3
+ const deserializer = require('../src/index.js')
4
+
5
+ // deserializer
6
+
7
+ // ################################
8
+
9
+ test('deserialize issue 885', (t) => {
10
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="5.791015625" height="11.71875">
11
+ <path d="M1.84-3.17L0.53-3.48L0.97-7.11L5.12-7.11L5.12-5.83L2.30-5.83L2.14-4.42Q2.32-4.52 2.60-4.60Q2.89-4.68 3.16-4.68L3.16-4.68Q4.22-4.68 4.79-4.05Q5.36-3.42 5.36-2.29L5.36-2.29Q5.36-1.61 5.06-1.05Q4.75-0.50 4.20-0.20Q3.65 0.10 2.90 0.10L2.90 0.10Q2.23 0.10 1.64-0.18Q1.05-0.45 0.72-0.94Q0.39-1.42 0.40-2.02L0.40-2.02L2.05-2.02Q2.07-1.63 2.29-1.40Q2.52-1.17 2.89-1.17L2.89-1.17Q3.72-1.17 3.72-2.40L3.72-2.40Q3.72-3.54 2.70-3.54L2.70-3.54Q2.12-3.54 1.84-3.17L1.84-3.17Z"/>
12
+ </svg>`
13
+
14
+ t.throws(() => {
15
+ deserializer.deserialize({ output: 'geometry' }, svg)
16
+ }, { instanceOf: Error }, 'Malformed svg path at 2:445. Path closed itself with command #29: "Q2.12 -3.54 1.84 -3.17". to avoid this error use pathSelfClosed:\'split\' or pathSelfClosed:\'trim\' option')
17
+
18
+ let shapes = deserializer.deserialize({ output: 'geometry', pathSelfClosed: 'split' }, svg)
19
+ t.is(shapes.length, 2)
20
+
21
+ shapes = deserializer.deserialize({ output: 'geometry', pathSelfClosed: 'trim' }, svg)
22
+ t.is(shapes.length, 1)
23
+ })
@@ -2,14 +2,14 @@ const test = require('ava')
2
2
 
3
3
  const countOf = require('../../test/helpers/countOf')
4
4
 
5
- const deserializer = require('../index.js')
5
+ const deserializer = require('../src/index.js')
6
6
 
7
7
  // deserializer
8
8
 
9
9
  // ################################
10
10
 
11
11
  test('deserialize : translate svg (rect) to script', (t) => {
12
- const sourceSvg = `<svg PXPMM="10" width="500" height="500">
12
+ const sourceSvg = `<svg pxpmm="10" width="500" height="500">
13
13
  <rect x="80" y="60" width="250" height="250" color="red"/>
14
14
  <rect x="140" y="120" width="250" height="250" rx="40" color="rgb(0,255,0)"/>
15
15
  <rect x="140" y="120" width="250" height="250" ry="40" color="blue"/>
@@ -88,7 +88,7 @@ test('deserialize : translate svg (line) to script', (t) => {
88
88
  // TODO
89
89
 
90
90
  // test getting stroke width from group
91
- sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120"">
91
+ sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120">
92
92
  <g stroke-width="2">
93
93
  <line x1="20" y1="100" x2="100" y2="20" stroke="black"/>
94
94
  </g>
@@ -133,7 +133,7 @@ test('deserialize : translate svg (polyline) to script', (t) => {
133
133
  // FIXME t.is(countOf('path2.fromPoints', obs), 1)
134
134
 
135
135
  // test getting stroke width from group
136
- sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120"">
136
+ sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120">
137
137
  <g stroke-width="2">
138
138
  <polyline fill="none" stroke="black" points="20,100 40,60 70,80 100,20"/>
139
139
  </g>
@@ -168,7 +168,7 @@ test('deserialize : translate svg (path: simple) to script', (t) => {
168
168
  t.is(countOf('geom2.fromPoints', obs), 1)
169
169
 
170
170
  // test getting stroke width from group
171
- sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120"">
171
+ sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120">
172
172
  <g stroke-width="2">
173
173
  <path d="M150 0 L75 200 L225 200 Z" />
174
174
  </g>
@@ -190,7 +190,7 @@ test('deserialize : translate svg (path: simple) to script', (t) => {
190
190
  t.is(countOf('path2.close', obs), 1)
191
191
  t.is(countOf('geom2.fromPoints', obs), 0)
192
192
 
193
- sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120"">
193
+ sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120">
194
194
  <path fill="#ff8000" d="m 240.00000 190.00000 h 30.00000 v 30.00000 h 30.00000 v 30.00000 h 30.00000 v 30.00000 h -90.00000 v -90.00000 z"/>
195
195
  </svg>`
196
196
 
@@ -202,7 +202,7 @@ test('deserialize : translate svg (path: simple) to script', (t) => {
202
202
  t.is(countOf('geom2.fromPoints', obs), 1)
203
203
  t.is(countOf('colors.colorize', obs), 1) // fill
204
204
 
205
- sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120"">
205
+ sourceSvg = `<svg width="120" height="120" viewBox="0 0 120 120">
206
206
  <path d="M 240.00000 56.00000 H 270.00000 V 86.00000 H 300.00000 V 116.00000 H 330.00000 V 146.00000 H 240.00000 V 56.00000 Z"/>
207
207
  </svg>`
208
208