<commit_before>// SVG Path transformations library
//
// Usage:
//
//    SvgPath('...')
//      .translate(-150, -100)
//      .scale(0.5)
//      .translate(-150, -100)
//      .round(1)
//      .toString()
//

'use strict';


var pathParse       = require('./path_parse');
var transformParse  = require('./transform_parse');
var affineTransform = require('./affine_transform');
var a2c             = require('./a2c');
var ellipse        = require('./ellipse');
var box        = require('./box');


// Class constructor
//
function SvgPath(path) {
  if (!(this instanceof SvgPath)) { return new SvgPath(path); }

  var pstate = pathParse(path);

  // Array of path segments.
  // Each segment is array [command, param1, param2, ...]
  this.segments         = pstate.segments;

  // Error message on parse error.
  this.err              = pstate.err;

  // The current transform (identity at the begining)
  this.affineTransform  = affineTransform();
}

// copy path
//
SvgPath.prototype.copy = function () {
  var newP = new SvgPath('');
  var i;

  // copy segments
  for (i = 0; i < this.segments.length; i++) {
    newP.segments[i] = this.segments[i].slice(0);
  }

  // copy err
  newP.err = this.err;

  // copy __stack
  for (i = 0; i < this.__stack.length; i++) {
    newP.matrix(this.__stack[i].toArray());
  }

  return newP;
};

SvgPath.prototype.__transform = function (m) {
  var self = this,
      ma, sx, sy, angle, arc2line, i;

  this.iterate(function (s, index, x, y) {
    var p, result, name, isRelative;

    switch (s[0]) {

      // Process 'assymetric' commands separately
      case 'v':
        p      = m.calc(0, s[1], true);
        result = (p[0] === 0) ? [ 'v', p[1] ] : [ 'l', p[0], p[1] ];
        break;

      case 'V':
        p      = m.calc(x, s[1], false);
        result = (p[0] === m.calc(x, y, false)[0]) ? [ 'V', p[1] ] : [ 'L', p[0], p[1] ];
        break;

      case 'h':
        p      = m.calc(s[1], 0, true);
        result = (p[1] === 0) ? [ 'h', p[0] ] : [ 'l', p[0], p[1] ];
        break;

      case 'H':
        p      = m.calc(s[1], y, false);
        result = (p[1] === m.calc(x, y, false)[1]) ? [ 'H', p[0] ] : [ 'L', p[0], p[1] ];
        break;

      case 'a':
      case 'A':
        // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]

        // Drop segment if arc is empty (end point === start point)
        if ((s[0] === 'A' && s[6] === x && s[7] === y) ||
            (s[0] === 'a' && s[6] === 0 && s[7] === 0)) {
          return [];
        }

        // Transform rx, ry and the x-axis-rotation
        var e = ellipse(s[1], s[2], s[3]).transform(m.toArray());

        // Transform end point as usual (without translation for relative notation)
        p = m.calc(s[6], s[7], s[0] === 'a');

        // if the resulting ellipse is (almost) a segment ...
        if (e.isDegenerate()) {
          // replace the arc by a line
          result = [ s[0] === 'a' ? 'l' : 'L', p[0], p[1] ];
        } else {
          // if it is a real ellipse
          // s[0], s[4] and s[5] are not modified
          result = [ s[0], e.rx, e.ry, e.ax, s[4], s[5], p[0], p[1] ];
        }

        break;

      case 'm':
        // Edge case. The very first `m` should be processed as absolute, if happens.
        // Make sense for coord shift transforms.
        isRelative = index > 0;

        p = m.calc(s[1], s[2], isRelative);
        result = [ 'm', p[0], p[1] ];
        break;

      default:
        name       = s[0];
        result     = [ name ];
        isRelative = (name.toLowerCase() === name);

        // Apply transformations to the segment
        for (i = 1; i < s.length; i += 2) {
          p = m.calc(s[i], s[i + 1], isRelative);
          result.push(p[0], p[1]);
        }
    }

    self.segments[index] = result;
  }, true);
};


// Apply stacked commands
//
SvgPath.prototype.applyTransform = function () {
  if (this.affineTransform.isIdentity()) { return this; }

  this.__transform(this.affineTransform);
  this.affineTransform.reset();

  return this;
};


