const {CSG, CAG} = require('@jscad/csg') const {svg2cagX, svg2cagY, cagLengthX, cagLengthY, cagLengthP, reflect, groupValue} = require('./helpers') const {cssPxUnit} = require('./constants') const shapesMap = function (obj, codify, params) { const {svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups} = params const types = { group: (obj) => { // cag from nested element return codify(obj) }, rect: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY) => { let x = cagLengthX(obj.x, svgUnitsPmm, svgUnitsX) let y = (0 - cagLengthY(obj.y, svgUnitsPmm, svgUnitsY)) const w = cagLengthX(obj.width, svgUnitsPmm, svgUnitsX) const h = cagLengthY(obj.height, svgUnitsPmm, svgUnitsY) const rx = cagLengthX(obj.rx, svgUnitsPmm, svgUnitsX) // const ry = cagLengthY(obj.ry, svgUnitsPmm, svgUnitsY) if (w > 0 && h > 0) { x = (x + (w / 2)).toFixed(4) // position the object via the center y = (y - (h / 2)).toFixed(4) // position the object via the center if (rx === 0) { return CAG.rectangle({center: [x, y], radius: [w / 2, h / 2]}) } else { return CAG.roundedRectangle({center: [x, y], radius: [w / 2, h / 2], roundradius: rx}) } } }, circle: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV) => { const x = cagLengthX(obj.x, svgUnitsPmm, svgUnitsX) const y = (0 - cagLengthY(obj.y, svgUnitsPmm, svgUnitsY)) const r = cagLengthP(obj.radius, svgUnitsPmm, svgUnitsV) if (r > 0) { return CAG.circle({center: [x, y], radius: r}) } }, ellipse: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV) => { const rx = cagLengthX(obj.rx, svgUnitsPmm, svgUnitsX) const ry = cagLengthY(obj.ry, svgUnitsPmm, svgUnitsY) const cx = cagLengthX(obj.cx, svgUnitsPmm, svgUnitsX) const cy = (0 - cagLengthY(obj.cy, svgUnitsPmm, svgUnitsY)) if (rx > 0 && ry > 0) { return CAG.ellipse({center: [cx, cy], radius: [rx, ry]}) } }, line: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV) => { const x1 = cagLengthX(obj.x1, svgUnitsPmm, svgUnitsX) const y1 = (0 - cagLengthY(obj.y1, svgUnitsPmm, svgUnitsY)) const x2 = cagLengthX(obj.x2, svgUnitsPmm, svgUnitsX) const y2 = (0 - cagLengthY(obj.y2, svgUnitsPmm, svgUnitsY)) let r = cssPxUnit // default if ('strokeWidth' in obj) { r = cagLengthP(obj.strokeWidth, svgUnitsPmm, svgUnitsV) / 2 } else { const v = groupValue(svgGroups, 'strokeWidth') if (v !== null) { r = cagLengthP(v, svgUnitsPmm, svgUnitsV) / 2 } } const tmpObj = new CSG.Path2D([[x1, y1], [x2, y2]], false) .expandToCAG(r, CSG.defaultResolution2D) return tmpObj }, polygon: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY) => { let points = [] for (let j = 0; j < obj.points.length; j++) { const p = obj.points[j] if ('x' in p && 'y' in p) { const x = cagLengthX(p.x, svgUnitsPmm, svgUnitsX) const y = (0 - cagLengthY(p.y, svgUnitsPmm, svgUnitsY)) points.push([x, y]) } } let tmpObj = new CSG.Path2D(points, true).innerToCAG() return tmpObj }, polyline: (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV) => { let points = [] let r = cssPxUnit // default if ('strokeWidth' in obj) { r = cagLengthP(obj.strokeWidth, svgUnitsPmm, svgUnitsV) / 2 } else { const v = groupValue(svgGroups, 'strokeWidth') if (v !== null) { r = cagLengthP(v, svgUnitsPmm, svgUnitsV) / 2 } } for (let j = 0; j < obj.points.length; j++) { const p = obj.points[j] if ('x' in p && 'y' in p) { let x = cagLengthX(p.x, svgUnitsPmm, svgUnitsX) let y = (0 - cagLengthY(p.y, svgUnitsPmm, svgUnitsY)) points.push([x, y]) } } let tmpObj = new CSG.Path2D(points, false).expandToCAG(r, CSG.defaultResolution2D) return tmpObj }, path // paths are a lot more complex, handled seperatly , see below } return types[obj.type](obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups) } module.exports = shapesMap function path (obj, svgUnitsPmm, svgUnitsX, svgUnitsY, svgUnitsV, svgGroups) { let pathCag = new CAG() let paths = {} const on = '' // not sure let r = cssPxUnit // default if ('strokeWidth' in obj) { r = cagLengthP(obj.strokeWidth, svgUnitsPmm, svgUnitsV) / 2 } else { const v = groupValue(svgGroups, 'strokeWidth') if (v !== null) { r = cagLengthP(v, svgUnitsPmm, svgUnitsV) / 2 } } // Note: All values are SVG values let sx = 0 // starting position let sy = 0 let cx = 0 // current position let cy = 0 let pi = 0 // current path index let pathName = on + pi // current path name let pc = false // current path closed let bx = 0 // 2nd control point from previous C command let by = 0 // 2nd control point from previous C command let qx = 0 // 2nd control point from previous Q command let qy = 0 // 2nd control point from previous Q command for (let j = 0; j < obj.commands.length; j++) { let co = obj.commands[j] let pts = co.p // console.log('postion: ['+cx+','+cy+'] before '+co.c); switch (co.c) { case 'm': // relative move to X,Y // special case, if at beginning of path then treat like absolute M if (j === 0) { cx = 0; cy = 0 } // close the previous path if (pi > 0 && pc === false) { paths[pathName].expandToCAG(CSG.defaultResolution2D) // code += indent + pathName + ' = ' + pathName + '.expandToCAG(' + r + ',CSG.defaultResolution2D);\n' } // open a new path if (pts.length >= 2) { cx = cx + parseFloat(pts.shift()) cy = cy + parseFloat(pts.shift()) pi++ pathName = on + pi pc = false paths[pathName] = new CSG.Path2D([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], false) sx = cx; sy = cy } // optional implicit relative lineTo (cf SVG spec 8.3.2) while (pts.length >= 2) { cx = cx + parseFloat(pts.shift()) cy = cy + parseFloat(pts.shift()) paths[pathName].appendPoint([svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]) } break case 'M': // absolute move to X,Y // close the previous path if (pi > 0 && pc === false) { paths[pathName] = paths[pathName].expandToCAG(CSG.defaultResolution2D) } // open a new path if (pts.length >= 2) { cx = parseFloat(pts.shift()) cy = parseFloat(pts.shift()) pi++ pathName = on + pi pc = false paths[pathName] = new CSG.Path2D([[svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]], false) sx = cx; sy = cy } // optional implicit absolute lineTo (cf SVG spec 8.3.2) while (pts.length >= 2) { cx = parseFloat(pts.shift()) cy = parseFloat(pts.shift()) paths[pathName].appendPoint([svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]) } break case 'a': // relative elliptical arc while (pts.length >= 7) { let rx = parseFloat(pts.shift()) let ry = parseFloat(pts.shift()) let ro = 0 - parseFloat(pts.shift()) let lf = (pts.shift() === '1') let sf = (pts.shift() === '1') cx = cx + parseFloat(pts.shift()) cy = cy + parseFloat(pts.shift()) paths[pathName].appendArc([svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)], {xradius: svg2cagX(rx, svgUnitsPmm), yradius: svg2cagY(ry, svgUnitsPmm), xaxisrotation: ro, clockwise: sf, large: lf}) } break case 'A': // absolute elliptical arc while (pts.length >= 7) { let rx = parseFloat(pts.shift()) let ry = parseFloat(pts.shift()) let ro = 0 - parseFloat(pts.shift()) let lf = (pts.shift() === '1') let sf = (pts.shift() === '1') cx = parseFloat(pts.shift()) cy = parseFloat(pts.shift()) paths[pathName].appendArc([svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)], {xradius: svg2cagX(rx, svgUnitsPmm), yradius: svg2cagY(ry, svgUnitsPmm), xaxisrotation: ro, clockwise: sf, large: lf}) } break case 'c': // relative cubic Bézier while (pts.length >= 6) { let x1 = cx + parseFloat(pts.shift()) let y1 = cy + parseFloat(pts.shift()) bx = cx + parseFloat(pts.shift()) by = cy + parseFloat(pts.shift()) cx = cx + parseFloat(pts.shift()) cy = cy + parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendBezier([[svg2cagX(x1, svgUnitsPmm), svg2cagY(y1, svgUnitsPmm)], [svg2cagX(bx, svgUnitsPmm), svg2cagY(by, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]]) let rf = reflect(bx, by, cx, cy) bx = rf[0] by = rf[1] } break case 'C': // absolute cubic Bézier while (pts.length >= 6) { let x1 = parseFloat(pts.shift()) let y1 = parseFloat(pts.shift()) bx = parseFloat(pts.shift()) by = parseFloat(pts.shift()) cx = parseFloat(pts.shift()) cy = parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendBezier([[svg2cagX(x1, svgUnitsPmm), svg2cagY(y1, svgUnitsPmm)], [svg2cagX(bx, svgUnitsPmm), svg2cagY(by, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]]) let rf = reflect(bx, by, cx, cy) bx = rf[0] by = rf[1] } break case 'q': // relative quadratic Bézier while (pts.length >= 4) { qx = cx + parseFloat(pts.shift()) qy = cy + parseFloat(pts.shift()) cx = cx + parseFloat(pts.shift()) cy = cy + parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendBezier([[svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]]) let rf = reflect(qx, qy, cx, cy) qx = rf[0] qy = rf[1] } break case 'Q': // absolute quadratic Bézier while (pts.length >= 4) { qx = parseFloat(pts.shift()) qy = parseFloat(pts.shift()) cx = parseFloat(pts.shift()) cy = parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendBezier([[svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]]) let rf = reflect(qx, qy, cx, cy) qx = rf[0] qy = rf[1] } break case 't': // relative quadratic Bézier shorthand while (pts.length >= 2) { cx = cx + parseFloat(pts.shift()) cy = cy + parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendBezier([[svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [cx, cy]]) let rf = reflect(qx, qy, cx, cy) qx = rf[0] qy = rf[1] } break case 'T': // absolute quadratic Bézier shorthand while (pts.length >= 2) { cx = parseFloat(pts.shift()) cy = parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendBezier([[svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(qx, svgUnitsPmm), svg2cagY(qy, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]]) let rf = reflect(qx, qy, cx, cy) qx = rf[0] qy = rf[1] } break case 's': // relative cubic Bézier shorthand while (pts.length >= 4) { let x1 = bx // reflection of 2nd control point from previous C let y1 = by // reflection of 2nd control point from previous C bx = cx + parseFloat(pts.shift()) by = cy + parseFloat(pts.shift()) cx = cx + parseFloat(pts.shift()) cy = cy + parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendBezier([[svg2cagX(x1, svgUnitsPmm), svg2cagY(y1, svgUnitsPmm)], [svg2cagX(bx, svgUnitsPmm), svg2cagY(by, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]]) let rf = reflect(bx, by, cx, cy) bx = rf[0] by = rf[1] } break case 'S': // absolute cubic Bézier shorthand while (pts.length >= 4) { let x1 = bx // reflection of 2nd control point from previous C let y1 = by // reflection of 2nd control point from previous C bx = parseFloat(pts.shift()) by = parseFloat(pts.shift()) cx = parseFloat(pts.shift()) cy = parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendBezier([[svg2cagX(x1, svgUnitsPmm), svg2cagY(y1, svgUnitsPmm)], [svg2cagX(bx, svgUnitsPmm), svg2cagY(by, svgUnitsPmm)], [svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]]) let rf = reflect(bx, by, cx, cy) bx = rf[0] by = rf[1] } break case 'h': // relative Horzontal line to while (pts.length >= 1) { cx = cx + parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendPoint([svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]) } break case 'H': // absolute Horzontal line to while (pts.length >= 1) { cx = parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendPoint([svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]) } break case 'l': // relative line to while (pts.length >= 2) { cx = cx + parseFloat(pts.shift()) cy = cy + parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendPoint([svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]) } break case 'L': // absolute line to while (pts.length >= 2) { cx = parseFloat(pts.shift()) cy = parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendPoint([svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]) } break case 'v': // relative Vertical line to while (pts.length >= 1) { cy = cy + parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendPoint([svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]) } break case 'V': // absolute Vertical line to while (pts.length >= 1) { cy = parseFloat(pts.shift()) paths[pathName] = paths[pathName].appendPoint([svg2cagX(cx, svgUnitsPmm), svg2cagY(cy, svgUnitsPmm)]) } break case 'z': // close current line case 'Z': paths[pathName] = paths[pathName].close().innerToCAG() pathCag = pathCag.union(paths[pathName]) cx = sx cy = sy // return to the starting point pc = true break default: console.log('Warning: Unknow PATH command [' + co.c + ']') break } // console.log('postion: ['+cx+','+cy+'] after '+co.c); } if (pi > 0 && pc === false) { paths[pathName] = paths[pathName].expandToCAG(r, CSG.defaultResolution2D) pathCag = pathCag.union(paths[pathName]) } return pathCag }