throrin19/svgutils

View on GitHub
libs/objects/polygon.js

Summary

Maintainability
F
3 days
Test Coverage
'use strict';

var Matrix      = require(__dirname + '/../matrix/extends'),
    SvgObject   = require(__dirname + '/svgobject'),
    utils       = require(__dirname + '/../matrix/utils'),
    _           = require('underscore'),
    nUtil       = require('util');

var EPSILON = 0.1;

var Polygon = function() {
    if (!(this instanceof Polygon)) {
        throw 'this function in a constructor. Use new to call it';
    }

    SvgObject.call(this);
    this.type   = "polygon";
    this.points = [];
};

nUtil.inherits(Polygon, SvgObject);

/**
 * Get Polygon points in Array to simply manipulation
 *
 * @param {string}    points                    Polygon|Polyline points attribute value
 */
Polygon.prototype.setPointsFromString = function setPointsFromString(points) {
    var coords          = [],
        point           = {},
        previousPoint   = {};

    points = points.replace(/ +(?= )/g, '');
    _.each(points.split(/[, ]/), function (xy, index) {
        if (index%2 == 0) {
            point.x = xy;
        } else {
            point.y = xy;
        }

        if (index%2 == 1 && index > 0 && (point.x !== previousPoint.x || point.y !== previousPoint.y)) {
            coords.push(point);
            previousPoint = point;
            point = {};
        }
    });

    this.points = coords;
    this.bbox   = undefined;
};

Polygon.prototype.addPoint = function addPoint(x, y) {
    var different = true;
    if (this.points.length > 0) {
        var lastPoint = this.points[this.points.length-1];
        different = lastPoint.x !== x || lastPoint.y !== y;
    }
    if (different) {
        this.points.push({ x : x, y : y });
    }
    this.bbox = undefined;
};

/**
 * Return JSON from object
 * @param   {boolean}    [matrix]       return transform attribute if false.
 * @returns {object}                    JSON Object
 */
Polygon.prototype.toJSON = function toJSON(matrix) {
    var parentJSON = SvgObject.prototype.toJSON.call(this, matrix);

    parentJSON.type     = this.type;
    parentJSON.points   = this.points;

    return parentJSON;
};

/**
 * Return XML from object
 * @param   {boolean}    [matrix]       return transform attribute if false.
 * @returns {xmlBuilder}                XML Object
 */
Polygon.prototype.toXml = function toXml(matrix) {

    var xml = SvgObject.prototype.toXml.call(this, matrix);

    var points = "";
    _.each(this.points, function (point) {
       points += point.x + "," + point.y + " ";
    });

    xml.att('points', points.substr(0, points.length-1));

    return xml;
};

/**
 * Return element converted into Path.
 * @return {Path}                           Path Object
 */
Polygon.prototype.toPath = function toPath() {
    var path = SvgObject.prototype.toPath.call(this);

    path.d =  "";
    this.points.forEach(function(point, index) {
        if(index == 0){
            path.d += "M " + point.x + " " + point.y;
        } else {
            path.d += " L" + point.x + " " + point.y
        }
    });
    path.d += ' Z';

    return path;
};

Polygon.prototype.applyMatrix = function applyMatrix(matrix, callback) {
    var polygon     = new Polygon();
    polygon.style   = this.style;
    polygon.classes = this.classes;
    polygon.id      = this.id;
    polygon.name    = this.name;
    polygon.stroke  = this.stroke;
    polygon.fill    = this.fill;
    polygon.type    = this.type;
    polygon.data    = this.data;

    _.each(this.points, function (point) {
        polygon.addPoint(
            matrix.x(point.x, point.y),
            matrix.y(point.x, point.y)
        );
    });

    callback(polygon);
};

/**
 * Get the element Bounding Box
 * @param {function} callback               Callback Function
 */
Polygon.prototype.getBBox = function getBBox(callback) {
    var minX = +Infinity,
        maxX = -Infinity,
        minY = +Infinity,
        maxY = -Infinity;

    _.each(this.points, function (point) {
        minX = Math.min(point.x, minX);
        maxX = Math.max(point.x, maxX);
        minY = Math.min(point.y, minY);
        maxY = Math.max(point.y, maxY);
    });


    this.bbox = utils.bbox(minX, minY, Math.abs(Math.abs(maxX) - Math.abs(minX)), Math.abs(Math.abs(maxY) - Math.abs(minY)));
    callback(this.bbox);
};

/**
 * Get the element innerBox
 * @param {function} callback               Callback function
 */
