UNPKG

17.8 kBJavaScriptView Raw
1const { geometries, primitives, transforms } = require('@jscad/modeling')
2
3const { svg2cagX, svg2cagY, cagLengthX, cagLengthY, cagLengthP, reflect } = require('./helpers')
4// const { cssPxUnit } = require('./constants')
5
6const shapesMapGeometry = (obj, objectify, params) => {
7 const { svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, target, segments } = params
8
9 const types = {
10 group: (obj) => objectify({ target, segments }, obj),
11
12 rect: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments) => {
13 let x = cagLengthX(obj.x, svgUnitsPmm, svgUnitsX)
14 let y = (0 - cagLengthY(obj.y, svgUnitsPmm, svgUnitsY))
15 const w = cagLengthX(obj.width, svgUnitsPmm, svgUnitsX)
16 const h = cagLengthY(obj.height, svgUnitsPmm, svgUnitsY)
17 const rx = cagLengthX(obj.rx, svgUnitsPmm, svgUnitsX)
18 // const ry = cagLengthY(obj.ry, svgUnitsPmm, svgUnitsY)
19
20 let shape
21 if (w > 0 && h > 0) {
22 x = (x + (w / 2)).toFixed(4) // position the object via the center
23 y = (y - (h / 2)).toFixed(4) // position the object via the center
24 if (rx === 0) {
25 shape = transforms.center({ center: [x, y, 0] }, primitives.rectangle({ size: [w, h] }))
26 } else {
27 shape = transforms.center({ center: [x, y, 0] }, primitives.roundedRectangle({ segments, size: [w, h], roundRadius: rx }))
28 }
29 if (target === 'path') {
30 shape = geometries.path2.fromPoints({ closed: true }, geometries.geom2.toPoints(shape))
31 }
32 }
33 return shape
34 },
35
36 circle: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments) => {
37 const x = cagLengthX(obj.x, svgUnitsPmm, svgUnitsX)
38 const y = (0 - cagLengthY(obj.y, svgUnitsPmm, svgUnitsY))
39 const r = cagLengthP(obj.radius, svgUnitsPmm, svgUnitsV)
40
41 let shape
42 if (r > 0) {
43 shape = transforms.center({ center: [x, y, 0] }, primitives.circle({ segments, radius: r }))
44 if (target === 'path') {
45 shape = geometries.path2.fromPoints({ closed: true }, geometries.geom2.toPoints(shape))
46 }
47 }
48 return shape
49 },
50
51 ellipse: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments) => {
52 const rx = cagLengthX(obj.rx, svgUnitsPmm, svgUnitsX)
53 const ry = cagLengthY(obj.ry, svgUnitsPmm, svgUnitsY)
54 const cx = cagLengthX(obj.cx, svgUnitsPmm, svgUnitsX)
55 const cy = (0 - cagLengthY(obj.cy, svgUnitsPmm, svgUnitsY))
56
57 let shape
58 if (rx > 0 && ry > 0) {
59 shape = transforms.center({ center: [cx, cy, 0] }, primitives.ellipse({ segments, radius: [rx, ry] }))
60 if (target === 'path') {
61 shape = geometries.path2.fromPoints({ closed: true }, geometries.geom2.toPoints(shape))
62 }
63 }
64 return shape
65 },
66
67 line: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV) => {
68 const x1 = cagLengthX(obj.x1, svgUnitsPmm, svgUnitsX)
69 const y1 = (0 - cagLengthY(obj.y1, svgUnitsPmm, svgUnitsY))
70 const x2 = cagLengthX(obj.x2, svgUnitsPmm, svgUnitsX)
71 const y2 = (0 - cagLengthY(obj.y2, svgUnitsPmm, svgUnitsY))
72 // let r = cssPxUnit // default
73 // if ('strokeWidth' in obj) {
74 // r = cagLengthP(obj.strokeWidth, svgUnitsPmm, svgUnitsV) / 2
75 // } else {
76 // const v = groupValue(svgGroups, 'strokeWidth')
77 // if (v !== null) {
78 // r = cagLengthP(v, svgUnitsPmm, svgUnitsV) / 2
79 // }
80 // }
81
82 const shape = primitives.line([[x1, y1], [x2, y2]])
83 if (target === 'geom2') {
84 // TODO expand if 2D target
85 }
86 return shape
87 },
88
89 polygon: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY) => {
90 const points = []
91 for (let j = 0; j < obj.points.length; j++) {
92 const p = obj.points[j]
93 if ('x' in p && 'y' in p) {
94 const x = cagLengthX(p.x, svgUnitsPmm, svgUnitsX)
95 const y = (0 - cagLengthY(p.y, svgUnitsPmm, svgUnitsY))
96 points.push([x, y])
97 }
98 }
99 if (target === 'geom2') {
100 return geometries.geom2.fromPoints(points)
101 }
102 return geometries.path2.fromPoints({}, points)
103 },
104
105 polyline: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV) => {
106 const points = []
107 // let r = cssPxUnit // default
108 // if ('strokeWidth' in obj) {
109 // r = cagLengthP(obj.strokeWidth, svgUnitsPmm, svgUnitsV) / 2
110 // } else {
111 // const v = groupValue(svgGroups, 'strokeWidth')
112 // if (v !== null) {
113 // r = cagLengthP(v, svgUnitsPmm, svgUnitsV) / 2
114 // }
115 // }
116 for (let j = 0; j < obj.points.length; j++) {
117 const p = obj.points[j]
118 if ('x' in p && 'y' in p) {
119 const x = cagLengthX(p.x, svgUnitsPmm, svgUnitsX)
120 const y = (0 - cagLengthY(p.y, svgUnitsPmm, svgUnitsY))
121 points.push([x, y])
122 }
123 }
124
125 const shape = primitives.line(points)
126 if (target === 'geom2') {
127 // TODO expand if 2D target
128 // .expandToCAG(r, CSG.defaultResolution2D)
129 }
130 return shape
131 },
132
133 path: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments) => {
134 const listofpaths = expandPath(obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments)
135 // order is important
136 const listofentries = Object.entries(listofpaths).sort((a, b) => a[0].localeCompare(b[0]))
137 const shapes = listofentries.map((entry) => {
138 const path = entry[1]
139 if (target === 'geom2' && path.isClosed) {
140 const points = geometries.path2.toPoints(path).slice()
141 points.push(points[0]) // add first point again to create closing sides
142 return geometries.geom2.fromPoints(points)
143 }
144 return path
145 })
146 return shapes
147 }
148 }
149
150 return types[obj.type](obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments)
151}
152
153module.exports = shapesMapGeometry
154
155const expandPath = (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups, segments) => {
156 const paths = {}
157 const on = 'path'
158
159 // let r = cssPxUnit // default
160 // if ('strokeWidth' in obj) {
161 // r = cagLengthP(obj.strokeWidth, svgUnitsPmm, svgUnitsV) / 2
162 // } else {
163 // const v = groupValue(svgGroups, 'strokeWidth')
164 // if (v !== null) {
165 // r = cagLengthP(v, svgUnitsPmm, svgUnitsV) / 2
166 // }
167 // }
168 // Note: All values are SVG values
169 let sx = 0 // starting position
170 let sy = 0
171 let cx = 0 // current position
172 let cy = 0
173 let pi = 0 // current path index
174 let pathName = on + pi // current path name
175 let pc = false // current path closed
176 let bx = 0 // 2nd control point from previous C command
177 let by = 0 // 2nd control point from previous C command
178 let qx = 0 // 2nd control point from previous Q command
179 let qy = 0 // 2nd control point from previous Q command
180
181 for (let j = 0; j < obj.commands.length; j++) {
182 const co = obj.commands[j]
183 const pts = co.p
184 // console.log('postion: ['+cx+','+cy+'] before '+co.c);
185 switch (co.c) {
186 case 'm': // relative move to X,Y
187 // special case, if at beginning of path then treat like absolute M
188 if (j === 0) {
189 cx = 0; cy = 0
190 }
191 // close the previous path
192 if (pi > 0 && pc === false) {
193 // FIXME paths[pathName] = paths[pathName]
194 }
195 // 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)]])
203 sx = cx; sy = cy
204 }
205 // 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])
210 }
211 break
212 case 'M': // absolute move to X,Y
213 // close the previous path
214 if (pi > 0 && pc === false) {
215 // FIXME paths[pathName] = paths[pathName]
216 }
217 // 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)]])
225 sx = cx; sy = cy
226 }
227 // 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])
232 }
233 break
234 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())
243 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 }
245 break
246 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())
255 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 }
257 break
258 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())
266 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 const rf = reflect(bx, by, cx, cy)
268 bx = rf[0]
269 by = rf[1]
270 }
271 break
272 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())
280 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 const rf = reflect(bx, by, cx, cy)
282 bx = rf[0]
283 by = rf[1]
284 }
285 break
286 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())
292 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 const rf = reflect(qx, qy, cx, cy)
294 qx = rf[0]
295 qy = rf[1]
296 }
297 break
298 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())
304 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 const rf = reflect(qx, qy, cx, cy)
306 qx = rf[0]
307 qy = rf[1]
308 }
309 break
310 case 't': // relative quadratic Bézier shorthand
311 while (pts.length >= 2) {
312 cx = cx + parseFloat(pts.shift())
313 cy = cy + parseFloat(pts.shift())
314 paths[pathName] = geometries.path2.appendBezier({ segments, controlPoints: [[svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [cx, cy]] }, paths[pathName])
315 const rf = reflect(qx, qy, cx, cy)
316 qx = rf[0]
317 qy = rf[1]
318 }
319 break
320 case 'T': // absolute quadratic Bézier shorthand
321 while (pts.length >= 2) {
322 cx = parseFloat(pts.shift())
323 cy = parseFloat(pts.shift())
324 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 const rf = reflect(qx, qy, cx, cy)
326 qx = rf[0]
327 qy = rf[1]
328 }
329 break
330 case 's': // relative cubic Bézier shorthand
331 while (pts.length >= 4) {
332 const x1 = bx // reflection of 2nd control point from previous C
333 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())
338 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 const rf = reflect(bx, by, cx, cy)
340 bx = rf[0]
341 by = rf[1]
342 }
343 break
344 case 'S': // absolute cubic Bézier shorthand
345 while (pts.length >= 4) {
346 const x1 = bx // reflection of 2nd control point from previous C
347 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())
352 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 const rf = reflect(bx, by, cx, cy)
354 bx = rf[0]
355 by = rf[1]
356 }
357 break
358 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])
362 }
363 break
364 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])
368 }
369 break
370 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])
375 }
376 break
377 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])
382 }
383 break
384 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])
388 }
389 break
390 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])
394 }
395 break
396 case 'z': // close current line
397 case 'Z':
398 paths[pathName] = geometries.path2.close(paths[pathName])
399 cx = sx
400 cy = sy // return to the starting point
401 pc = true
402 break
403 default:
404 console.log('Warning: Unknow PATH command [' + co.c + ']')
405 break
406 }
407 // console.log('postion: ['+cx+','+cy+'] after '+co.c);
408 }
409 return paths
410}