// Convert processed SVG Path back to string
//
SvgPath.prototype.toString = function () {
  var elements = [], skipCmd, cmd;

  this.applyTransform();

  for (var i = 0; i < this.segments.length; i++) {
    // remove repeating commands names
    cmd = this.segments[i][0];
    skipCmd = i > 0 && cmd !== 'm' && cmd !== 'M' && cmd === this.segments[i - 1][0];
    elements = elements.concat(skipCmd ? this.segments[i].slice(1) : this.segments[i]);
  }

  return elements.join(' ')
            // Optimizations: remove spaces around commands & before `-`
            //
            // We could also remove leading zeros for `0.5`-like values,
            // but their count is too small to spend time for.
            .replace(/ ?([achlmqrstvz]) ?/gi, '$1')
            .replace(/ \-/g, '-')
            // workaround for FontForge SVG importing bug
            .replace(/zm/g, 'z m');
};


// Translate path to (x [, y])
//
SvgPath.prototype.translate = function (x, y) {
  this.affineTransform.translate(x, y || 0);
  return this;
};


// Scale path to (sx [, sy])
// sy = sx if not defined
//
SvgPath.prototype.scale = function (sx, sy) {
  this.affineTransform.scale(sx, isNaN(sy) ? sx : sy);
  return this;
};


// Rotate path around point (sx [, sy])
// sy = sx if not defined
//
SvgPath.prototype.rotate = function (angle, rx, ry) {
  this.affineTransform.rotate(angle, rx || 0, ry || 0);
  return this;
};


// Apply matrix transform (array of 6 elements)
//
SvgPath.prototype.matrix = function (m) {
  this.affineTransform.compose(m);
  return this;
};


// Transform path according to 'transform' attr of SVG spec
//
SvgPath.prototype.transform = function (transformString) {
  if (!transformString.trim()) {
    return this;
  }
  this.affineTransform.compose(transformParse(transformString));
  return this;
};


// Round coords with given decimal precition.
// 0 by default (to integers)
//
SvgPath.prototype.round = function (d) {
  var contourStartDeltaX = 0, contourStartDeltaY = 0, deltaX = 0, deltaY = 0, l;

  d = d || 0;

  this.applyTransform();

  this.segments.forEach(function (s) {
    var isRelative = (s[0].toLowerCase() === s[0]), t;

    switch (s[0]) {
      case 'H':
      case 'h':
        if (isRelative) { s[1] += deltaX; }
        deltaX = s[1] - s[1].toFixed(d);
        s[1] = +s[1].toFixed(d);
        return;

      case 'V':
      case 'v':
        if (isRelative) { s[1] += deltaY; }
        deltaY = s[1] - s[1].toFixed(d);
        s[1] = +s[1].toFixed(d);
        return;

      case 'Z':
      case 'z':
        contourStartDeltaX = deltaX;
        contourStartDeltaY = deltaY;
        return;

      case 'M':
      case 'm':
        if (isRelative) {
          s[1] += deltaX;
          s[2] += deltaY;
        }

        deltaX = s[1] - s[1].toFixed(d);
        deltaY = s[2] - s[2].toFixed(d);

        contourStartDeltaX = deltaX;
        contourStartDeltaY = deltaY;

        s[1] = +s[1].toFixed(d);
        s[2] = +s[2].toFixed(d);
        return;

      case 'A':
      case 'a':
        // [cmd, rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
        if (isRelative) {
          s[6] += deltaX;
          s[7] += deltaY;
        }

        deltaX = s[6] - s[6].toFixed(d);
        deltaY = s[7] - s[7].toFixed(d);

        s[1] = +s[1].toFixed(d);
        s[2] = +s[2].toFixed(d);
        s[3] = +s[3].toFixed(d + 2); // better precision for rotation
        s[6] = +s[6].toFixed(d);
        s[7] = +s[7].toFixed(d);
        return;

      default:
        // a c l q s t
        l = s.length;

        if (isRelative) {
          s[l - 2] += deltaX;
          s[l - 1] += deltaY;
        }

        deltaX = s[l - 2] - s[l - 2].toFixed(d);
        deltaY = s[l - 1] - s[l - 1].toFixed(d);

        s.forEach(function (val, i) {
          if (!i) { return; }
          s[i] = +s[i].toFixed(d);
        });
        return;
    }
  });

  return this;
};


// Apply iterator function to all segments. If function returns result,
// current segment will be replaced to array of returned segments.
// If empty array is returned, current regment will be deleted.
//<commit_msg>better round()<commit_after>// SVG Path transformations library
//
// Usage:
//
//    SvgPath('...')
//      .translate(-150, -100)
//      .scale(0.5)
//      .translate(-150, -100)
//      .round(1)
//      .toString()
//

/*eslint no-bitwise: 0*/
'use strict';


