This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/svg-deserializer/index.js

353 lines
10 KiB
JavaScript

/*
## License
Copyright (c) 2016 Z3 Development https://github.com/z3dev
2017 Mark 'kaosat-dev' Moissette
* The upgrades (direct CSG output from this deserializer) and refactoring
have been very kindly sponsored by [Copenhagen Fabrication / Stykka](https://www.stykka.com/)***
All code released under MIT license
*/
const sax = require('sax')
const {CAG} = require('@jscad/csg')
const {cagLengthX, cagLengthY} = require('./helpers')
const {svgSvg, svgRect, svgCircle, svgGroup, svgLine, svgPath, svgEllipse, svgPolygon, svgPolyline, svgUse} = require('./svgElementHelpers')
const shapesMapCsg = require('./shapesMapCsg')
const shapesMapJscad = require('./shapesMapJscad')
// FIXE: should these be kept here ? any risk of side effects ?
let svgUnitsX
let svgUnitsY
let svgUnitsV
// processing controls
let svgObjects = [] // named objects
let svgGroups = [] // groups of objects
let svgInDefs = false // svg DEFS element in process
let svgObj = null // svg in object form
let svgUnitsPmm = [1, 1]
const objectify = function (group) {
const level = svgGroups.length
// add this group to the heiarchy
svgGroups.push(group)
// create an indent for the generated code
let i = level
while (i > 0) {
i--
}
let lnCAG = new CAG()
const params = {
svgUnitsPmm,
svgUnitsX,
svgUnitsY,
svgUnitsV,
level,
svgGroups
}
// generate code for all objects
for (i = 0; i < group.objects.length; i++) {
const obj = group.objects[i]
let onCAG = shapesMapCsg(obj, objectify, params)
if ('fill' in obj) {
// FIXME when CAG supports color
// code += indent+on+' = '+on+'.setColor(['+obj.fill[0]+','+obj.fill[1]+','+obj.fill[2]+']);\n';
}
if ('transforms' in obj) {
// NOTE: SVG specifications require that transforms are applied in the order given.
// But these are applied in the order as required by CSG/CAG
let tr
let ts
let tt
for (let j = 0; j < obj.transforms.length; j++) {
const t = obj.transforms[j]
if ('rotate' in t) { tr = t }
if ('scale' in t) { ts = t }
if ('translate' in t) { tt = t }
}
if (ts !== null) {
const x = ts.scale[0]
const y = ts.scale[1]
onCAG = onCAG.scale([x, y])
}
if (tr !== null) {
const z = 0 - tr.rotate
onCAG = onCAG.rotateZ(z)
}
if (tt !== null) {
const x = cagLengthX(tt.translate[0], svgUnitsPmm, svgUnitsX)
const y = (0 - cagLengthY(tt.translate[1], svgUnitsPmm, svgUnitsY))
onCAG = onCAG.translate([x, y])
}
}
lnCAG = lnCAG.union(onCAG)
}
// remove this group from the hiearchy
svgGroups.pop()
return lnCAG
}
const codify = function (group) {
const level = svgGroups.length
// add this group to the heiarchy
svgGroups.push(group)
// create an indent for the generated code
var indent = ' '
var i = level
while (i > 0) {
indent += ' '
i--
}
// pre-code
var code = ''
if (level === 0) {
code += 'function main(params) {\n'
}
var ln = 'cag' + level
code += indent + 'var ' + ln + ' = new CAG();\n'
// generate code for all objects
for (i = 0; i < group.objects.length; i++) {
const obj = group.objects[i]
const on = ln + i
const params = {
level,
indent,
ln,
on,
svgUnitsPmm,
svgUnitsX,
svgUnitsY,
svgUnitsV,
svgGroups
}
let tmpCode = shapesMapJscad(obj, codify, params)
code += tmpCode
if ('fill' in obj) {
// FIXME when CAG supports color
// code += indent+on+' = '+on+'.setColor(['+obj.fill[0]+','+obj.fill[1]+','+obj.fill[2]+']);\n';
}
if ('transforms' in obj) {
// NOTE: SVG specifications require that transforms are applied in the order given.
// But these are applied in the order as required by CSG/CAG
let tr
let ts
let tt
for (let j = 0; j < obj.transforms.length; j++) {
var t = obj.transforms[j]
if ('rotate' in t) { tr = t }
if ('scale' in t) { ts = t }
if ('translate' in t) { tt = t }
}
if (ts !== null && ts !== undefined) {
const x = ts.scale[0]
const y = ts.scale[1]
code += indent + on + ' = ' + on + '.scale([' + x + ',' + y + ']);\n'
}
if (tr !== null && tr !== undefined) {
console.log('tr', tr)
const z = 0 - tr.rotate
code += indent + on + ' = ' + on + '.rotateZ(' + z + ');\n'
}
if (tt !== null && tt !== undefined) {
const x = cagLengthX(tt.translate[0], svgUnitsPmm, svgUnitsX)
const y = (0 - cagLengthY(tt.translate[1], svgUnitsPmm, svgUnitsY))
code += indent + on + ' = ' + on + '.translate([' + x + ',' + y + ']);\n'
}
}
code += indent + ln + ' = ' + ln + '.union(' + on + ');\n'
}
// post-code
if (level === 0) {
code += indent + 'return ' + ln + ';\n'
code += '}\n'
}
// remove this group from the hiearchy
svgGroups.pop()
return code
}
function createSvgParser (src, pxPmm) {
// create a parser for the XML
const parser = sax.parser(false, {trim: true, lowercase: false, position: true})
if (pxPmm !== undefined && pxPmm > parser.pxPmm) {
parser.pxPmm = pxPmm
}
// extend the parser with functions
parser.onerror = e => console.log('error: line ' + e.line + ', column ' + e.column + ', bad character [' + e.c + ']')
parser.onopentag = function (node) {
// console.log('opentag: '+node.name+' at line '+this.line+' position '+this.column);
const objMap = {
SVG: svgSvg,
G: svgGroup,
RECT: svgRect,
CIRCLE: svgCircle,
ELLIPSE: svgEllipse,
LINE: svgLine,
POLYLINE: svgPolyline,
POLYGON: svgPolygon,
PATH: svgPath,
USE: svgUse,
DEFS: () => { svgInDefs = true },
DESC: () => undefined, // ignored by design
TITLE: () => undefined, // ignored by design
STYLE: () => undefined, // ignored by design
undefined: () => console.log('Warning: Unsupported SVG element: ' + node.name)
}
let obj = objMap[node.name] ? objMap[node.name](node.attributes, {svgObjects, customPxPmm: pxPmm}) : null
// case 'SYMBOL':
// this is just like an embedded SVG but does NOT render directly, only named
// this requires another set of control objects
// only add to named objects for later USE
// break;
// console.log('node',node)
if (obj !== null) {
// add to named objects if necessary
if ('id' in obj) {
svgObjects[obj.id] = obj
}
if (obj.type === 'svg') {
// initial SVG (group)
svgGroups.push(obj)
// console.log('units', obj.unitsPmm)
svgUnitsPmm = obj.unitsPmm
svgUnitsX = obj.viewW
svgUnitsY = obj.viewH
svgUnitsV = obj.viewP
} else {
// add the object to the active group if necessary
if (svgGroups.length > 0 && svgInDefs === false) {
var group = svgGroups.pop()
if ('objects' in group) {
// console.log('push object ['+obj.type+']');
// console.log(JSON.stringify(obj));
// TBD apply presentation attributes from the group
group.objects.push(obj)
}
svgGroups.push(group)
}
if (obj.type === 'group') {
// add GROUPs to the stack
svgGroups.push(obj)
}
}
}
}
parser.onclosetag = function (node) {
// console.log('closetag: '+node)
const popGroup = () => svgGroups.pop()
const objMap = {
SVG: popGroup,
DEFS: () => { svgInDefs = false },
USE: popGroup,
G: popGroup,
undefined: () => {}
}
const obj = objMap[node] ? objMap[node]() : undefined
// check for completeness
if (svgGroups.length === 0) {
svgObj = obj
}
}
// parser.onattribute = function (attr) {};
// parser.ontext = function (t) {};
parser.onend = function () {
// console.log('SVG parsing completed')
}
// start the parser
parser.write(src).close()
return parser
}
/**
* Parse the given SVG source and return a JSCAD script
* @param {string} src svg data as text
* @param {string} filename (optional) original filename of SVG source
* @param {object} options options (optional) anonymous object with:
* pxPmm {number: pixels per milimeter for calcuations
* version: {string} version number to add to the metadata
* addMetadata: {boolean} flag to enable/disable injection of metadata (producer, date, source)
*
* @return a CAG (2D CSG) object
*/
function deserializeToCSG (src, filename, options) {
filename = filename || 'svg'
const defaults = {pxPmm: require('./constants').pxPmm, version: '0.0.0', addMetaData: true}
options = Object.assign({}, defaults, options)
const {pxPmm} = options
// parse the SVG source
createSvgParser(src, pxPmm)
if (!svgObj) {
throw new Error('SVG parsing failed, no valid svg data retrieved')
}
return objectify(svgObj)
}
/**
* Parse the given SVG source and return a JSCAD script
* @param {string} src svg data as text
* @param {string} filename (optional) original filename of SVG source
* @param {object} options options (optional) anonymous object with:
* pxPmm {number: pixels per milimeter for calcuations
* version: {string} version number to add to the metadata
* addMetadata: {boolean} flag to enable/disable injection of metadata (producer, date, source)
* at the start of the file
* @return a CAG (2D CSG) object
*/
function translate (src, filename, options) {
filename = filename || 'svg'
const defaults = {pxPmm: require('./constants').pxPmm, version: '0.0.0', addMetaData: true}
options = Object.assign({}, defaults, options)
const {version, pxPmm, addMetaData} = options
// parse the SVG source
createSvgParser(src, pxPmm)
// convert the internal objects to JSCAD code
let code = addMetaData ? `//
// producer: OpenJSCAD.org ${version} SVG Importer
// date: ${new Date()}
// source: ${filename}
//
` : ''
if (!svgObj) {
throw new Error('SVG parsing failed, no valid svg data retrieved')
}
const scadCode = codify(svgObj)
code += scadCode
return code
}
const deserialize = function (src, filename, options) {
const defaults = {
output: 'jscad'
}
options = Object.assign({}, defaults, options)
return options.output === 'jscad' ? translate(src, filename, options) : deserializeToCSG(src, filename, options)
}
module.exports = {deserialize}