transformsWithOnePath.js 10.6 KB
'use strict';

/*
 * Thanks to http://fontello.com project for sponsoring this plugin
 */

exports.type = 'full';

exports.active = false;

exports.description = 'performs a set of operations on SVG with one path inside (disabled by default)';

exports.params = {
    // width and height to resize SVG and rescale inner Path
    width: false,
    height: false,

    // scale inner Path without resizing SVG
    scale: false,

    // shiftX/Y inner Path
    shiftX: false,
    shiftY: false,

    // crop SVG width along the real width of inner Path
    hcrop: false,

    // vertical center inner Path inside SVG height
    vcenter: false,

    // stringify params
    floatPrecision: 3,
    leadingZero: true,
    negativeExtraSpace: true
};

var _path = require('./_path.js'),
    relative2absolute = _path.relative2absolute,
    computeCubicBoundingBox = _path.computeCubicBoundingBox,
    computeQuadraticBoundingBox = _path.computeQuadraticBoundingBox,
    applyTransforms = _path.applyTransforms,
    js2path = _path.js2path,
    path2js = _path.path2js,
    EXTEND = require('whet.extend');

exports.fn = function(data, params) {

    data.content.forEach(function(item) {

        // only for SVG with one Path inside
        if (item.isElem('svg') &&
            item.content.length === 1 &&
            item.content[0].isElem('path')
        ) {

            var svgElem = item,
                pathElem = svgElem.content[0],
                // get absoluted Path data
                path = relative2absolute(EXTEND(true, [], path2js(pathElem))),
                xs = [],
                ys = [],
                cubicСontrolPoint = [0, 0],
                quadraticСontrolPoint = [0, 0],
                lastPoint = [0, 0],
                cubicBoundingBox,
                quadraticBoundingBox,
                i,
                segment;

            path.forEach(function(pathItem) {

                // ML
                if ('ML'.indexOf(pathItem.instruction) > -1) {

                    for (i = 0; i < pathItem.data.length; i++) {
                        if (i % 2 === 0) {
                            xs.push(pathItem.data[i]);
                        } else {
                            ys.push(pathItem.data[i]);
                        }
                    }

                    lastPoint = cubicСontrolPoint = quadraticСontrolPoint = pathItem.data.slice(-2);

                // H
                } else if (pathItem.instruction === 'H') {

                    pathItem.data.forEach(function(d) {
                        xs.push(d);
                    });

                    lastPoint[0] = cubicСontrolPoint[0] = quadraticСontrolPoint[0] = pathItem.data[pathItem.data.length - 2];

                // V
                } else if (pathItem.instruction === 'V') {

                    pathItem.data.forEach(function(d) {
                        ys.push(d);
                    });

                    lastPoint[1] = cubicСontrolPoint[1] = quadraticСontrolPoint[1] = pathItem.data[pathItem.data.length - 1];

                // C
                } else if (pathItem.instruction === 'C') {

                    for (i = 0; i < pathItem.data.length; i += 6) {

                        segment = pathItem.data.slice(i, i + 6);

                        cubicBoundingBox = computeCubicBoundingBox.apply(this, lastPoint.concat(segment));

                        xs.push(cubicBoundingBox.minx);
                        xs.push(cubicBoundingBox.maxx);

                        ys.push(cubicBoundingBox.miny);
                        ys.push(cubicBoundingBox.maxy);

                        // reflected control point for the next possible S
                        cubicСontrolPoint = [
                            2 * segment[4] - segment[2],
                            2 * segment[5] - segment[3]
                        ];

                        lastPoint = segment.slice(-2);

                    }

                // S
                } else if (pathItem.instruction === 'S') {

                    for (i = 0; i < pathItem.data.length; i += 4) {

                        segment = pathItem.data.slice(i, i + 4);

                        cubicBoundingBox = computeCubicBoundingBox.apply(this, lastPoint.concat(cubicСontrolPoint).concat(segment));

                        xs.push(cubicBoundingBox.minx);
                        xs.push(cubicBoundingBox.maxx);

                        ys.push(cubicBoundingBox.miny);
                        ys.push(cubicBoundingBox.maxy);

                        // reflected control point for the next possible S
                        cubicСontrolPoint = [
                            2 * segment[2] - cubicСontrolPoint[0],
                            2 * segment[3] - cubicСontrolPoint[1],
                        ];

                        lastPoint = segment.slice(-2);

                    }

                // Q
                } else if (pathItem.instruction === 'Q') {

                    for (i = 0; i < pathItem.data.length; i += 4) {

                        segment = pathItem.data.slice(i, i + 4);

                        quadraticBoundingBox = computeQuadraticBoundingBox.apply(this, lastPoint.concat(segment));

                        xs.push(quadraticBoundingBox.minx);
                        xs.push(quadraticBoundingBox.maxx);

                        ys.push(quadraticBoundingBox.miny);
                        ys.push(quadraticBoundingBox.maxy);

                        // reflected control point for the next possible T
                        quadraticСontrolPoint = [
                            2 * segment[2] - segment[0],
                            2 * segment[3] - segment[1]
                        ];

                        lastPoint = segment.slice(-2);

                    }

                // S
                } else if (pathItem.instruction === 'T') {

                    for (i = 0; i < pathItem.data.length; i += 2) {

                        segment = pathItem.data.slice(i, i + 2);

                        quadraticBoundingBox = computeQuadraticBoundingBox.apply(this, lastPoint.concat(quadraticСontrolPoint).concat(segment));

                        xs.push(quadraticBoundingBox.minx);
                        xs.push(quadraticBoundingBox.maxx);

                        ys.push(quadraticBoundingBox.miny);
                        ys.push(quadraticBoundingBox.maxy);

                        // reflected control point for the next possible T
                        quadraticСontrolPoint = [
                            2 * segment[0] - quadraticСontrolPoint[0],
                            2 * segment[1] - quadraticСontrolPoint[1]
                        ];

                        lastPoint = segment.slice(-2);

                    }

                }

            });

            var xmin = Math.min.apply(this, xs).toFixed(params.floatPrecision),
                xmax = Math.max.apply(this, xs).toFixed(params.floatPrecision),
                ymin = Math.min.apply(this, ys).toFixed(params.floatPrecision),
                ymax = Math.max.apply(this, ys).toFixed(params.floatPrecision),
                svgWidth = +svgElem.attr('width').value,
                svgHeight = +svgElem.attr('height').value,
                realWidth = Math.round(xmax - xmin),
                realHeight = Math.round(ymax - ymin),
                transform = '',
                scale;

            // width & height
            if (params.width && params.height) {

                scale = Math.min(params.width / svgWidth, params.height / svgHeight);

                realWidth = realWidth * scale;
                realHeight = realHeight * scale;

                svgWidth = svgElem.attr('width').value = params.width;
                svgHeight = svgElem.attr('height').value = params.height;

                transform += ' scale(' + scale + ')';

            // width
            } else if (params.width && !params.height) {

                scale = params.width / svgWidth;

                realWidth = realWidth * scale;
                realHeight = realHeight * scale;

                svgWidth = svgElem.attr('width').value = params.width;
                svgHeight = svgElem.attr('height').value = svgHeight * scale;

                transform += ' scale(' + scale + ')';

            // height
            } else if (params.height && !params.width) {

                scale = params.height / svgHeight;

                realWidth = realWidth * scale;
                realHeight = realHeight * scale;

                svgWidth = svgElem.attr('width').value = svgWidth * scale;
                svgHeight = svgElem.attr('height').value = params.height;

                transform += ' scale(' + scale + ')';

            }

            // shiftX
            if (params.shiftX) {
                transform += ' translate(' + realWidth * params.shiftX + ', 0)';
            }

            // shiftY
            if (params.shiftY) {
                transform += ' translate(0, ' + realHeight * params.shiftY + ')';
            }

            // scale
            if (params.scale) {
                scale = params.scale;

                var shiftX = svgWidth / 2,
                    shiftY = svgHeight / 2;

                realWidth = realWidth * scale;
                realHeight = realHeight * scale;

                if (params.shiftX || params.shiftY) {
                    transform += ' scale(' + scale + ')';
                } else {
                    transform += ' translate(' + shiftX + ' ' + shiftY + ') scale(' + scale + ') translate(-' + shiftX + ' -' + shiftY + ')';
                }
            }

            // hcrop
            if (params.hcrop) {
                transform += ' translate(' + (-xmin) + ' 0)';

                svgElem.attr('width').value = realWidth;
            }

            // vcenter
            if (params.vcenter) {
                transform += ' translate(0 ' + (((svgHeight - realHeight) / 2) - ymin) + ')';
            }

            if (transform) {
                pathElem.addAttr({
                    name: 'transform',
                    prefix: '',
                    local: 'transform',
                    value: transform
                });

                path = applyTransforms(pathElem, pathElem.pathJS, true, params.floatPrecision);

                // transformed data rounding
                path.forEach(function(pathItem) {
                    if (pathItem.data) {
                        pathItem.data = pathItem.data.map(function(num) {
                            return +num.toFixed(params.floatPrecision);
                        });
                    }
                });

                // save new
                js2path(pathElem, path, params);
            }

        }

    });

    return data;

};