var pathParse       = require('./path_parse');
var transformParse  = require('./transform_parse');
var affineTransform = require('./affine_transform');
var a2c             = require('./a2c');
var ellipse        = require('./ellipse');
var box        = require('./box');


// Class constructor
//
function SvgPath(path) {
  if (!(this instanceof SvgPath)) { return new SvgPath(path); }

  var pstate = pathParse(path);

  // Array of path segments.
  // Each segment is array [command, param1, param2, ...]
  this.segments         = pstate.segments;

  // Error message on parse error.
  this.err              = pstate.err;

  // The current transform (identity at the begining)
  this.affineTransform  = affineTransform();
}

// copy path
//
SvgPath.prototype.copy = function () {
  var newP = new SvgPath('');
  var i;

  // copy segments
  for (i = 0; i < this.segments.length; i++) {
    newP.segments[i] = this.segments[i].slice(0);
  }

  // copy err
  newP.err = this.err;

  // copy affineTransform
  newP.affineTransform = this.affineTransform.copy()

  return newP;
};

SvgPath.prototype.__transform = function (m) {
  var self = this,
      ma, sx, sy, angle, arc2line, i;

  this.iterate(function (s, index, x, y) {
    var p, result, name, isRelative;

    switch (s[0]) {

      // Process 'assymetric' commands separately
      case 'v':
        p      = m.calc(0, s[1], true);
        result = (p[0] === 0) ? [ 'v', p[1] ] : [ 'l', p[0], p[1] ];
        break;

      case 'V':
        p      = m.calc(x, s[1], false);
        result = (p[0] === m.calc(x, y, false)[0]) ? [ 'V', p[1] ] : [ 'L', p[0], p[1] ];
        break;

      case 'h':
        p      = m.calc(s[1], 0, true);
        result = (p[1] === 0) ? [ 'h', p[0] ] : [ 'l', p[0], p[1] ];
        break;

      case 'H':
        p      = m.calc(s[1], y, false);
        result = (p[1] === m.calc(x, y, false)[1]) ? [ 'H', p[0] ] : [ 'L', p[0], p[1] ];
        break;

      case 'a':
      case 'A':
        // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]

        // Drop segment if arc is empty (end point === start point)
        if ((s[0] === 'A' && s[6] === x && s[7] === y) ||
            (s[0] === 'a' && s[6] === 0 && s[7] === 0)) {
          return [];
        }

        // Transform rx, ry and the x-axis-rotation
        var e = ellipse(s[1], s[2], s[3]).transform(m.toArray());

        // Transform end point as usual (without translation for relative notation)
        p = m.calc(s[6], s[7], s[0] === 'a');

        // if the resulting ellipse is (almost) a segment ...
        if (e.isDegenerate()) {
          // replace the arc by a line
          result = [ s[0] === 'a' ? 'l' : 'L', p[0], p[1] ];
        } else {
          // if it is a real ellipse
          // s[0], s[4] and s[5] are not modified
          result = [ s[0], e.rx, e.ry, e.ax, s[4], s[5], p[0], p[1] ];
        }

        break;

      case 'm':
        // Edge case. The very first `m` should be processed as absolute, if happens.
        // Make sense for coord shift transforms.
        isRelative = index > 0;

        p = m.calc(s[1], s[2], isRelative);
        result = [ 'm', p[0], p[1] ];
        break;

      default:
        name       = s[0];
        result     = [ name ];
        isRelative = (name.toLowerCase() === name);

        // Apply transformations to the segment
        for (i = 1; i < s.length; i += 2) {
          p = m.calc(s[i], s[i + 1], isRelative);
          result.push(p[0], p[1]);
        }
    }

    self.segments[index] = result;
  }, true);
};


// Apply stacked commands
//
SvgPath.prototype.applyTransform = function () {
  if (this.affineTransform.isIdentity()) { return this; }

  this.__transform(this.affineTransform);
  this.affineTransform.reset();

  return this;
};


// Convert processed SVG Path back to string
//
SvgPath.prototype.toString = function () {
  var elements = [], skipCmd, cmd;

  this.applyTransform();

  for (var i = 0; i < this.segments.length; i++) {
    // remove repeating commands names
    cmd = this.segments[i][0];
    skipCmd = i > 0 && cmd !== 'm' && cmd !== 'M' && cmd === this.segments[i - 1][0];
    elements = elements.concat(skipCmd ? this.segments[i].slice(1) : this.segments[i]);
  }

  return elements.join(' ')
            // Optimizations: remove spaces around commands & before `-`
            //
            // We could also remove leading zeros for `0.5`-like values,
            // but their count is too small to spend time for.
            .replace(/ ?([achlmqrstvz]) ?/gi, '$1')
            .replace(/ \-/g, '-')
            // workaround for FontForge SVG importing bug
            .replace(/zm/g, 'z m');
};


