import { Box2, BufferGeometry, FileLoader, Float32BufferAttribute, Loader, Matrix3, Path, Shape, ShapePath, ShapeUtils, SRGBColorSpace, Vector2, Vector3 } from 'three'; const COLOR_SPACE_SVG = SRGBColorSpace; /** * A loader for the SVG format. * * Scalable Vector Graphics is an XML-based vector image format for two-dimensional graphics * with support for interactivity and animation. * * ```js * const loader = new SVGLoader(); * const data = await loader.loadAsync( 'data/svgSample.svg' ); * * const paths = data.paths; * const group = new THREE.Group(); * * for ( let i = 0; i < paths.length; i ++ ) { * * const path = paths[ i ]; * const material = new THREE.MeshBasicMaterial( { * color: path.color, * side: THREE.DoubleSide, * depthWrite: false * } ); * * const shapes = SVGLoader.createShapes( path ); * * for ( let j = 0; j < shapes.length; j ++ ) { * * const shape = shapes[ j ]; * const geometry = new THREE.ShapeGeometry( shape ); * const mesh = new THREE.Mesh( geometry, material ); * group.add( mesh ); * * } * * } * * scene.add( group ); * ``` * * @augments Loader * @three_import import { SVGLoader } from 'three/addons/loaders/SVGLoader.js'; */ class SVGLoader extends Loader { /** * Constructs a new SVG loader. * * @param {LoadingManager} [manager] - The loading manager. */ constructor( manager ) { super( manager ); /** * Default dots per inch. * * @type {number} * @default 90 */ this.defaultDPI = 90; /** * Default unit. * * @type {('mm'|'cm'|'in'|'pt'|'pc'|'px')} * @default 'px' */ this.defaultUnit = 'px'; } /** * Starts loading from the given URL and passes the loaded SVG asset * to the `onLoad()` callback. * * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. * @param {function({paths:Array,xml:string})} onLoad - Executed when the loading process has been finished. * @param {onProgressCallback} onProgress - Executed while the loading is in progress. * @param {onErrorCallback} onError - Executed when errors occur. */ load( url, onLoad, onProgress, onError ) { const scope = this; const loader = new FileLoader( scope.manager ); loader.setPath( scope.path ); loader.setRequestHeader( scope.requestHeader ); loader.setWithCredentials( scope.withCredentials ); loader.load( url, function ( text ) { try { onLoad( scope.parse( text ) ); } catch ( e ) { if ( onError ) { onError( e ); } else { console.error( e ); } scope.manager.itemError( url ); } }, onProgress, onError ); } /** * Parses the given SVG data and returns the resulting data. * * @param {string} text - The raw SVG data as a string. * @return {{paths:Array,xml:string}} An object holding an array of shape paths and the * SVG XML document. */ parse( text ) { const scope = this; function parseNode( node, style ) { if ( node.nodeType !== 1 ) return; const transform = getNodeTransform( node ); let isDefsNode = false; let path = null; switch ( node.nodeName ) { case 'svg': style = parseStyle( node, style ); break; case 'style': parseCSSStylesheet( node ); break; case 'g': style = parseStyle( node, style ); break; case 'path': style = parseStyle( node, style ); if ( node.hasAttribute( 'd' ) ) path = parsePathNode( node ); break; case 'rect': style = parseStyle( node, style ); path = parseRectNode( node ); break; case 'polygon': style = parseStyle( node, style ); path = parsePolygonNode( node ); break; case 'polyline': style = parseStyle( node, style ); path = parsePolylineNode( node ); break; case 'circle': style = parseStyle( node, style ); path = parseCircleNode( node ); break; case 'ellipse': style = parseStyle( node, style ); path = parseEllipseNode( node ); break; case 'line': style = parseStyle( node, style ); path = parseLineNode( node ); break; case 'defs': isDefsNode = true; break; case 'use': style = parseStyle( node, style ); const href = node.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ) || ''; const usedNodeId = href.substring( 1 ); const usedNode = node.viewportElement.getElementById( usedNodeId ); if ( usedNode ) { parseNode( usedNode, style ); } else { console.warn( 'SVGLoader: \'use node\' references non-existent node id: ' + usedNodeId ); } break; default: // console.log( node ); } if ( path ) { if ( style.fill !== undefined && style.fill !== 'none' ) { path.color.setStyle( style.fill, COLOR_SPACE_SVG ); } transformPath( path, currentTransform ); paths.push( path ); path.userData = { node: node, style: style }; } const childNodes = node.childNodes; for ( let i = 0; i < childNodes.length; i ++ ) { const node = childNodes[ i ]; if ( isDefsNode && node.nodeName !== 'style' && node.nodeName !== 'defs' ) { // Ignore everything in defs except CSS style definitions // and nested defs, because it is OK by the standard to have //