base/asset/SVGAsset.js

/*
 * Copyright 2020 WICKLETS LLC
 *
 * This file is part of Wick Engine.
 *
 * Wick Engine is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Wick Engine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Wick Engine.  If not, see <https://www.gnu.org/licenses/>.
 */

Wick.SVGAsset = class extends Wick.FileAsset {
    /**
     * Returns all valid MIME types for files which can be converted to SVGAssets.
     * @return {string[]} Array of strings of MIME types in the form MediaType/Subtype.
     */
    static getValidMIMETypes() {
        return ['image/svg+xml'];
    }

    /**
     * Returns all valid extensions types for files which can be attempted to be
     * converted to SVGAssets.
     * @return  {string[]} Array of strings representing extensions.
     */
    static getValidExtensions() {
        return ['.svg']
    }

    /**
     * Create a new SVGAsset.
     * @param {object} args
     */
    constructor(args) {
        super(args);
    }

    _serialize(args) {
        var data = super._serialize(args);
        return data;
    }

    _deserialize(data) {
        super._deserialize(data);
    }

    get classname() {
        return 'SVGAsset';
    }

    /**
     * A list of Wick Paths, Clips and Layers that use this SVGAsset as their image source.
     * I think this should return Assets not Paths
     * @returns {Wick.Path[]}
     */
    getInstances() {
        return []; // TODO
    }

    /**
     * Check if there are any objects in the project that use this asset.
     * @returns {boolean}
     */
    hasInstances() {
        return false;
    }

    /**
     * Removes all Items using this asset as their source from the project.
     * @returns {boolean}
     */
    removeAllInstances() {
        // TODO
    }

    /**
     * Load data in the asset
     */
    load(callback) {
        // We don't need to do anything here, the data for SVGAssets is just SVG
        callback();
    }

    /**
     * Walks through the items tree creating the apprptiate wick object for each node*
     * @param {paper.Item} item - called when the Path is done loading.
     * @returns {Wick.Base}
     */
    static walkItems(item) {
            // create paths for all the path items, this also needs to be done for the following item.className=:
            // 'Group', 'Layer', 'Path', 'CompoundPath', 'Shape', 'Raster', 'SymbolItem', 'PointText'
            // I think path automatically handles this, but maybe not layer or group
            var wickItem = null; // Groups (clips) and layers do this differently so they must be handled separately

            if (item instanceof paper.Layer || (item.name !== null && item.name.startsWith("layer") && item instanceof paper.Group)) {
                wickItem = new Wick.Layer(); // If we've just added a layer set it to be the active layer
                //TODO: Find out how multiple layers are handled

                var frame = new Wick.Frame();
                wickItem.addFrame(frame);
                var groupChildren = Array.from(item.children); //prevent any side effects
                groupChildren.forEach(childItem => {
                    var wickChildItem = Wick.SVGAsset.walkItems(childItem).copy();

                    if (wickChildItem instanceof Wick.Clip) {
                        frame.addClip(wickChildItem);
                    } else if (wickChildItem instanceof Wick.Path) {
                        frame.addPath(wickChildItem);
                    } else if (wickChildItem instanceof Wick.Layer) {
                        frame.addLayer(wickChildItem);
                        //console.error("SVG Import: Error importing, nested layers.ignoring."); // Insert text
                    } else {
                        console.error("SVG Import: Unknown item type.".concat(wickChildItem.classname)); // Insert text
                    }
                });
            } else if (item instanceof paper.Group) {
                wickItem = new Wick.Clip();
                var wickObjects = [];
                var layers = [];
                var groupChildren = Array.from(item.children); //prevent any side effects
                groupChildren.forEach(childItem => {
                    var clipActiveLayer = wickItem.activeLayer;
                    ///This should be clips and paths not layers
                    var walkItem = Wick.SVGAsset.walkItems(childItem).copy();

                    if (walkItem instanceof Wick.Layer) {
                        //console.error("SVG Import: Clip has a child that is a layer, this should never happen. ignoring."); // Insert text
                        layers.push(walkItem);
                        clipActiveLayer.activate();
                    } else {
                        wickObjects.push(walkItem);
                    }
                });
                wickItem.addObjects(wickObjects); //add the items to the project
                // add layers after onjects so the objexts don't get bound to the new layer
                var layersCopy = Array.from(layers); //prevent any side effects
                layersCopy.forEach(layer => {
                    wickItem.timeline.addLayer(layer);
                });
            } else if (item instanceof paper.Shape) {
                //console.error("SVG Import: Item is an instance of a shape. This should never happen as all shapes should be converted to paths when we call paperProject.importSVG(data, options.expandShapes = true);");
                wickItem = new Wick.Path({ //json: item.clone().toPath().exportJSON()
                });
            } else {
                //'Path', 'CompoundPath', 'Raster', 'SymbolItem', 'PointText' all handled by Path which takes the loaded paper object expressed as JSON to load
                wickItem = new Wick.Path({
                    json: item.exportJSON()
                });
            }

            return wickItem;
        }
        /**
         * Walks through the items tree creating the appropriate wick object for each node
         * @param {Paper.Item} item - the item to turn into paths
         */

    /**
     * Walks through the items tree converting shapes into paths. This should be possible to do in the walkitems routine
     * @param {Paper.Item} item - called when the Path is done loading.
     */
    static _breakAppartShapesRecursively(item) {
        item.applyMatrix = true;
        if (item instanceof paper.Group || item instanceof paper.Layer) {
            var children = Array.from(item.children);
            children.forEach(childItem => {
                Wick.SVGAsset._breakAppartShapesRecursively(childItem);
            })
        } else if (item instanceof paper.Shape) { //This should have been done automatically by the import options, spo shouldn't be needed
            var path = item.toPath();
            //item.parent.addChild(path);
            //path.insertAbove(item);
            //item.remove();
            item.replaceWith(path);
        }
    }


    /**
     * Creates a new Wick SVG that uses this asset's data.
     * @param {function} callback - called when the SVG is done loading.
     */


    createInstance(callback) {
        // needs to take a base64 encoded string.
        //we need a viewSVG and an SVG object that extends base by the looks of things.

        /*
                var myPath = new paper.Path();
                myPath.strokeColor = 'black';
                myPath.add(new paper.Point(0, 0));
                myPath.add(new paper.Point(100, 50));
                var anItem = this.walkItems(myPath);
                this.project.addObject(anItem);
                var myLayer = new paper.Layer();
                var secondPath = new paper.Path.Circle(new paper.Point(150, 50), 35);
                secondPath.fillColor = 'green';
                var aLayer = this.walkItems(myLayer);
                this.project.addObject(aLayer);
                    // Create two circle shaped paths:
                var firstPath = new paper.Path.Circle(new paper.Point(80, 50), 35);
                var secondPath = new paper.Path.Circle(new paper.Point(120, 50), 35);
                var group = new paper.Group([firstPath, secondPath]);
                // Change the fill color of the items contained within the group:
                group.style = {
                    fillColor: 'red',
                    strokeColor: 'black'
                };
                var agroup = this.walkItems(group);
                this.project.addObject(agroup);
          */
        var importSVG = function(data) {
            var item = paper.project.importSVG(data, {
                expandShapes: true,
                insert: false
            });
            Wick.SVGAsset._breakAppartShapesRecursively(item);
            var wickItem = Wick.SVGAsset.walkItems(item).copy();
            callback(wickItem);
        };

        Wick.SVGFile.fromSVGFile(this.src, importSVG);
    }

};