// Translate path to (x [, y])
//
SvgPath.prototype.translate = function (x, y) {
  this.affineTransform.translate(x, y || 0);
  return this;
};


// Scale path to (sx [, sy])
// sy = sx if not defined
//
SvgPath.prototype.scale = function (sx, sy) {
  this.affineTransform.scale(sx, isNaN(sy) ? sx : sy);
  return this;
};


// Rotate path around point (sx [, sy])
// sy = sx if not defined
//
SvgPath.prototype.rotate = function (angle, rx, ry) {
  this.affineTransform.rotate(angle, rx || 0, ry || 0);
  return this;
};


// Apply matrix transform (array of 6 elements)
//
SvgPath.prototype.matrix = function (m) {
  this.affineTransform.compose(m);
  return this;
};


// Transform path according to 'transform' attr of SVG spec
//
SvgPath.prototype.transform = function (transformString) {
  if (!transformString.trim()) {
    return this;
  }
  this.affineTransform.compose(transformParse(transformString));
  return this;
};


// round a decimal number
//  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
//  for integers ~~" "  is faster then +" " and ~~undefined=0
//
function round(value, exp) {
  // convert exp to integer (undefined => 0)
  exp = ~~exp;
  // If the exp is undefined or zero...
  if (exp === 0) {
    return Math.round(value);
  }
  // convert value to number
  value = +value;
  // If the value is not a number...
  if (isNaN(value)) {
    return NaN;
  }
  // Shift
  value = value.toString().split('e');
  value = Math.round(+value[0] + 'e' + (~~value[1] + exp));
  // Shift back
  value = value.toString().split('e');
  return +(value[0] + 'e' + (~~value[1] - exp));
}

// Round coords with given decimal precition.
// 0 by default (to integers)
//
SvgPath.prototype.round = function (d) {
  var contourStartDeltaX = 0, contourStartDeltaY = 0,
      deltaX = 0, deltaY = 0;

  this.applyTransform();

  this.segments.forEach(function (s) {
    var isRelative = (s[0].toLowerCase() === s[0]),
        oldX, oldY, l;

    switch (s[0]) {
      case 'H':
      case 'h':
        if (isRelative) { s[1] += deltaX; }
        oldX = s[1];
        s[1] = round(oldX, d);
        deltaX = oldX - s[1];
        return;

      case 'V':
      case 'v':
        if (isRelative) { s[1] += deltaY; }
        oldY = s[1];
        s[1] = round(oldY, d);
        deltaY = oldY - s[1];
        return;

      case 'Z':
      case 'z':
        deltaX = contourStartDeltaX;
        deltaY = contourStartDeltaY;
        return;

      case 'A':
      case 'a':
        // [cmd, rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
        s[1] = round(s[1], d);
        s[2] = round(s[2], d);
        s[3] = round(s[3], d + 2); // better precision for rotation
        if (isRelative) {
          s[6] += deltaX;
          s[7] += deltaY;
        }
        // round x,y
        oldX = s[6];
        oldY = s[7];
        s[6] = round(oldX, d);
        s[7] = round(oldY, d);
        deltaX = oldX - s[6];
        deltaY = oldY - s[7];
        return;

      default:
        // Mm Ll Cc Qq Ss Tt
        l = s.length - 3;
        if (isRelative) {
          s[l + 1] += deltaX;
          s[l + 2] += deltaY;
        }
        // round x,y
        oldX = s[l + 1];
        oldY = s[l + 2];
        s[l + 1] = round(oldX, d);
        s[l + 2] = round(oldY, d);
        deltaX = oldX - s[l + 1];
        deltaY = oldY - s[l + 2];
        // round the control points (if any)
        for (;l > 0;l--) {
          s[l] = round(s[l], d);
        }

        if (s[0].toLowerCase() === 'm') {
          contourStartDeltaX = deltaX;
          contourStartDeltaY = deltaY;
        }

        return;
    }
  });

  return this;
};


// Apply iterator function to all segments. If function returns result,
// current segment will be replaced to array of returned segments.
// If empty array is returned, current regment will be deleted.
//
{'fraction_non_alphanumeric': 0.17996952648851383, 'fraction_numerical': 0.017522269104547586, 'mean_word_length': 1.9724786622539627, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 9, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6836229', 'n_tokens_mistral': 6185, 'n_tokens_neox': 5929, 'n_words': 1624}