Polygon.prototype.getInnerBox = function getInnerBox(callback) {
    var verticesY       = [],
        pointsCount     = this.points.length,
        segments        = [],
        prevY           = Infinity,
        innerRect       = {
            x       : 0,
            y       : 0,
            width   : 0,
            height  : 0
        },
        segment;

    if (pointsCount === 0) {
        callback(innerRect);
        return;
    }

    _.each(this.points, function (point) {
        verticesY.push(point.y);
    }, this);
    verticesY = _.sortBy(verticesY, function (y) {
        return y;
    });
    _.each(verticesY, function (y, i) {
        if (Math.abs(y - prevY) < EPSILON) {
            return;
        }
        if (i > 0) {
            segment = this._widestSegmentAtY(y-0.1);
            if (segment.width > 0) {
                segments.push(segment);
            }
        }
        if (i < pointsCount-1) {
            segment = this._widestSegmentAtY(y+0.1);
            if (segment.width > 0) {
                segments.push(segment);
            }
        }

        prevY = y;
    }, this);

    if (segments.length > 1) {
        var iSeg0   = 0,
            iSeg1   = 0,
            curRect = innerRect;

        for (iSeg0 = 0, iSeg1 = 1; iSeg1 < segments.length; iSeg0 += 2, iSeg1 += 2) {
            var segment0 = segments[iSeg0],
                segment1 = segments[iSeg1];

            if (Math.abs(segment0.width - segment1.width) < EPSILON) {
                var x0      = Math.max(segment0.x, segment1.x),
                    x1      = Math.min(segment0.x + segment0.width, segment1.x + segment1.width),
                    width   = x1 - x0;
                curRect = {
                    x : x0,
                    y : segment0.y,
                    width : width,
                    height : segment1.y - segment0.y
                };
            } else {
                var point0, point1;

                if (segment1.width > segment0.width) {
                    point0 = {
                        x : 0.5 * (segment0.x + segment1.x),
                        y : 0.5 * (segment0.y + segment1.y)
                    };
                    point1 = {
                        x : 0.5 * (segment0.x + segment0.width + segment1.x + segment1.width),
                        y : 0.5 * (segment0.y + segment0.width + segment1.y + segment1.height)
                    };
                    curRect = {
                        x : point0.x,
                        y : point0.y,
                        width : point1.x - point0.x,
                        height : segment1.y - point0.y
                    }
                } else {
                    point0 = {
                        x : 0.5 * (segment0.x + segment1.x),
                        y : 0.5 * (segment0.y + segment1.y)
                    };
                    point1 = {
                        x : 0.5 * (segment0.x + segment0.width + segment1.x + segment1.width),
                        y : 0.5 * (segment0.y + segment0.width + segment1.y + segment1.height)
                    };
                    curRect = {
                        x : point0.x,
                        y : segment0.y,
                        width : point1.x - point0.x,
                        height : point0.y - segment0.y
                    }
                }
            }

            if (
                curRect.width > innerRect.width ||
                (curRect.width === innerRect.width && curRect.height > innerRect.height)
            ) {
                innerRect = curRect;
            }
        }
    }

    callback(innerRect);
};

/**
 * Get segment at specific Y coordinate
 * @param {number} y            Y coordinate
 * @returns {{x: number, y: number, width: number}}
 * @private
 */
Polygon.prototype._widestSegmentAtY = function _widestSegmentAtY(y) {
    var segment = {
            x : 0,
            y : y,
            width : 0
        },
        pointsCount = this.points.length,
        xArray      = [],
        i, j;

    if (pointsCount < 3) {
        return segment;
    }

    // compute all the intersections (x coordinates)
    for (i = 0,  j = pointsCount-1; i < pointsCount; j = i++) {
        var point1 = this.points[i],
            point2 = this.points[j];
        if ((point1.y > y) != (point2.y > y)) {
            if (Math.abs(point2.x - point1.x) < EPSILON) {
                xArray.push(point1.x);
            } else {
                // y = a x + b
                var a = (point2.y - point1.y)/(point2.x - point1.x),
                    b = point2.y - a * point2.x,
                    x = (y - b)/a;
                if (x >= Math.min(point2.x, point1.x) && x <= Math.max(point2.x, point1.x)) {
                    xArray.push(x);
                }
            }
        }
    }

    xArray = _.sortBy(xArray, function (x) {
        return x;
    });

    for (i = 0, j = 1; j < xArray.length; i+=2, j+=2) {
        var width = xArray[j] - xArray[i];
        if (width > segment.width) {
            segment.x = xArray[i];
            segment.width = width;
        }
    }

    return segment;
};

module.exports = Polygon;

/**
 * Create Polygon from SVG polygon|polyline node
 *
 * @param   {object}    node        xml2js node from SVG file
 * @param   {boolean}   [line]      true : polyline, false : polygon. False as default
 * @returns {Polygon}               the polygon object
 */
module.exports.fromNode = function fromNode(node, line) {
    var polygon = new Polygon();

    if (line == true) {
        polygon.type = 'polyline';
    }
    if (typeof node != 'undefined' && typeof node.$ != 'undefined') {
        SvgObject.fromNode(polygon, node);

        if (typeof node.$.points != 'undefined') {
            polygon.setPointsFromString(node.$.points);
        }
    }

    return polygon;
};

/**
 * Create Polygon From JSON object
 * @param   {object}    json            JSON polygon Object
 * @param   {boolean}   line            True : polyline, false : polygon
 * @returns {Polygon}
 */
module.exports.fromJson = function fromJson(json, line) {

    var polygon = new Polygon();

    if (line == true) {
        polygon.type = 'polyline';
    }
    if (typeof json != 'undefined') {
        SvgObject.fromJson(polygon, json);

        if (typeof json.points != 'undefined') {
            polygon.points = json.points;
        }
    }

    return polygon;
};