text: /*
   * 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.
   */
  
  // We call our variable `isNodeContext` because resemble.js exposes a global
  // `isNode` function which would override it and break node check.
  var isNodeContext = typeof global === 'object',
      isPhantomContext = typeof window === 'object' && !!window.callPhantom,
      scope;
  
  if (isNodeContext) {
      scope = global;
      // Resemble.js needs the Image constructor global.
      global.Image = paper.window.Image;
  } else {
      scope = window;
      // This is only required when running in the browser:
      // Until window.history.pushState() works when running locally, we need to
      // trick qunit into thinking that the feature is not present. This appears
      // to work...
      // TODO: Ideally we should fix this in QUnit instead.
      delete window.history;
      window.history = {};
      QUnit.begin(function() {
          if (QUnit.urlParams.hidepassed) {
              document.getElementById('qunit-tests').className += ' hidepass';
          }
      });
  }
  
  // Some native javascript classes have name collisions with Paper.js classes.
  // If they have not already been stored in `src/load.js`, do it now:
  var nativeClasses = this.nativeClasses || {
      Event: this.Event || {},
      MouseEvent: this.MouseEvent || {}
  };
  
  // The unit-tests expect the paper classes to be global.
  paper.install(scope);
  
  // Override console.error, so that we can catch errors that are only logged to
  // the console.
  var errorHandler = console.error;
  console.error = function() {
      var current = QUnit.config.current;
      if (current) {
          QUnit.pushFailure([].join.call(arguments, ' '), current.stack);
      }
      errorHandler.apply(this, arguments);
  };
  
  var currentProject;
  
  QUnit.done(function(details) {
      console.error = errorHandler;
      // Clear all event listeners after final test.
      if (currentProject) {
          currentProject.remove();
      }
  });
  
  // NOTE: In order to "export" all methods into the shared Prepro.js scope when
  // using node-qunit, we need to define global functions as:
  // `var name = function() {}`. `function name() {}` does not work!
  var test = function(testName, expected) {
      return QUnit.test(testName, function(assert) {
          // Since tests can be asynchronous, remove the old project before
          // running the next test.
          if (currentProject) {
              currentProject.remove();
              // This is needed for interactions tests, to make sure that test is
              // run with a fresh state.
              View._resetState();
          }
  
          // Instantiate project with 100x100 pixels canvas instead of default
          // 1x1 to make interactions tests simpler by working with integers.
          currentProject = new Project(new Size(100, 100));
          expected(assert);
      });
  };
  
  // Override equals to convert functions to message and execute them as tests()
  var equals = function(actual, expected, message, options) {
      // Allow the use of functions for actual, which will get called and their
      // source content extracted for readable reports.
      if (typeof actual === 'function') {
          if (!message)
              message = getFunctionMessage(actual);
          actual = actual();
      }
      // Get the comparator based on the expected value's type only and ignore the
      // actual value's type.
      var type = typeof expected,
          cls;
      type = expected === null && 'Null'
              || type === 'number' && 'Number'
              || type === 'boolean' && 'Boolean'
              || type === 'undefined' && 'Undefined'
              || Array.isArray(expected) && 'Array'
              || expected instanceof window.Element && 'Element' // DOM Elements
              || (cls = expected && expected._class) // check _class 2nd last
              || type === 'object' && 'Object'; // Object as catch-all
      var comparator = type && comparators[type];
      if (!message)
          message = type ? type.toLowerCase() : 'value';
      if (comparator) {
          comparator(actual, expected, message, options);
      } else if (expected && expected.equals) {
          // Fall back to equals
          QUnit.push(expected.equals(actual), actual, expected, message);
      } else {
          // Finally perform a strict compare
          QUnit.push(actual === expected, actual, expected, message);
      }
      if (options && options.cloned && cls) {
          var identical = identicalAfterCloning[cls];
          QUnit.push(identical ? actual === expected : actual !== expected,
                  actual, identical ? expected : 'not ' + expected,
                  message + ': identical after cloning');
      }
  };
  
  // A list of classes that should be identical after their owners were cloned.
  var identicalAfterCloning = {
      Gradient: true,
      SymbolDefinition: true
  };
  
  // Register a jsDump parser for Base.
  QUnit.jsDump.setParser('Base', function (obj, stack) {
      // Just compare the string representation of classes inheriting from Base,
      // since they hide the internal values.
      return obj.toString();
  });
  
  // Override the default object parser to handle Base objects.
  // We need to keep a reference to the previous implementation.
  var objectParser = QUnit.jsDump.parsers.object;
  
  QUnit.jsDump.setParser('object', function (obj, stack) {
      return (obj instanceof Base
              ? QUnit.jsDump.parsers.Base
              : objectParser).call(this, obj, stack);
  });
  
  var compareProperties = function(actual, expected, properties, message, options) {
      for (var i = 0, l = properties.length; i < l; i++) {
          var key = properties[i];
          equals(actual[key], expected[key],
                  message + ' (#' + key + ')', options);
      }
  };
  
  /**
   * Compare 2 image data with resemble.js library.
   * When comparison fails, expected, actual and compared images are displayed.
   * @param {ImageData} imageData1 the expected image data
   * @param {ImageData} imageData2 the actual image data
   * @param {number} tolerance
   * @param {string} diffDetail text displayed when comparison fails
   */
  var compareImageData = function(imageData1, imageData2, tolerance, diffDetail) {
      /**
       * Build an image element from a given image data.
       * @param {ImageData} imageData
       * @return {HTMLImageElement}
       */
      function image(imageData) {
          var canvas = document.createElement('canvas');
          canvas.width = imageData.width;
          canvas.height = imageData.height;
          canvas.getContext('2d').putImageData(imageData, 0, 0);
          var image = new Image();
          image.src = canvas.toDataURL();
          canvas.remove();
          return image;
      }
  
      tolerance = (tolerance || 1e-4) * 100;
  
      var id = QUnit.config.current.testId,
          index = QUnit.config.current.assertions.length + 1,
          result;
      // Compare image-data using resemble.js:
      resemble.compare(
          imageData1,
          imageData2,
          {
              output: {
                  errorColor: { red: 255, green: 51, blue: 0 },
                  errorType: 'flat',
                  transparency: 1
              },
              ignore: ['antialiasing']
          },
          // When working with imageData, this call is synchronous:
          function (error, data) {
              if (error) {
                  console.error(error);
              } else {
                  result = data;
              }
          }
      )
      // Compare with tolerance in percentage...
      var fixed = tolerance < 1 ? ((1 / tolerance) + '').length - 1 : 0,
          identical = result ? 100 - result.misMatchPercentage : 0,
          ok = Math.abs(100 - identical) <= tolerance,
          text = identical.toFixed(fixed) + '% identical',
          detail = text;
      if (!ok && diffDetail) {
          detail += diffDetail;
      }
      QUnit.push(ok, text, (100).toFixed(fixed) + '% identical');
      if (!ok && result && !isNodeContext) {
          // Get the right entry for this unit test and assertion, and
          // replace the results with images
          var entry = document.getElementById('qunit-test-output-' + id)
              .querySelector('li:nth-child(' + (index) + ')'),
              bounds = result.diffBounds;
          entry.querySelector('.test-expected td').appendChild(image(imageData2));
          entry.querySelector('.test-actual td').appendChild(image(imageData1));
          entry.querySelector('.test-diff td').innerHTML = '<pre>' + detail
              + '</pre><br>'
              + '<img src="' + result.getImageDataUrl() + '">';
      }
  };
  
  var comparePixels = function(actual, expected, message, options) {
      function rasterize(item, group, resolution) {
          var raster = null;
          if (group) {
              var parent = item.parent,
                  index = item.index;
              group.addChild(item);
              raster = group.rasterize(resolution, false);
              if (parent) {
                  parent.insertChild(index, item);
              } else {
                  item.remove();
              }
          }
          return raster;
      }
  
      if (!expected) {
          return QUnit.strictEqual(actual, expected, message, options);
      } else if (!actual) {
          // In order to compare pixels, just create an empty item that can be
          // rasterized to an empty raster.
          actual = new Group();
      }
  
      options = options || {};
      // In order to properly compare pixel by pixel, we need to put each item
      // into a group with a white background of the united dimensions of the
      // bounds of both items before rasterizing.
      var resolution = options.resolution || 72,
          actualBounds = actual.strokeBounds,
          expectedBounds = expected.strokeBounds,
          bounds = actualBounds.isEmpty()
              ? expectedBounds
              : expectedBounds.isEmpty()
                  ? actualBounds
                  : actualBounds.unite(expectedBounds);
      if (bounds.isEmpty()) {
          QUnit.equal('empty', 'empty', message);
          return;
      }
      var group = actual && expected && new Group({
              insert: false,
              children: [
                  new Shape.Rectangle({
                      rectangle: bounds,
                      fillColor: 'white'
                  })
              ]
          }),
          actualRaster = rasterize(actual, group, resolution),
          expectedRaster = rasterize(expected, group, resolution);
      if (!actualRaster || !expectedRaster) {
          QUnit.push(false, null, null, 'Unable to compare rasterized items: ' +
              (!actualRaster ? 'actual' : 'expected') + ' item is null',
              QUnit.stack(2));
      } else {
          // Compare the two rasterized items.
          var detail = actual instanceof PathItem && expected instanceof PathItem
              ? '\nExpected:\n' + expected.pathData +
                  '\nActual:\n' + actual.pathData
              : '';
          compareImageData(
              actualRaster.getImageData(),
              expectedRaster.getImageData(),
              options.tolerance,
              detail
          );
      }
  };
  
  var compareItem = function(actual, expected, message, options, properties) {
      options = options || {};
      if (options.rasterize) {
          comparePixels(actual, expected, message, options);
      } else if (!actual || !expected) {
          QUnit.strictEqual(actual, expected, message);
      } else {
          if (options.cloned)
              QUnit.notStrictEqual(actual.id, expected.id,
                      message + ' (not #id)');
          QUnit.strictEqual(actual.constructor, expected.constructor,
                  message + ' (#constructor)');
          // When item is cloned and has a name, the name will be versioned:
          equals(actual.name,
                  options.cloned && expected.name
                      ? expected.name + ' 1' : expected.name,
                  message + ' (#name)');
          compareProperties(actual, expected, ['children', 'bounds', 'position',
                  'matrix', 'data', 'opacity', 'locked', 'visible', 'blendMode',
                  'selected', 'fullySelected', 'clipMask', 'guide'],
                  message, options);
          if (properties)
              compareProperties(actual, expected, properties, message, options);
          // Style
          var styles = ['fillColor',
                  'strokeColor', 'strokeCap', 'strokeJoin', 'dashArray',
                  'dashOffset', 'miterLimit'];
          if (expected instanceof TextItem)
              styles.push('fontSize', 'font', 'leading', 'justification');
          compareProperties(actual.style, expected.style, styles,
                  message + ' (#style)', options);
      }
  };
  
  /**
   * Run each callback in a separated canvas context and compare both outputs.
   * This can be used to do selection drawing tests as it is not possible with
   * comparePixels() method which relies on the item.rasterize() method which
   * ignores selection.
   * @param {number} width the width of the canvas
   * @param {number} height the height of the canvas
   * @param {function} expectedCallback the function producing the expected result
   * @param {function} actualCallback the function producing the actual result
   * @param {number} tolerance between 0 and 1
   */
  var compareCanvas = function(width, height, expected, actual, tolerance) {
      function getImageData(width, height, callback) {
          var canvas = document.createElement('canvas');
          canvas.width = width;
          canvas.height = height;
          var project = new Project(canvas);
          var view = project.view;
          callback();
          view.update();
          var imageData = view.context.getImageData(0, 0, width, height);
          project.remove();
          canvas.remove();
          return imageData;
      }
  
      compareImageData(
          getImageData(width, height, expected),
          getImageData(width, height, actual),
          tolerance
      );
  
      currentProject.activate();
  };
  
  // A list of comparator functions, based on `expected` type. See equals() for
  // an explanation of how the type is determined.
  var comparators = {
      Null: QUnit.strictEqual,
      Undefined: QUnit.strictEqual,
      Boolean: QUnit.strictEqual,
  
      Object: function(actual, expected, message, options) {
          QUnit.push(Base.equals(actual, expected), actual, expected, message);
      },
  
      Element: function(actual, expected, message, options) {
          // Convention: Loop through the attribute lists of both actual and
          // expected element, and compare values even if they may be inherited.
          // This is to handle styling values on SVGElement items more flexibly.
          equals(actual && actual.tagName, expected.tagName,
                  (message || '') + ' (#tagName)', options);
          for (var i = 0; i < expected.attributes.length; i++) {
              var attr = expected.attributes[i];
              if (attr.specified) {
                  equals(actual && actual.getAttribute(attr.name), attr.value,
                          (message || '') + ' (#' + attr.name + ')', options);
              }
          }
          for (var i = 0; i < actual && actual.attributes.length; i++) {
              var attr = actual.attributes[i];
              if (attr.specified) {
                  equals(attr.value, expected.getAttribute(attr.name)
                          (message || '') + ' #(' + attr.name + ')', options);
              }
          }
      },
  
      Base: function(actual, expected, message, options) {
          comparators.Object(actual, expected, message, options);
      },
  
      Number: function(actual, expected, message, options) {
          // Compare with a default tolerance of 1e-5:
          var ok = Math.abs(actual - expected)
                  <= Base.pick(options && options.tolerance, 1e-5);
          QUnit.push(ok, ok ? expected : actual, expected, message);
      },
  
      Array: function(actual, expected, message, options) {
          QUnit.strictEqual(actual.length, expected.length, message
                  + ' (#length)');
          for (var i = 0, l = actual.length; i < l; i++) {
              equals(actual[i], expected[i], (message || '') + '[' + i + ']',
                  options);
          }
      },
  
      Point: function(actual, expected, message, options) {
          comparators.Number(actual.x, expected.x, message + ' (#x)', options);
          comparators.Number(actual.y, expected.y, message + ' (#y)', options);
      },
  
      Size: function(actual, expected, message, options) {
          comparators.Number(actual.width, expected.width,
                  message + ' (#width)', options);
          comparators.Number(actual.height, expected.height,
                  message + ' (#height)', options);
      },
  
      Rectangle: function(actual, expected, message, options) {
          comparators.Point(actual, expected, message, options);
          comparators.Size(actual, expected, message, options);
      },
  
      Matrix: function(actual, expected, message, options) {
          comparators.Array(actual.values, expected.values, message, options);
      },
  
      Color: function(actual, expected, message, options) {
          if (actual && expected) {
              equals(actual.type, expected.type, message + ' (#type)', options);
              // NOTE: This also compares gradients, with identity checks and all.
              equals(actual.components, expected.components,
                      message + ' (#components)', options);
          } else {
              QUnit.push(expected.equals(actual), actual, expected, message);
          }
      },
  
      Segment: function(actual, expected, message, options) {
          compareProperties(actual, expected, ['handleIn', 'handleOut', 'point',
                  'selected'], message, options);
      },
  
      SegmentPoint: function(actual, expected, message, options) {
          comparators.Point(actual, expected, message, options);
          comparators.Boolean(actual.selected, expected.selected,
                  message + ' (#selected)', options);
      },
  
      Item: compareItem,
  
      Group: function(actual, expected, message, options) {
          compareItem(actual, expected, message, options, ['clipped']);
      },
  
      Layer: function(actual, expected, message, options) {
          compareItem(actual, expected, message, options);
          var sameProject = actual.project === expected.project;
          var sharedProject = !(options && options.dontShareProject);
          QUnit.push(sharedProject ? sameProject : !sameProject,
                  actual.project,
                  sharedProject ? expected.project : 'not ' + expected.project,
                  message + ' (#project)');
      },
  
      Path: function(actual, expected, message, options) {
          compareItem(actual, expected, message, options,
                  ['segments', 'closed', 'clockwise']);
      },
  
      CompoundPath: function(actual, expected, message, options) {
          compareItem(actual, expected, message, options);
      },
  
      Raster: function(actual, expected, message, options) {
          var pixels = options && options.pixels,
              properties = ['size', 'width', 'height', 'resolution'];
          if (!pixels)
              properties.push('source', 'image');
          compareItem(actual, expected, message, options, properties);
          if (pixels) {
              comparePixels(actual, expected, message, options);
          } else {
              equals(actual.toDataURL(), expected.toDataURL(),
                      message + ' (#toDataUrl())');
          }
      },
  
      Shape: function(actual, expected, message, options) {
          compareItem(actual, expected, message, options,
                  ['shape', 'size', 'radius']);
      },
  
      PointText: function(actual, expected, message, options) {
          compareItem(actual, expected, message, options,
                  ['content', 'point']);
      },
  
      SymbolItem: function(actual, expected, message, options) {
          compareItem(actual, expected, message,
                  // Cloning SymbolItems does not result in cloned
                  // SymbolDefinitions
                  options && options.cloned
                          ? Base.set({}, options, { cloned: false })
                          : options,
                  ['symbol']);
      },
  
      SymbolDefinition: function(actual, expected, message, options) {
          equals(actual.definition, expected.definition,
                  message + ' (#definition)', options);
      },
  
      Project: function(actual, expected, message, options) {
          compareProperties(actual, expected, ['layers'], message, options);
      }
  };
  
  // Some other helpers:
  
  var getFunctionMessage = function(func) {
      var message = func.toString().match(
          /^\s*function[^\{]*\{([\s\S]*)\}\s*$/)[1]
              .replace(/    /g, '')
              .replace(/^\s+|\s+$/g, '');
      if (/^return /.test(message)) {
          message = message
              .replace(/^return /, '')
              .replace(/;$/, '');
      }
      return message;
  };
  
  var compareBoolean = function(actual, expected, message, options) {
      expected = typeof expected === 'string'
              ? PathItem.create(expected)
              : expected;
      if (typeof actual === 'function') {
          if (!message)
              message = getFunctionMessage(actual);
          actual = actual();
      }
      var parent,
          index,
          style = {
              strokeColor: 'black',
              fillColor: expected && (expected.closed
                  || expected.firstChild && expected.firstChild.closed && 'yellow')
                  || null
          };
      if (actual) {
          parent = actual.parent;
          index = actual.index;
          // Remove it from parent already now, in case we're comparing children
          // of compound-paths, so we can apply styling to them.
          if (parent && parent instanceof CompoundPath) {
              actual.remove();
          } else {
              parent = null;
          }
          actual.style = style;
      }
      if (expected) {
          expected.style = style;
      }
      equals(actual, expected, message, Base.set({ rasterize: true }, options));
      if (parent) {
          // Insert it back.
          parent.insertChild(index, actual);
      }
  };
  
  var createSVG = function(str, attrs) {
      // Similar to SvgElement.create():
      var node = document.createElementNS('http://www.w3.org/2000/svg', str);
      for (var key in attrs)
          node.setAttribute(key, attrs[key]);
      // Paper.js paths do not have a fill by default, SVG does.
      node.setAttribute('fill', 'none');
      return node;
  };
  
  var compareSVG = function(done, actual, expected, message, options) {
      function getItem(item) {
          return item instanceof Item
              ? item
              : typeof item === 'string'
              ? new Raster({
                  source: 'data:image/svg+xml;base64,' + window.btoa(item),
                  insert: false
              })
              : null;
      }
  
      actual = getItem(actual);
      expected = getItem(expected);
      actual.position = expected.position;
  
      if (typeof actual === 'function') {
          if (!message)
              message = getFunctionMessage(actual);
          actual = actual();
      }
  
      function compare() {
          comparePixels(actual, expected, message, Base.set({
              tolerance: 1e-3,
              resolution: 72
          }, options));
          done();
      }
  
      if (expected instanceof Raster) {
          expected.onLoad = compare;
      } else if (actual instanceof Raster) {
          actual.onLoad = compare;
      } else {
          compare();
      }
  };
  
  
  //
  // Interactions helpers
  //
  var MouseEventPolyfill = function(type, params) {
      var mouseEvent = document.createEvent('MouseEvent');
      mouseEvent.initMouseEvent(
          type,
          params.bubbles,
          params.cancelable,
          window,
          0,
          params.screenX,
          params.screenY,
          params.clientX,
          params.clientY,
          params.ctrlKey,
          params.altKey,
          params.shiftKey,
          params.metaKey,
          params.button,
          params.relatedTarget
      );
      return mouseEvent;
  };
  
  MouseEventPolyfill.prototype = nativeClasses.Event.prototype;
  
  var triggerMouseEvent = function(type, point, target) {
      // Depending on event type, events have to be triggered on different
      // elements due to the event handling implementation (see `viewEvents`
      // and `docEvents` in View.js). And we cannot rely on the fact that event
      // will bubble from canvas to document, since the canvas used in tests is
      // not inserted in DOM.
      target = target || (type === 'mousedown' ? view.element : document);
      // If `gulp load` was run, there is a name collision between paper Event /
      // MouseEvent and native javascript classes. In this case, we need to use
      // native classes stored in the nativeClasses object instead.
      // MouseEvent class does not exist in PhantomJS, so in that case, we need to
      // use a polyfill method, see: https://stackoverflow.com/questions/42929639
      var MouseEvent = typeof nativeClasses.MouseEvent === 'function'
          ? nativeClasses.MouseEvent
          : MouseEventPolyfill;
      var event = new MouseEvent(type, {
          bubbles: true,
          cancelable: true,
          composed: true,
          clientX: point.x,
          clientY: point.y,
          screenX: point.x,
          screenY: point.y
      });
      target.dispatchEvent(event);
  };
  