All files / lib/path PathFlattener.ts

63.8% Statements 67/105
80% Branches 8/10
50% Functions 3/6
63.8% Lines 67/105

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179                              1x 1x 1x             1x 1x 1x                                             1x     2x 2x 2x   2x 2x 2x 2x       2x 5x 5x 5x 5x   2x   31x 31x 31x       31x 31x 13x 13x   13x 13x 31x   18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 31x   2x 3x 3x 3x 3x 2x 2x 2x 2x     2x 2x   1x                                                                               1x                 1x 1x 1x 1x 216x       216x 1x 1x 1x   1x  
/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
 * http://juerglehni.com/ & https://puckey.studio/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */
 
// TODO: remove eslint-disable comment and deal with errors over time
/* eslint-disable */
 
import { ref } from '~/globals';
import { Base } from '~/straps';
import { Curve } from './Curve';
 
/**
 * @name PathFlattener
 * @class
 * @private
 */
export const PathFlattener = Base.extend(
  {
    _class: 'PathFlattener',
 
    /**
     * Creates a path flattener for the given path. The flattener converts
     * curves into a sequence of straight lines by the use of curve-subdivision
     * with an allowed maximum error to create a lookup table that maps curve-
     * time to path offsets, and can be used for efficient iteration over the
     * full length of the path, and getting points / tangents / normals and
     * curvature in path offset space.
     *
     * @param {Path} path the path to create the flattener for
     * @param {Number} [flatness=0.25] the maximum error allowed for the
     *     straight lines to deviate from the original curves
     * @param {Number} [maxRecursion=32] the maximum amount of recursion in
     *     curve subdivision when mapping offsets to curve parameters
     * @param {Boolean} [ignoreStraight=false] if only interested in the result
     *     of the sub-division (e.g. for path flattening), passing `true` will
     *     protect straight curves from being subdivided for curve-time
     *     translation
     * @param {Matrix} [matrix] the matrix by which to transform the path's
     *     coordinates without modifying the actual path.
     * @return {PathFlattener} the newly created path flattener
     */
    initialize: function (path, flatness, maxRecursion, ignoreStraight, matrix) {
      // Instead of relying on path.curves, we only use segments here and
      // get the curve values from them.
      var curves = [], // The curve values as returned by getValues()
        parts = [], // The calculated, subdivided parts of the path
        length = 0, // The total length of the path
        // By default, we're not subdividing more than 32 times.
        minSpan = 1 / (maxRecursion || 32),
        segments = path._segments,
        segment1 = segments[0],
        segment2;
 
      // Iterate through all curves and compute the parts for each of them,
      // by recursively calling computeParts().
      function addCurve(segment1, segment2) {
        var curve = Curve.getValues(segment1, segment2, matrix);
        curves.push(curve);
        computeParts(curve, segment1._index, 0, 1);
      }
 
      function computeParts(curve, index, t1, t2) {
        // Check if the t-span is big enough for subdivision.
        if (
          t2 - t1 > minSpan &&
          !(ignoreStraight && Curve.isStraight(curve)) &&
          // After quite a bit of testing, a default flatness of 0.25
          // appears to offer a good trade-off between speed and
          // precision for display purposes.
          !Curve.isFlatEnough(curve, flatness || 0.25)
        ) {
          var halves = Curve.subdivide(curve, 0.5),
            tMid = (t1 + t2) / 2;
          // Recursively subdivide and compute parts again.
          computeParts(halves[0], index, t1, tMid);
          computeParts(halves[1], index, tMid, t2);
        } else {
          // Calculate the length of the curve interpreted as a line.
          var dx = curve[6] - curve[0],
            dy = curve[7] - curve[1],
            dist = Math.sqrt(dx * dx + dy * dy);
          if (dist > 0) {
            length += dist;
            parts.push({
              offset: length,
              curve: curve,
              index: index,
              time: t2,
            });
          }
        }
      }
 
      for (var i = 1, l = segments.length; i < l; i++) {
        segment2 = segments[i];
        addCurve(segment1, segment2);
        segment1 = segment2;
      }
      if (path._closed) addCurve(segment2 || segment1, segments[0]);
      this.curves = curves;
      this.parts = parts;
      this.length = length;
      // Keep a current index from the part where we last where in
      // _get(), to optimise for iterator-like usage of flattener.
      this.index = 0;
    },
 
    _get: function (offset) {
      // Make sure we're not beyond the requested offset already. Search the
      // start position backwards from where to then process the loop below.
      var parts = this.parts,
        length = parts.length,
        start,
        i,
        j = this.index;
      for (;;) {
        i = j;
        if (!j || parts[--j].offset < offset) break;
      }
      // Find the part that succeeds the given offset, then interpolate
      // with the previous part
      for (; i < length; i++) {
        var part = parts[i];
        if (part.offset >= offset) {
          // Found the right part, remember current position
          this.index = i;
          // Now get the previous part so we can linearly interpolate
          // the curve parameter
          var prev = parts[i - 1],
            // Make sure we only use the previous parameter value if its
            // for the same curve, by checking index. Use 0 otherwise.
            prevTime = prev && prev.index === part.index ? prev.time : 0,
            prevOffset = prev ? prev.offset : 0;
          return {
            index: part.index,
            // Interpolate
            time: prevTime + ((part.time - prevTime) * (offset - prevOffset)) / (part.offset - prevOffset),
          };
        }
      }
      // If we're still here, return last one
      return {
        index: parts[length - 1].index,
        time: 1,
      };
    },
 
    drawPart: function (ctx, from, to) {
      var start = this._get(from),
        end = this._get(to);
      for (var i = start.index, l = end.index; i <= l; i++) {
        var curve = Curve.getPart(this.curves[i], i === start.index ? start.time : 0, i === end.index ? end.time : 1);
        if (i === start.index) ctx.moveTo(curve[0], curve[1]);
        ctx.bezierCurveTo.apply(ctx, curve.slice(2));
      }
    },
  },
  Base.each(
    Curve._evaluateMethods,
    function (name) {
      this[name + 'At'] = function (offset) {
        var param = this._get(offset);
        return Curve[name](this.curves[param.index], param.time);
      };
    },
    {}
  )
);
 
ref.PathFlattener = PathFlattener;